mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
chore: merge with main
This commit is contained in:
commit
da509392e9
104
.github/actions/flutter_build/action.yml
vendored
Normal file
104
.github/actions/flutter_build/action.yml
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
name: Flutter Integration Test
|
||||||
|
description: Run integration tests for AppFlowy
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
os:
|
||||||
|
description: "The operating system to run the tests on"
|
||||||
|
required: true
|
||||||
|
flutter_version:
|
||||||
|
description: "The version of Flutter to use"
|
||||||
|
required: true
|
||||||
|
rust_toolchain:
|
||||||
|
description: "The version of Rust to use"
|
||||||
|
required: true
|
||||||
|
cargo_make_version:
|
||||||
|
description: "The version of cargo-make to use"
|
||||||
|
required: true
|
||||||
|
rust_target:
|
||||||
|
description: "The target to build for"
|
||||||
|
required: true
|
||||||
|
flutter_profile:
|
||||||
|
description: "The profile to build with"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
id: rust_toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ inputs.rust_toolchain }}
|
||||||
|
target: ${{ inputs.rust_target }}
|
||||||
|
override: true
|
||||||
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Export pub environment variables and add to PATH
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_OS" == "Windows" ]; then
|
||||||
|
echo "PUB_CACHE=$LOCALAPPDATA\\Pub\\Cache" >> $GITHUB_ENV
|
||||||
|
fi
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
id: flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ inputs.flutter_version }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- uses: Swatinem/rust-cache@v2
|
||||||
|
with:
|
||||||
|
prefix-key: ${{ inputs.os }}
|
||||||
|
workspaces: |
|
||||||
|
frontend/rust-lib
|
||||||
|
cache-all-crates: true
|
||||||
|
|
||||||
|
- uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-make@${{ inputs.cargo_make_version }}, duckscript_cli
|
||||||
|
|
||||||
|
- name: Install prerequisites
|
||||||
|
working-directory: frontend
|
||||||
|
run: |
|
||||||
|
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||||
|
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||||
|
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
||||||
|
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||||
|
vcpkg integrate install
|
||||||
|
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||||
|
echo 'do nothing'
|
||||||
|
fi
|
||||||
|
cargo make appflowy-flutter-deps-tools
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Build AppFlowy
|
||||||
|
working-directory: frontend
|
||||||
|
run: cargo make --profile ${{ inputs.flutter_profile }} appflowy-core-dev
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Run code generation
|
||||||
|
working-directory: frontend
|
||||||
|
run: cargo make code_generation
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Flutter Analyzer
|
||||||
|
working-directory: frontend/appflowy_flutter
|
||||||
|
run: flutter analyze .
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Compress appflowy_flutter
|
||||||
|
run: tar -czf appflowy_flutter.tar.gz frontend/appflowy_flutter
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.run_id }}-${{ matrix.os }}
|
||||||
|
path: appflowy_flutter.tar.gz
|
78
.github/actions/flutter_integration_test/action.yml
vendored
Normal file
78
.github/actions/flutter_integration_test/action.yml
vendored
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
name: Flutter Integration Test
|
||||||
|
description: Run integration tests for AppFlowy
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
test_path:
|
||||||
|
description: "The path to the integration test file"
|
||||||
|
required: true
|
||||||
|
flutter_version:
|
||||||
|
description: "The version of Flutter to use"
|
||||||
|
required: true
|
||||||
|
rust_toolchain:
|
||||||
|
description: "The version of Rust to use"
|
||||||
|
required: true
|
||||||
|
cargo_make_version:
|
||||||
|
description: "The version of cargo-make to use"
|
||||||
|
required: true
|
||||||
|
rust_target:
|
||||||
|
description: "The target to build for"
|
||||||
|
required: true
|
||||||
|
|
||||||
|
runs:
|
||||||
|
using: "composite"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Rust toolchain
|
||||||
|
id: rust_toolchain
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: ${{ inputs.RUST_TOOLCHAIN }}
|
||||||
|
target: ${{ inputs.rust_target }}
|
||||||
|
override: true
|
||||||
|
profile: minimal
|
||||||
|
|
||||||
|
- name: Install flutter
|
||||||
|
id: flutter
|
||||||
|
uses: subosito/flutter-action@v2
|
||||||
|
with:
|
||||||
|
channel: "stable"
|
||||||
|
flutter-version: ${{ inputs.flutter_version }}
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- uses: taiki-e/install-action@v2
|
||||||
|
with:
|
||||||
|
tool: cargo-make@${{ inputs.cargo_make_version }}
|
||||||
|
|
||||||
|
- name: Install prerequisites
|
||||||
|
working-directory: frontend
|
||||||
|
run: |
|
||||||
|
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
||||||
|
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev network-manager
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Enable Flutter Desktop
|
||||||
|
run: |
|
||||||
|
flutter config --enable-linux-desktop
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: ${{ github.run_id }}-ubuntu-latest
|
||||||
|
|
||||||
|
- name: Uncompressed appflowy_flutter
|
||||||
|
run: tar -xf appflowy_flutter.tar.gz
|
||||||
|
shell: bash
|
||||||
|
|
||||||
|
- name: Run Flutter integration tests
|
||||||
|
working-directory: frontend/appflowy_flutter
|
||||||
|
run: |
|
||||||
|
export DISPLAY=:99
|
||||||
|
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
||||||
|
sudo apt-get install network-manager
|
||||||
|
flutter test ${{ inputs.test_path }} -d Linux --coverage
|
||||||
|
shell: bash
|
317
.github/workflows/flutter_ci.yaml
vendored
317
.github/workflows/flutter_ci.yaml
vendored
@ -32,28 +32,21 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
prepare:
|
prepare-linux:
|
||||||
if: github.event.pull_request.draft != true
|
if: github.event.pull_request.draft != true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: true
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest]
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
flutter_profile: development-linux-x86_64
|
flutter_profile: development-linux-x86_64
|
||||||
target: x86_64-unknown-linux-gnu
|
target: x86_64-unknown-linux-gnu
|
||||||
- os: macos-latest
|
|
||||||
flutter_profile: development-mac-x86_64
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
- os: windows-latest
|
|
||||||
flutter_profile: development-windows-x86
|
|
||||||
target: x86_64-pc-windows-msvc
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
# the following step is required to avoid running out of space
|
# the following step is required to avoid running out of space
|
||||||
- name: Maximize build space
|
- name: Maximize build space
|
||||||
if: matrix.os == 'ubuntu-latest'
|
|
||||||
run: |
|
run: |
|
||||||
sudo rm -rf /usr/share/dotnet
|
sudo rm -rf /usr/share/dotnet
|
||||||
sudo rm -rf /opt/ghc
|
sudo rm -rf /opt/ghc
|
||||||
@ -63,80 +56,70 @@ jobs:
|
|||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Flutter build
|
||||||
id: rust_toolchain
|
uses: ./.github/actions/flutter_build
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
os: ${{ matrix.os }}
|
||||||
target: ${{ matrix.target }}
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
override: true
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
profile: minimal
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
|
rust_target: ${{ matrix.target }}
|
||||||
|
flutter_profile: ${{ matrix.flutter_profile }}
|
||||||
|
|
||||||
- name: Export pub environment variables and add to PATH
|
prepare-windows:
|
||||||
run: |
|
if: github.event.pull_request.draft != true
|
||||||
if [ "$RUNNER_OS" == "Windows" ]; then
|
strategy:
|
||||||
echo "PUB_CACHE=$LOCALAPPDATA\\Pub\\Cache" >> $GITHUB_ENV
|
fail-fast: true
|
||||||
fi
|
matrix:
|
||||||
shell: bash
|
os: [windows-latest]
|
||||||
|
include:
|
||||||
|
- os: windows-latest
|
||||||
|
flutter_profile: development-windows-x86
|
||||||
|
target: x86_64-pc-windows-msvc
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
- name: Install flutter
|
steps:
|
||||||
id: flutter
|
- name: Checkout source code
|
||||||
uses: subosito/flutter-action@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Flutter build
|
||||||
|
uses: ./.github/actions/flutter_build
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
os: ${{ matrix.os }}
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
|
rust_target: ${{ matrix.target }}
|
||||||
|
flutter_profile: ${{ matrix.flutter_profile }}
|
||||||
|
|
||||||
- uses: Swatinem/rust-cache@v2
|
prepare-macos:
|
||||||
|
if: github.event.pull_request.draft != true
|
||||||
|
strategy:
|
||||||
|
fail-fast: true
|
||||||
|
matrix:
|
||||||
|
os: [macos-latest]
|
||||||
|
include:
|
||||||
|
- os: macos-latest
|
||||||
|
flutter_profile: development-mac-x86_64
|
||||||
|
target: x86_64-apple-darwin
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Flutter build
|
||||||
|
uses: ./.github/actions/flutter_build
|
||||||
with:
|
with:
|
||||||
prefix-key: ${{ matrix.os }}
|
os: ${{ matrix.os }}
|
||||||
workspaces: |
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
frontend/rust-lib
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
cache-all-crates: true
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
|
rust_target: ${{ matrix.target }}
|
||||||
- uses: taiki-e/install-action@v2
|
flutter_profile: ${{ matrix.flutter_profile }}
|
||||||
with:
|
|
||||||
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}, duckscript_cli
|
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
working-directory: frontend
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
|
||||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
|
||||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
|
||||||
vcpkg integrate install
|
|
||||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
|
||||||
echo 'do nothing'
|
|
||||||
fi
|
|
||||||
cargo make appflowy-flutter-deps-tools
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Build AppFlowy
|
|
||||||
working-directory: frontend
|
|
||||||
run: cargo make --profile ${{ matrix.flutter_profile }} appflowy-core-dev
|
|
||||||
|
|
||||||
- name: Run code generation
|
|
||||||
working-directory: frontend
|
|
||||||
run: cargo make code_generation
|
|
||||||
|
|
||||||
- name: Flutter Analyzer
|
|
||||||
working-directory: frontend/appflowy_flutter
|
|
||||||
run: flutter analyze .
|
|
||||||
|
|
||||||
- name: Compress appflowy_flutter
|
|
||||||
run: |
|
|
||||||
tar -czf appflowy_flutter.tar.gz frontend/appflowy_flutter
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
|
||||||
path: appflowy_flutter.tar.gz
|
|
||||||
|
|
||||||
unit_test:
|
unit_test:
|
||||||
needs: [prepare]
|
needs: [prepare-linux]
|
||||||
if: github.event.pull_request.draft != true
|
if: github.event.pull_request.draft != true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -227,7 +210,7 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
cloud_integration_test:
|
cloud_integration_test:
|
||||||
needs: [prepare]
|
needs: [prepare-linux]
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@ -314,8 +297,9 @@ jobs:
|
|||||||
flutter test integration_test/cloud/cloud_runner.dart -d Linux --coverage
|
flutter test integration_test/cloud/cloud_runner.dart -d Linux --coverage
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|
||||||
integration_test:
|
# split the integration tests into different machines to minimize the time
|
||||||
needs: [prepare]
|
integration_test_1:
|
||||||
|
needs: [prepare-linux]
|
||||||
if: github.event.pull_request.draft != true
|
if: github.event.pull_request.draft != true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
@ -323,158 +307,65 @@ jobs:
|
|||||||
os: [ubuntu-latest]
|
os: [ubuntu-latest]
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
flutter_profile: development-linux-x86_64
|
target: 'x86_64-unknown-linux-gnu'
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Flutter Integration Test 1
|
||||||
id: rust_toolchain
|
uses: ./.github/actions/flutter_integration_test
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
test_path: integration_test/desktop_runner_1.dart
|
||||||
target: ${{ matrix.target }}
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
override: true
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
profile: minimal
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
|
rust_target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Install flutter
|
integration_test_2:
|
||||||
id: flutter
|
needs: [prepare-linux]
|
||||||
uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: "stable"
|
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
|
||||||
cache: true
|
|
||||||
|
|
||||||
- uses: taiki-e/install-action@v2
|
|
||||||
with:
|
|
||||||
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
working-directory: frontend
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
|
||||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Enable Flutter Desktop
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
flutter config --enable-linux-desktop
|
|
||||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
|
||||||
flutter config --enable-macos-desktop
|
|
||||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
|
||||||
git config --system core.longpaths true
|
|
||||||
flutter config --enable-windows-desktop
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
|
||||||
|
|
||||||
- name: Uncompressed appflowy_flutter
|
|
||||||
run: tar -xf appflowy_flutter.tar.gz
|
|
||||||
|
|
||||||
- name: Run flutter pub get
|
|
||||||
working-directory: frontend
|
|
||||||
run: cargo make pub_get
|
|
||||||
|
|
||||||
- name: Run Flutter integration tests
|
|
||||||
working-directory: frontend/appflowy_flutter
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
export DISPLAY=:99
|
|
||||||
sudo Xvfb -ac :99 -screen 0 1280x1024x24 > /dev/null 2>&1 &
|
|
||||||
sudo apt-get install network-manager
|
|
||||||
flutter test integration_test/runner.dart -d Linux --coverage
|
|
||||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
|
||||||
flutter test integration_test/runner.dart -d macOS --coverage
|
|
||||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
|
||||||
flutter test integration_test/runner.dart -d Windows --coverage
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
build:
|
|
||||||
needs: [prepare]
|
|
||||||
if: github.event.pull_request.draft != true
|
if: github.event.pull_request.draft != true
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
os: [ubuntu-latest]
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
flutter_profile: development-linux-x86_64
|
target: 'x86_64-unknown-linux-gnu'
|
||||||
target: x86_64-unknown-linux-gnu
|
|
||||||
- os: macos-latest
|
|
||||||
flutter_profile: development-mac-x86_64
|
|
||||||
target: x86_64-apple-darwin
|
|
||||||
- os: windows-latest
|
|
||||||
flutter_profile: development-windows-x86
|
|
||||||
target: x86_64-pc-windows-msvc
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Flutter Integration Test 2
|
||||||
id: rust_toolchain
|
uses: ./.github/actions/flutter_integration_test
|
||||||
uses: actions-rs/toolchain@v1
|
|
||||||
with:
|
with:
|
||||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
test_path: integration_test/desktop_runner_2.dart
|
||||||
target: ${{ matrix.target }}
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
override: true
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
profile: minimal
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
|
rust_target: ${{ matrix.target }}
|
||||||
|
|
||||||
- name: Install flutter
|
integration_test_3:
|
||||||
id: flutter
|
needs: [prepare-linux]
|
||||||
uses: subosito/flutter-action@v2
|
if: github.event.pull_request.draft != true
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
target: 'x86_64-unknown-linux-gnu'
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout source code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Flutter Integration Test 3
|
||||||
|
uses: ./.github/actions/flutter_integration_test
|
||||||
with:
|
with:
|
||||||
channel: "stable"
|
test_path: integration_test/desktop_runner_3.dart
|
||||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
flutter_version: ${{ env.FLUTTER_VERSION }}
|
||||||
cache: true
|
rust_toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||||
|
cargo_make_version: ${{ env.CARGO_MAKE_VERSION }}
|
||||||
- uses: taiki-e/install-action@v2
|
rust_target: ${{ matrix.target }}
|
||||||
with:
|
|
||||||
tool: cargo-make@${{ env.CARGO_MAKE_VERSION }}
|
|
||||||
|
|
||||||
- name: Install prerequisites
|
|
||||||
working-directory: frontend
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
sudo wget -qO /etc/apt/trusted.gpg.d/dart_linux_signing_key.asc https://dl-ssl.google.com/linux/linux_signing_key.pub
|
|
||||||
sudo wget -qO /etc/apt/sources.list.d/dart_stable.list https://storage.googleapis.com/download.dartlang.org/linux/debian/dart_stable.list
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y dart curl build-essential libssl-dev clang cmake ninja-build pkg-config libgtk-3-dev keybinder-3.0 libnotify-dev
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- name: Enable Flutter Desktop
|
|
||||||
run: |
|
|
||||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
|
||||||
flutter config --enable-linux-desktop
|
|
||||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
|
||||||
flutter config --enable-macos-desktop
|
|
||||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
|
||||||
git config --system core.longpaths true
|
|
||||||
flutter config --enable-windows-desktop
|
|
||||||
fi
|
|
||||||
shell: bash
|
|
||||||
|
|
||||||
- uses: actions/download-artifact@v4
|
|
||||||
with:
|
|
||||||
name: ${{ github.run_id }}-${{ matrix.os }}
|
|
||||||
|
|
||||||
- name: Uncompressed appflowy_flutter
|
|
||||||
run: tar -xf appflowy_flutter.tar.gz
|
|
||||||
|
|
||||||
- name: Build flutter product
|
|
||||||
working-directory: frontend
|
|
||||||
run: |
|
|
||||||
cargo make --profile ${{ matrix.flutter_profile }} appflowy-make-product-dev
|
|
21
.github/workflows/rust_ci.yaml
vendored
21
.github/workflows/rust_ci.yaml
vendored
@ -25,6 +25,22 @@ jobs:
|
|||||||
test-on-ubuntu:
|
test-on-ubuntu:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
# - name: Maximize build space
|
||||||
|
# uses: easimon/maximize-build-space@master
|
||||||
|
# with:
|
||||||
|
# root-reserve-mb: 2048
|
||||||
|
# swap-size-mb: 1024
|
||||||
|
# remove-dotnet: 'true'
|
||||||
|
#
|
||||||
|
# # the following step is required to avoid running out of space
|
||||||
|
# - 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
|
||||||
|
|
||||||
- name: Checkout source code
|
- name: Checkout source code
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
@ -86,3 +102,8 @@ jobs:
|
|||||||
- name: clippy rust-lib
|
- name: clippy rust-lib
|
||||||
run: cargo clippy --all-targets -- -D warnings
|
run: cargo clippy --all-targets -- -D warnings
|
||||||
working-directory: frontend/rust-lib
|
working-directory: frontend/rust-lib
|
||||||
|
|
||||||
|
- name: Clean up Docker images
|
||||||
|
run: |
|
||||||
|
docker image prune -af
|
||||||
|
docker volume prune -f
|
||||||
|
77
.github/workflows/tauri_ci.yaml
vendored
77
.github/workflows/tauri_ci.yaml
vendored
@ -25,32 +25,33 @@ jobs:
|
|||||||
platform: [ubuntu-latest]
|
platform: [ubuntu-latest]
|
||||||
|
|
||||||
runs-on: ${{ matrix.platform }}
|
runs-on: ${{ matrix.platform }}
|
||||||
|
|
||||||
|
env:
|
||||||
|
CI: true
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Maximize build space (ubuntu only)
|
||||||
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
|
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
|
- name: setup node
|
||||||
uses: actions/setup-node@v3
|
uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: ${{ env.NODE_VERSION }}
|
node-version: ${{ env.NODE_VERSION }}
|
||||||
|
|
||||||
- name: Cache Rust Dependencies
|
- name: setup pnpm
|
||||||
uses: Swatinem/rust-cache@v2
|
uses: pnpm/action-setup@v2
|
||||||
with:
|
with:
|
||||||
key: rust-dependencies-${{ runner.os }}
|
version: ${{ env.PNPM_VERSION }}
|
||||||
workspaces: |
|
|
||||||
frontend/rust-lib
|
|
||||||
frontend/appflowy_tauri/src-tauri
|
|
||||||
|
|
||||||
- name: Cache Node.js dependencies
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: ~/.npm
|
|
||||||
key: npm-${{ runner.os }}
|
|
||||||
|
|
||||||
- name: Cache node_modules
|
|
||||||
uses: actions/cache@v2
|
|
||||||
with:
|
|
||||||
path: frontend/appflowy_tauri/node_modules
|
|
||||||
key: node-modules-${{ runner.os }}
|
|
||||||
|
|
||||||
- name: Install Rust toolchain
|
- name: Install Rust toolchain
|
||||||
id: rust_toolchain
|
id: rust_toolchain
|
||||||
@ -60,15 +61,23 @@ jobs:
|
|||||||
override: true
|
override: true
|
||||||
profile: minimal
|
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)
|
- name: install dependencies (windows only)
|
||||||
if: matrix.platform == 'windows-latest'
|
if: matrix.platform == 'windows-latest'
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: |
|
run: |
|
||||||
cargo install --force cargo-make
|
|
||||||
cargo install --force duckscript_cli
|
cargo install --force duckscript_cli
|
||||||
vcpkg integrate install
|
vcpkg integrate install
|
||||||
cargo make appflowy-tauri-deps-tools
|
|
||||||
npm install -g pnpm@${{ env.PNPM_VERSION }}
|
|
||||||
|
|
||||||
- name: install dependencies (ubuntu only)
|
- name: install dependencies (ubuntu only)
|
||||||
if: matrix.platform == 'ubuntu-latest'
|
if: matrix.platform == 'ubuntu-latest'
|
||||||
@ -76,35 +85,29 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf
|
||||||
cargo install --force cargo-make
|
|
||||||
cargo make appflowy-tauri-deps-tools
|
|
||||||
npm install -g pnpm@${{ env.PNPM_VERSION }}
|
|
||||||
|
|
||||||
- name: install dependencies (macOS only)
|
- name: install cargo-make
|
||||||
if: matrix.platform == 'macos-latest'
|
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: |
|
run: |
|
||||||
cargo install --force cargo-make
|
cargo install --force cargo-make
|
||||||
cargo make appflowy-tauri-deps-tools
|
cargo make appflowy-tauri-deps-tools
|
||||||
npm install -g pnpm@${{ env.PNPM_VERSION }}
|
|
||||||
|
|
||||||
- name: Build
|
- name: install frontend dependencies
|
||||||
working-directory: frontend/appflowy_tauri
|
working-directory: frontend/appflowy_tauri
|
||||||
run: |
|
run: |
|
||||||
mkdir dist
|
mkdir dist
|
||||||
pnpm install
|
pnpm install
|
||||||
cargo make --cwd .. tauri_build
|
cargo make --cwd .. tauri_build
|
||||||
|
|
||||||
|
- name: frontend tests and linting
|
||||||
|
working-directory: frontend/appflowy_tauri
|
||||||
|
run: |
|
||||||
pnpm test
|
pnpm test
|
||||||
pnpm test:errors
|
pnpm test:errors
|
||||||
|
|
||||||
- name: Check for uncommitted changes
|
|
||||||
run: |
|
|
||||||
diff_files=$(git status --porcelain)
|
|
||||||
if [ -n "$diff_files" ]; then
|
|
||||||
echo "There are uncommitted changes in the working tree. Please commit them before pushing."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
with:
|
||||||
|
tauriScript: pnpm tauri
|
||||||
|
projectPath: frontend/appflowy_tauri
|
153
.github/workflows/tauri_release.yml
vendored
Normal file
153
.github/workflows/tauri_release.yml
vendored
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
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.75"
|
||||||
|
|
||||||
|
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-latest
|
||||||
|
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-latest'
|
||||||
|
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 duckscript_cli
|
||||||
|
vcpkg integrate install
|
||||||
|
|
||||||
|
- name: install dependencies (ubuntu only)
|
||||||
|
if: matrix.settings.platform == 'ubuntu-latest'
|
||||||
|
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 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-latest'
|
||||||
|
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-latest'
|
||||||
|
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
|
@ -16,7 +16,7 @@ void main() {
|
|||||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||||
await tester.tapCreateRowButtonInGrid();
|
await tester.tapCreateRowButtonInGrid();
|
||||||
|
|
||||||
// The initial number of rows is 3
|
// 3 initial rows + 1 created
|
||||||
await tester.assertNumberOfRowsInGridPage(4);
|
await tester.assertNumberOfRowsInGridPage(4);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
@ -31,9 +31,8 @@ void main() {
|
|||||||
|
|
||||||
await tester.tapCreateRowButtonInRowMenuOfGrid();
|
await tester.tapCreateRowButtonInRowMenuOfGrid();
|
||||||
|
|
||||||
// The initial number of rows is 3
|
// 3 initial rows + 1 created
|
||||||
await tester.assertNumberOfRowsInGridPage(4);
|
await tester.assertNumberOfRowsInGridPage(4);
|
||||||
await tester.assertRowCountInGridPage(4);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -48,9 +47,8 @@ void main() {
|
|||||||
await tester.tapRowMenuButtonInGrid();
|
await tester.tapRowMenuButtonInGrid();
|
||||||
await tester.tapDeleteOnRowMenu();
|
await tester.tapDeleteOnRowMenu();
|
||||||
|
|
||||||
// The initial number of rows is 3
|
// 3 initial rows - 1 deleted
|
||||||
await tester.assertNumberOfRowsInGridPage(2);
|
await tester.assertNumberOfRowsInGridPage(2);
|
||||||
await tester.assertRowCountInGridPage(2);
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -60,7 +58,6 @@ void main() {
|
|||||||
await tester.tapGoButton();
|
await tester.tapGoButton();
|
||||||
|
|
||||||
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
await tester.createNewPageWithNameUnderParent(layout: ViewLayoutPB.Grid);
|
||||||
await tester.assertRowCountInGridPage(3);
|
|
||||||
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
});
|
});
|
||||||
|
@ -1,68 +0,0 @@
|
|||||||
import 'desktop/uncategorized/appearance_settings_test.dart' as appearance_test_runner;
|
|
||||||
import 'desktop/board/board_test_runner.dart' as board_test_runner;
|
|
||||||
import 'desktop/database/database_calendar_test.dart' as database_calendar_test;
|
|
||||||
import 'desktop/database/database_cell_test.dart' as database_cell_test;
|
|
||||||
import 'desktop/database/database_field_settings_test.dart'
|
|
||||||
as database_field_settings_test;
|
|
||||||
import 'desktop/database/database_field_test.dart' as database_field_test;
|
|
||||||
import 'desktop/database/database_filter_test.dart' as database_filter_test;
|
|
||||||
import 'desktop/database/database_row_page_test.dart' as database_row_page_test;
|
|
||||||
import 'desktop/database/database_row_test.dart' as database_row_test;
|
|
||||||
import 'desktop/database/database_setting_test.dart' as database_setting_test;
|
|
||||||
import 'desktop/database/database_share_test.dart' as database_share_test;
|
|
||||||
import 'desktop/database/database_sort_test.dart' as database_sort_test;
|
|
||||||
import 'desktop/database/database_view_test.dart' as database_view_test;
|
|
||||||
import 'desktop/document/document_test_runner.dart' as document_test_runner;
|
|
||||||
import 'desktop/uncategorized/emoji_shortcut_test.dart' as emoji_shortcut_test;
|
|
||||||
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
|
||||||
import 'desktop/uncategorized/hotkeys_test.dart' as hotkeys_test;
|
|
||||||
import 'desktop/uncategorized/import_files_test.dart' as import_files_test;
|
|
||||||
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
|
||||||
import 'desktop/uncategorized/share_markdown_test.dart' as share_markdown_test;
|
|
||||||
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
|
||||||
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
|
|
||||||
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
|
|
||||||
|
|
||||||
Future<void> runIntegrationOnDesktop() async {
|
|
||||||
// This test must be run first, otherwise the CI will fail.
|
|
||||||
first_test.main();
|
|
||||||
|
|
||||||
switch_folder_test.main();
|
|
||||||
share_markdown_test.main();
|
|
||||||
import_files_test.main();
|
|
||||||
|
|
||||||
// Document integration tests
|
|
||||||
document_test_runner.startTesting();
|
|
||||||
|
|
||||||
// Sidebar integration tests
|
|
||||||
sidebar_test_runner.startTesting();
|
|
||||||
|
|
||||||
// Board integration test
|
|
||||||
board_test_runner.startTesting();
|
|
||||||
|
|
||||||
// Database integration tests
|
|
||||||
database_cell_test.main();
|
|
||||||
database_field_test.main();
|
|
||||||
database_field_settings_test.main();
|
|
||||||
database_share_test.main();
|
|
||||||
database_row_page_test.main();
|
|
||||||
database_row_test.main();
|
|
||||||
database_setting_test.main();
|
|
||||||
database_filter_test.main();
|
|
||||||
database_sort_test.main();
|
|
||||||
database_view_test.main();
|
|
||||||
database_calendar_test.main();
|
|
||||||
|
|
||||||
// Tabs
|
|
||||||
tabs_test.main();
|
|
||||||
|
|
||||||
// Others
|
|
||||||
hotkeys_test.main();
|
|
||||||
emoji_shortcut_test.main();
|
|
||||||
|
|
||||||
// Appearance integration test
|
|
||||||
appearance_test_runner.main();
|
|
||||||
|
|
||||||
// User settings
|
|
||||||
settings_test_runner.main();
|
|
||||||
}
|
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'desktop/document/document_test_runner.dart' as document_test_runner;
|
||||||
|
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||||
|
import 'desktop/uncategorized/switch_folder_test.dart' as switch_folder_test;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
await runIntegration1OnDesktop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> runIntegration1OnDesktop() async {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// This test must be run first, otherwise the CI will fail.
|
||||||
|
first_test.main();
|
||||||
|
|
||||||
|
switch_folder_test.main();
|
||||||
|
document_test_runner.startTesting();
|
||||||
|
|
||||||
|
// DON'T add more tests here. This is the first test runner for desktop.
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'desktop/database/database_calendar_test.dart' as database_calendar_test;
|
||||||
|
import 'desktop/database/database_cell_test.dart' as database_cell_test;
|
||||||
|
import 'desktop/database/database_field_settings_test.dart'
|
||||||
|
as database_field_settings_test;
|
||||||
|
import 'desktop/database/database_field_test.dart' as database_field_test;
|
||||||
|
import 'desktop/database/database_filter_test.dart' as database_filter_test;
|
||||||
|
import 'desktop/database/database_row_page_test.dart' as database_row_page_test;
|
||||||
|
import 'desktop/database/database_row_test.dart' as database_row_test;
|
||||||
|
import 'desktop/database/database_setting_test.dart' as database_setting_test;
|
||||||
|
import 'desktop/database/database_share_test.dart' as database_share_test;
|
||||||
|
import 'desktop/database/database_sort_test.dart' as database_sort_test;
|
||||||
|
import 'desktop/database/database_view_test.dart' as database_view_test;
|
||||||
|
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
await runIntegration2OnDesktop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> runIntegration2OnDesktop() async {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// This test must be run first, otherwise the CI will fail.
|
||||||
|
first_test.main();
|
||||||
|
|
||||||
|
database_cell_test.main();
|
||||||
|
database_field_test.main();
|
||||||
|
database_field_settings_test.main();
|
||||||
|
database_share_test.main();
|
||||||
|
database_row_page_test.main();
|
||||||
|
database_row_test.main();
|
||||||
|
database_setting_test.main();
|
||||||
|
database_filter_test.main();
|
||||||
|
database_sort_test.main();
|
||||||
|
database_view_test.main();
|
||||||
|
database_calendar_test.main();
|
||||||
|
|
||||||
|
// DON'T add more tests here. This is the second test runner for desktop.
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
|
import 'desktop/board/board_test_runner.dart' as board_test_runner;
|
||||||
|
import 'desktop/settings/settings_runner.dart' as settings_test_runner;
|
||||||
|
import 'desktop/sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||||
|
import 'desktop/uncategorized/appearance_settings_test.dart'
|
||||||
|
as appearance_test_runner;
|
||||||
|
import 'desktop/uncategorized/emoji_shortcut_test.dart' as emoji_shortcut_test;
|
||||||
|
import 'desktop/uncategorized/empty_test.dart' as first_test;
|
||||||
|
import 'desktop/uncategorized/hotkeys_test.dart' as hotkeys_test;
|
||||||
|
import 'desktop/uncategorized/import_files_test.dart' as import_files_test;
|
||||||
|
import 'desktop/uncategorized/share_markdown_test.dart' as share_markdown_test;
|
||||||
|
import 'desktop/uncategorized/tabs_test.dart' as tabs_test;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
|
await runIntegration3OnDesktop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> runIntegration3OnDesktop() async {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// This test must be run first, otherwise the CI will fail.
|
||||||
|
first_test.main();
|
||||||
|
|
||||||
|
hotkeys_test.main();
|
||||||
|
emoji_shortcut_test.main();
|
||||||
|
hotkeys_test.main();
|
||||||
|
emoji_shortcut_test.main();
|
||||||
|
appearance_test_runner.main();
|
||||||
|
settings_test_runner.main();
|
||||||
|
share_markdown_test.main();
|
||||||
|
import_files_test.main();
|
||||||
|
sidebar_test_runner.startTesting();
|
||||||
|
board_test_runner.startTesting();
|
||||||
|
tabs_test.main();
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
|
||||||
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
|
import 'mobile/sign_in/anonymous_sign_in_test.dart' as anonymous_sign_in_test;
|
||||||
|
|
||||||
Future<void> runIntegrationOnMobile() async {
|
Future<void> runIntegrationOnMobile() async {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
anonymous_sign_in_test.main();
|
anonymous_sign_in_test.main();
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'desktop_runner_1.dart';
|
||||||
|
import 'desktop_runner_2.dart';
|
||||||
import 'desktop_runner.dart';
|
import 'desktop_runner_3.dart';
|
||||||
import 'mobile_runner.dart';
|
import 'mobile_runner.dart';
|
||||||
|
|
||||||
/// The main task runner for all integration tests in AppFlowy.
|
/// The main task runner for all integration tests in AppFlowy.
|
||||||
@ -13,9 +13,10 @@ import 'mobile_runner.dart';
|
|||||||
/// Once removed, the integration_test.yaml must be updated to exclude this as
|
/// Once removed, the integration_test.yaml must be updated to exclude this as
|
||||||
/// as the test target.
|
/// as the test target.
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) {
|
||||||
await runIntegrationOnDesktop();
|
await runIntegration1OnDesktop();
|
||||||
|
await runIntegration2OnDesktop();
|
||||||
|
await runIntegration3OnDesktop();
|
||||||
} else if (Platform.isIOS || Platform.isAndroid) {
|
} else if (Platform.isIOS || Platform.isAndroid) {
|
||||||
await runIntegrationOnMobile();
|
await runIntegrationOnMobile();
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,26 +1,12 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/plugins/database/application/calculations/calculation_type_ext.dart';
|
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart';
|
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/number.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';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/number.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart';
|
|
||||||
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/url.dart';
|
|
||||||
import 'package:appflowy/util/field_type_extension.dart';
|
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||||
import 'package:appflowy/generated/locale_keys.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/board/presentation/board_page.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/board/presentation/widgets/board_column_header.dart';
|
||||||
import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart';
|
import 'package:appflowy/plugins/database/calendar/application/calendar_bloc.dart';
|
||||||
@ -30,6 +16,9 @@ import 'package:appflowy/plugins/database/calendar/presentation/calendar_event_e
|
|||||||
import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart';
|
import 'package:appflowy/plugins/database/calendar/presentation/calendar_page.dart';
|
||||||
import 'package:appflowy/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
import 'package:appflowy/plugins/database/calendar/presentation/toolbar/calendar_layout_setting.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/grid_page.dart';
|
||||||
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculate_cell.dart';
|
||||||
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
||||||
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/common/type_option_separator.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checkbox.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist/checklist.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/checklist/checklist.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/option_list.dart';
|
||||||
@ -43,6 +32,7 @@ import 'package:appflowy/plugins/database/grid/presentation/widgets/header/deskt
|
|||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_editor.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_list.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/field_type_list.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/date/date_time_format.dart';
|
||||||
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/header/type_option/number.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/row/row.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/create_sort_list.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/order_panel.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/sort/order_panel.dart';
|
||||||
@ -52,14 +42,22 @@ 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/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_add_button.dart';
|
||||||
import 'package:appflowy/plugins/database/tab_bar/desktop/tab_bar_header.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/database/widgets/cell/editable_cell_skeleton/checkbox.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.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';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/number.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/select_option.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/text.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/timestamp.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/cell/editable_cell_skeleton/url.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.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/cell_editor/checklist_progress_bar.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/date_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/extension.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_editor.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_editor.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_text_field.dart';
|
import 'package:appflowy/plugins/database/widgets/cell_editor/select_option_text_field.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/database_layout_ext.dart';
|
||||||
|
import 'package:appflowy/plugins/database/widgets/row/accessory/cell_accessory.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/row_action.dart';
|
import 'package:appflowy/plugins/database/widgets/row/row_action.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
|
import 'package:appflowy/plugins/database/widgets/row/row_banner.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
import 'package:appflowy/plugins/database/widgets/row/row_detail.dart';
|
||||||
@ -70,6 +68,7 @@ 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/database_settings_list.dart';
|
||||||
import 'package:appflowy/plugins/database/widgets/setting/setting_button.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/plugins/database/widgets/setting/setting_property_list.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/clear_date_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
|
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_type_option_button.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/end_time_button.dart';
|
||||||
@ -77,6 +76,7 @@ import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/remi
|
|||||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:appflowy_board/appflowy_board.dart';
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:calendar_view/calendar_view.dart';
|
import 'package:calendar_view/calendar_view.dart';
|
||||||
@ -86,10 +86,9 @@ import 'package:flowy_infra_ui/style_widget/text_input.dart';
|
|||||||
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
import 'package:flowy_infra_ui/widget/buttons/primary_button.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:table_calendar/table_calendar.dart';
|
|
||||||
|
|
||||||
// Non-exported member of the table_calendar library
|
// Non-exported member of the table_calendar library
|
||||||
import 'package:table_calendar/src/widgets/cell_content.dart';
|
import 'package:table_calendar/src/widgets/cell_content.dart';
|
||||||
|
import 'package:table_calendar/table_calendar.dart';
|
||||||
|
|
||||||
import 'base.dart';
|
import 'base.dart';
|
||||||
import 'common_operations.dart';
|
import 'common_operations.dart';
|
||||||
@ -974,11 +973,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
|||||||
await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
|
await tapButtonWithName(LocaleKeys.grid_row_delete.tr());
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> assertRowCountInGridPage(int num) async {
|
|
||||||
final text = find.text('${rowCountString()} $num', findRichText: true);
|
|
||||||
expect(text, findsOneWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> createField(FieldType fieldType, String name) async {
|
Future<void> createField(FieldType fieldType, String name) async {
|
||||||
await scrollToRight(find.byType(GridPage));
|
await scrollToRight(find.byType(GridPage));
|
||||||
await tapNewPropertyButton();
|
await tapNewPropertyButton();
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
|
||||||
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart';
|
||||||
@ -14,8 +17,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/embe
|
|||||||
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
@ -132,8 +133,7 @@ class EditorOperations {
|
|||||||
of: find.byType(EmbedImageUrlWidget),
|
of: find.byType(EmbedImageUrlWidget),
|
||||||
matching: find.byType(TextField),
|
matching: find.byType(TextField),
|
||||||
);
|
);
|
||||||
final textField = tester.widget<TextField>(imageUrlTextField);
|
await tester.enterText(imageUrlTextField, imageUrl);
|
||||||
textField.controller?.text = imageUrl;
|
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
await tester.tapButton(
|
await tester.tapButton(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
|
@ -58,4 +58,10 @@ class KVKeys {
|
|||||||
/// The value range is from 0.8 to 1.0. If it's greater than 1.0, it will cause
|
/// The value range is from 0.8 to 1.0. If it's greater than 1.0, it will cause
|
||||||
/// the text to be too large and not aligned with the icon
|
/// the text to be too large and not aligned with the icon
|
||||||
static const String textScaleFactor = 'textScaleFactor';
|
static const String textScaleFactor = 'textScaleFactor';
|
||||||
|
|
||||||
|
/// The key for saving the feature flags
|
||||||
|
///
|
||||||
|
/// The value is a json string with the following format:
|
||||||
|
/// {'feature_flag_1': true, 'feature_flag_2': false}
|
||||||
|
static const String featureFlag = 'featureFlag';
|
||||||
}
|
}
|
||||||
|
@ -281,7 +281,7 @@ Future<AppFlowyCloudConfiguration> configurationFromUri(
|
|||||||
if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) {
|
if (authenticatorType == AuthenticatorType.appflowyCloudDevelop) {
|
||||||
return AppFlowyCloudConfiguration(
|
return AppFlowyCloudConfiguration(
|
||||||
base_url: "$baseUrl:8000",
|
base_url: "$baseUrl:8000",
|
||||||
ws_base_url: "ws://${baseUri.host}:8000/ws",
|
ws_base_url: "ws://${baseUri.host}:8000/ws/v1",
|
||||||
gotrue_url: "$baseUrl:9999",
|
gotrue_url: "$baseUrl:9999",
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@ -319,7 +319,7 @@ Future<String> _getAppFlowyCloudWSUrl(String baseURL) async {
|
|||||||
|
|
||||||
// Construct the WebSocket URL directly from the parsed URI.
|
// Construct the WebSocket URL directly from the parsed URI.
|
||||||
final wsScheme = uri.isScheme('HTTPS') ? 'wss' : 'ws';
|
final wsScheme = uri.isScheme('HTTPS') ? 'wss' : 'ws';
|
||||||
final wsUrl = Uri(scheme: wsScheme, host: uri.host, path: '/ws');
|
final wsUrl = Uri(scheme: wsScheme, host: uri.host, path: '/ws/v1');
|
||||||
|
|
||||||
return wsUrl.toString();
|
return wsUrl.toString();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -42,12 +42,13 @@ class OptionTextField extends StatelessWidget {
|
|||||||
width: 38,
|
width: 38,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
color: type.mobileIconBackgroundColor,
|
color: Theme.of(context).brightness == Brightness.light
|
||||||
|
? type.mobileIconBackgroundColor
|
||||||
|
: type.mobileIconBackgroundColorDark,
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: FlowySvg(
|
child: FlowySvg(
|
||||||
type.svgData,
|
type.svgData,
|
||||||
blendMode: null,
|
|
||||||
size: const Size.square(22),
|
size: const Size.square(22),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/mobile/presentation/base/app_bar.dart';
|
import 'package:appflowy/mobile/presentation/base/app_bar.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
|
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_search_text_field.dart';
|
||||||
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||||
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
@ -23,9 +25,7 @@ class FontPickerScreen extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class LanguagePickerPage extends StatefulWidget {
|
class LanguagePickerPage extends StatefulWidget {
|
||||||
const LanguagePickerPage({
|
const LanguagePickerPage({super.key});
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LanguagePickerPage> createState() => _LanguagePickerPageState();
|
State<LanguagePickerPage> createState() => _LanguagePickerPageState();
|
||||||
@ -52,6 +52,7 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
|||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Scrollbar(
|
child: Scrollbar(
|
||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
|
itemCount: availableFonts.length + 1, // with search bar
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
// search bar
|
// search bar
|
||||||
@ -65,7 +66,8 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
availableFonts = _availableFonts
|
availableFonts = _availableFonts
|
||||||
.where(
|
.where(
|
||||||
(element) => parseFontFamilyName(element)
|
(font) => font
|
||||||
|
.parseFontFamilyName()
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(keyword.toLowerCase()),
|
.contains(keyword.toLowerCase()),
|
||||||
)
|
)
|
||||||
@ -75,8 +77,9 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final fontFamilyName = availableFonts[index - 1];
|
final fontFamilyName = availableFonts[index - 1];
|
||||||
final displayName = parseFontFamilyName(fontFamilyName);
|
final displayName = fontFamilyName.parseFontFamilyName();
|
||||||
return FlowyOptionTile.checkbox(
|
return FlowyOptionTile.checkbox(
|
||||||
text: displayName,
|
text: displayName,
|
||||||
isSelected: selectedFontFamilyName == fontFamilyName,
|
isSelected: selectedFontFamilyName == fontFamilyName,
|
||||||
@ -86,17 +89,9 @@ class _LanguagePickerPageState extends State<LanguagePickerPage> {
|
|||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
itemCount: availableFonts.length + 1, // with search bar
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseFontFamilyName(String fontFamilyName) {
|
|
||||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
|
||||||
return fontFamilyName
|
|
||||||
.replaceAll('_regular', '')
|
|
||||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -11,8 +11,22 @@ extension CalcTypeLabel on CalculationType {
|
|||||||
LocaleKeys.grid_calculationTypeLabel_median.tr(),
|
LocaleKeys.grid_calculationTypeLabel_median.tr(),
|
||||||
CalculationType.Min => LocaleKeys.grid_calculationTypeLabel_min.tr(),
|
CalculationType.Min => LocaleKeys.grid_calculationTypeLabel_min.tr(),
|
||||||
CalculationType.Sum => LocaleKeys.grid_calculationTypeLabel_sum.tr(),
|
CalculationType.Sum => LocaleKeys.grid_calculationTypeLabel_sum.tr(),
|
||||||
|
CalculationType.Count =>
|
||||||
|
LocaleKeys.grid_calculationTypeLabel_count.tr(),
|
||||||
|
CalculationType.CountEmpty =>
|
||||||
|
LocaleKeys.grid_calculationTypeLabel_countEmpty.tr(),
|
||||||
|
CalculationType.CountNonEmpty =>
|
||||||
|
LocaleKeys.grid_calculationTypeLabel_countNonEmpty.tr(),
|
||||||
_ => throw UnimplementedError(
|
_ => throw UnimplementedError(
|
||||||
'Label for $this has not been implemented',
|
'Label for $this has not been implemented',
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
String get shortLabel => switch (this) {
|
||||||
|
CalculationType.CountEmpty =>
|
||||||
|
LocaleKeys.grid_calculationTypeLabel_countEmptyShort.tr(),
|
||||||
|
CalculationType.CountNonEmpty =>
|
||||||
|
LocaleKeys.grid_calculationTypeLabel_countNonEmptyShort.tr(),
|
||||||
|
_ => label,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -128,7 +128,7 @@ extension NumberFormatExtension on NumberFormatPB {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String iconSymbol() {
|
String iconSymbol([bool defaultPrefixInc = true]) {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case NumberFormatPB.ArgentinePeso:
|
case NumberFormatPB.ArgentinePeso:
|
||||||
return "\$";
|
return "\$";
|
||||||
@ -169,7 +169,7 @@ extension NumberFormatExtension on NumberFormatPB {
|
|||||||
case NumberFormatPB.NorwegianKrone:
|
case NumberFormatPB.NorwegianKrone:
|
||||||
return "kr";
|
return "kr";
|
||||||
case NumberFormatPB.Num:
|
case NumberFormatPB.Num:
|
||||||
return "#";
|
return defaultPrefixInc ? "#" : "";
|
||||||
case NumberFormatPB.Percent:
|
case NumberFormatPB.Percent:
|
||||||
return "%";
|
return "%";
|
||||||
case NumberFormatPB.PhilippinePeso:
|
case NumberFormatPB.PhilippinePeso:
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||||
|
|
||||||
|
extension AvailableCalculations on FieldType {
|
||||||
|
List<CalculationType> calculationsForFieldType() {
|
||||||
|
final calculationTypes = [
|
||||||
|
CalculationType.Count,
|
||||||
|
];
|
||||||
|
|
||||||
|
// These FieldTypes cannot be empty, no need to count empty/non-empty
|
||||||
|
if (![FieldType.Checkbox, FieldType.LastEditedTime, FieldType.CreatedTime]
|
||||||
|
.contains(this)) {
|
||||||
|
calculationTypes.addAll([
|
||||||
|
CalculationType.CountEmpty,
|
||||||
|
CalculationType.CountNonEmpty,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (this) {
|
||||||
|
case FieldType.Number:
|
||||||
|
calculationTypes.addAll([
|
||||||
|
CalculationType.Sum,
|
||||||
|
CalculationType.Average,
|
||||||
|
CalculationType.Min,
|
||||||
|
CalculationType.Max,
|
||||||
|
CalculationType.Median,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return calculationTypes;
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@ import 'package:appflowy_backend/log.dart';
|
|||||||
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:easy_localization/easy_localization.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/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
|
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
|
||||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||||
@ -237,7 +236,6 @@ class _GridPageContentState extends State<GridPageContent> {
|
|||||||
viewId: widget.view.id,
|
viewId: widget.view.id,
|
||||||
scrollController: _scrollController,
|
scrollController: _scrollController,
|
||||||
),
|
),
|
||||||
const _GridFooter(),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -431,40 +429,3 @@ class _WrapScrollView extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridFooter extends StatelessWidget {
|
|
||||||
const _GridFooter();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocSelector<GridBloc, GridState, int>(
|
|
||||||
selector: (state) => state.rowCount,
|
|
||||||
builder: (context, rowCount) {
|
|
||||||
return Padding(
|
|
||||||
padding: GridSize.contentInsets,
|
|
||||||
child: RichText(
|
|
||||||
text: TextSpan(
|
|
||||||
text: rowCountString(),
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium!
|
|
||||||
.copyWith(color: Theme.of(context).hintColor),
|
|
||||||
children: [
|
|
||||||
TextSpan(
|
|
||||||
text: ' $rowCount',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
|
||||||
color: AFThemeExtension.of(context).gridRowCountColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String rowCountString() {
|
|
||||||
return '${LocaleKeys.grid_row_count.tr()} :';
|
|
||||||
}
|
|
||||||
|
@ -5,9 +5,11 @@ import 'package:appflowy/plugins/database/application/calculations/calculation_t
|
|||||||
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
import 'package:appflowy/plugins/database/application/field/field_info.dart';
|
||||||
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
|
import 'package:appflowy/plugins/database/application/field/type_option/number_format_bloc.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart';
|
import 'package:appflowy/plugins/database/grid/application/calculations/calculations_bloc.dart';
|
||||||
|
import 'package:appflowy/plugins/database/grid/application/calculations/field_type_calc_ext.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_selector.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/calculation_type_item.dart';
|
||||||
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart';
|
import 'package:appflowy/plugins/database/grid/presentation/widgets/calculations/remove_calculation_button.dart';
|
||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/calculation_entities.pb.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||||
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
import 'package:appflowy_backend/protobuf/flowy-database2/number_entities.pb.dart';
|
||||||
@ -67,31 +69,29 @@ class _CalculateCellState extends State<CalculateCell> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
...CalculationType.values.map(
|
...widget.fieldInfo.fieldType.calculationsForFieldType().map(
|
||||||
(type) => CalculationTypeItem(
|
(type) => CalculationTypeItem(
|
||||||
type: type,
|
type: type,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (type != widget.calculation?.calculationType) {
|
if (type != widget.calculation?.calculationType) {
|
||||||
context.read<CalculationsBloc>().add(
|
context.read<CalculationsBloc>().add(
|
||||||
CalculationsEvent.updateCalculationType(
|
CalculationsEvent.updateCalculationType(
|
||||||
widget.fieldInfo.id,
|
widget.fieldInfo.id,
|
||||||
type,
|
type,
|
||||||
calculationId: widget.calculation?.id,
|
calculationId: widget.calculation?.id,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: widget.fieldInfo.fieldType == FieldType.Number
|
child: widget.calculation != null
|
||||||
? widget.calculation != null
|
? _showCalculateValue(context, prefix)
|
||||||
? _showCalculateValue(context, prefix)
|
: CalculationSelector(isSelected: isSelected),
|
||||||
: CalculationSelector(isSelected: isSelected)
|
|
||||||
: const SizedBox.shrink(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -107,7 +107,7 @@ class _CalculateCellState extends State<CalculateCell> {
|
|||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: FlowyText(
|
child: FlowyText(
|
||||||
widget.calculation!.calculationType.label,
|
widget.calculation!.calculationType.shortLabel,
|
||||||
color: Theme.of(context).hintColor,
|
color: Theme.of(context).hintColor,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@ -133,9 +133,8 @@ class _CalculateCellState extends State<CalculateCell> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String _withoutTrailingZeros(String value) {
|
String _withoutTrailingZeros(String value) {
|
||||||
final regex = RegExp(r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$');
|
if (trailingZerosRegex.hasMatch(value)) {
|
||||||
if (regex.hasMatch(value)) {
|
final match = trailingZerosRegex.firstMatch(value)!;
|
||||||
final match = regex.firstMatch(value)!;
|
|
||||||
return match.group(1)!;
|
return match.group(1)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +145,7 @@ class _CalculateCellState extends State<CalculateCell> {
|
|||||||
FieldType.Number =>
|
FieldType.Number =>
|
||||||
NumberTypeOptionPB.fromBuffer(widget.fieldInfo.field.typeOptionData)
|
NumberTypeOptionPB.fromBuffer(widget.fieldInfo.field.typeOptionData)
|
||||||
.format
|
.format
|
||||||
.iconSymbol(),
|
.iconSymbol(false),
|
||||||
_ => null,
|
_ => null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ const List<FieldType> _supportedFieldTypes = [
|
|||||||
FieldType.URL,
|
FieldType.URL,
|
||||||
FieldType.LastEditedTime,
|
FieldType.LastEditedTime,
|
||||||
FieldType.CreatedTime,
|
FieldType.CreatedTime,
|
||||||
|
FieldType.Relation,
|
||||||
];
|
];
|
||||||
|
|
||||||
class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
|
class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
|
||||||
|
@ -84,12 +84,11 @@ class _MobileSelectOptionEditorState extends State<MobileSelectOptionEditor> {
|
|||||||
const height = 44.0;
|
const height = 44.0;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Align(
|
if (showMoreOptions)
|
||||||
alignment: Alignment.centerLeft,
|
Align(
|
||||||
child: showMoreOptions
|
alignment: Alignment.centerLeft,
|
||||||
? AppBarBackButton(onTap: _popOrBack)
|
child: AppBarBackButton(onTap: _popOrBack),
|
||||||
: AppBarCloseButton(onTap: _popOrBack),
|
),
|
||||||
),
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 44.0,
|
height: 44.0,
|
||||||
child: Align(
|
child: Align(
|
||||||
|
@ -1,10 +1,7 @@
|
|||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/editor_state_paste_node_extension.dart';
|
||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
|
|
||||||
RegExp _hrefRegex = RegExp(
|
|
||||||
r'https?://(?:www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?:/[^\s]*)?',
|
|
||||||
);
|
|
||||||
|
|
||||||
extension PasteFromPlainText on EditorState {
|
extension PasteFromPlainText on EditorState {
|
||||||
Future<void> pastePlainText(String plainText) async {
|
Future<void> pastePlainText(String plainText) async {
|
||||||
if (await pasteHtmlIfAvailable(plainText)) {
|
if (await pasteHtmlIfAvailable(plainText)) {
|
||||||
@ -23,7 +20,7 @@ extension PasteFromPlainText on EditorState {
|
|||||||
.map((e) {
|
.map((e) {
|
||||||
// parse the url content
|
// parse the url content
|
||||||
final Attributes attributes = {};
|
final Attributes attributes = {};
|
||||||
if (_hrefRegex.hasMatch(e)) {
|
if (hrefRegex.hasMatch(e)) {
|
||||||
attributes[AppFlowyRichTextKeys.href] = e;
|
attributes[AppFlowyRichTextKeys.href] = e;
|
||||||
}
|
}
|
||||||
return Delta()..insert(e, attributes: attributes);
|
return Delta()..insert(e, attributes: attributes);
|
||||||
@ -45,7 +42,7 @@ extension PasteFromPlainText on EditorState {
|
|||||||
if (selection == null ||
|
if (selection == null ||
|
||||||
!selection.isSingle ||
|
!selection.isSingle ||
|
||||||
selection.isCollapsed ||
|
selection.isCollapsed ||
|
||||||
!_hrefRegex.hasMatch(plainText)) {
|
!hrefRegex.hasMatch(plainText)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class EmbedImageUrlWidget extends StatefulWidget {
|
class EmbedImageUrlWidget extends StatefulWidget {
|
||||||
const EmbedImageUrlWidget({
|
const EmbedImageUrlWidget({
|
||||||
@ -16,6 +18,7 @@ class EmbedImageUrlWidget extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
||||||
|
bool isUrlValid = true;
|
||||||
String inputText = '';
|
String inputText = '';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -25,8 +28,15 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
|||||||
FlowyTextField(
|
FlowyTextField(
|
||||||
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
|
hintText: LocaleKeys.document_imageBlock_embedLink_placeholder.tr(),
|
||||||
onChanged: (value) => inputText = value,
|
onChanged: (value) => inputText = value,
|
||||||
onEditingComplete: () => widget.onSubmit(inputText),
|
onEditingComplete: submit,
|
||||||
),
|
),
|
||||||
|
if (!isUrlValid) ...[
|
||||||
|
const VSpace(8),
|
||||||
|
FlowyText(
|
||||||
|
LocaleKeys.document_plugins_cover_invalidImageUrl.tr(),
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
],
|
||||||
const VSpace(8),
|
const VSpace(8),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 160,
|
width: 160,
|
||||||
@ -37,10 +47,20 @@ class _EmbedImageUrlWidgetState extends State<EmbedImageUrlWidget> {
|
|||||||
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
LocaleKeys.document_imageBlock_embedLink_label.tr(),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
onTap: () => widget.onSubmit(inputText),
|
onTap: submit,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void submit() {
|
||||||
|
if (checkUrlValidity(inputText)) {
|
||||||
|
return widget.onSubmit(inputText);
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() => isUrlValid = false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool checkUrlValidity(String url) => imgUrlRegex.hasMatch(url);
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart';
|
||||||
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_toolbar_theme.dart';
|
||||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
@ -59,3 +60,15 @@ class TemporaryDirectoryCache implements ICache {
|
|||||||
await tmpDir.delete(recursive: true);
|
await tmpDir.delete(recursive: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FeatureFlagCache implements ICache {
|
||||||
|
@override
|
||||||
|
Future<int> cacheSize() async {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> clearAll() async {
|
||||||
|
await FeatureFlag.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,10 +1,17 @@
|
|||||||
|
import 'dart:collection';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
|
import 'package:appflowy/core/config/kv_keys.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
|
||||||
|
typedef FeatureFlagMap = Map<FeatureFlag, bool>;
|
||||||
|
|
||||||
/// The [FeatureFlag] is used to control the front-end features of the app.
|
/// The [FeatureFlag] is used to control the front-end features of the app.
|
||||||
///
|
///
|
||||||
/// For example, if your feature is still under development,
|
/// For example, if your feature is still under development,
|
||||||
/// you can set the value to `false` to hide the feature.
|
/// you can set the value to `false` to hide the feature.
|
||||||
enum FeatureFlag {
|
enum FeatureFlag {
|
||||||
// Feature flags
|
|
||||||
|
|
||||||
// used to control the visibility of the collaborative workspace feature
|
// used to control the visibility of the collaborative workspace feature
|
||||||
// if it's on, you can see the workspace list and the workspace settings
|
// if it's on, you can see the workspace list and the workspace settings
|
||||||
// in the top-left corner of the app
|
// in the top-left corner of the app
|
||||||
@ -14,7 +21,56 @@ enum FeatureFlag {
|
|||||||
// if it's on, you can see the members settings in the settings page
|
// if it's on, you can see the members settings in the settings page
|
||||||
membersSettings;
|
membersSettings;
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
final values = await getIt<KeyValueStorage>().getWithFormat<FeatureFlagMap>(
|
||||||
|
KVKeys.featureFlag,
|
||||||
|
(value) => Map.from(jsonDecode(value)).map(
|
||||||
|
(key, value) => MapEntry(
|
||||||
|
FeatureFlag.values.firstWhere((e) => e.name == key),
|
||||||
|
value as bool,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) ??
|
||||||
|
{};
|
||||||
|
|
||||||
|
_values = {
|
||||||
|
...{for (final flag in FeatureFlag.values) flag: false},
|
||||||
|
...values,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static UnmodifiableMapView<FeatureFlag, bool> get data =>
|
||||||
|
UnmodifiableMapView(_values);
|
||||||
|
|
||||||
|
Future<void> turnOn() async {
|
||||||
|
await update(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> turnOff() async {
|
||||||
|
await update(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> update(bool value) async {
|
||||||
|
_values[this] = value;
|
||||||
|
|
||||||
|
await getIt<KeyValueStorage>().set(
|
||||||
|
KVKeys.featureFlag,
|
||||||
|
jsonEncode(
|
||||||
|
_values.map((key, value) => MapEntry(key.name, value)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> clear() async {
|
||||||
|
_values = {};
|
||||||
|
await getIt<KeyValueStorage>().remove(KVKeys.featureFlag);
|
||||||
|
}
|
||||||
|
|
||||||
bool get isOn {
|
bool get isOn {
|
||||||
|
if (_values.containsKey(this)) {
|
||||||
|
return _values[this]!;
|
||||||
|
}
|
||||||
|
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case FeatureFlag.collaborativeWorkspace:
|
case FeatureFlag.collaborativeWorkspace:
|
||||||
return false;
|
return false;
|
||||||
@ -22,4 +78,17 @@ enum FeatureFlag {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String get description {
|
||||||
|
switch (this) {
|
||||||
|
case FeatureFlag.collaborativeWorkspace:
|
||||||
|
return 'if it\'s on, you can see the workspace list and the workspace settings in the top-left corner of the app';
|
||||||
|
case FeatureFlag.membersSettings:
|
||||||
|
return 'if it\'s on, you can see the members settings in the settings page';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get key => 'appflowy_feature_flag_${toString()}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FeatureFlagMap _values = {};
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
const _trailingZerosPattern = r'^(\d+(?:\.\d*?[1-9](?=0|\b))?)\.?0*$';
|
||||||
|
final trailingZerosRegex = RegExp(_trailingZerosPattern);
|
||||||
|
|
||||||
|
const _hrefPattern =
|
||||||
|
r'https?://(?:www\.)?[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,}(?:/[^\s]*)?';
|
||||||
|
final hrefRegex = RegExp(_hrefPattern);
|
||||||
|
|
||||||
|
/// This pattern allows for both HTTP and HTTPS Scheme
|
||||||
|
/// It allows for query parameters
|
||||||
|
/// It only allows the following image extensions: .png, .jpg, .gif, .webm
|
||||||
|
///
|
||||||
|
const _imgUrlPattern =
|
||||||
|
r'(https?:\/\/)([^\s(["<,>/]*)(\/)[^\s[",><]*(.png|.jpg|.gif|.webm)(\?[^\s[",><]*)?';
|
||||||
|
final imgUrlRegex = RegExp(_imgUrlPattern);
|
||||||
|
|
||||||
|
const _appflowyCloudUrlPattern = r'^(https:\/\/)(.*)(\.appflowy\.cloud\/)(.*)';
|
||||||
|
final appflowyCloudUrlRegex = RegExp(_appflowyCloudUrlPattern);
|
||||||
|
|
||||||
|
const _camelCasePattern = '(?<=[a-z])[A-Z]';
|
||||||
|
final camelCaseRegex = RegExp(_camelCasePattern);
|
||||||
|
|
||||||
|
const _macOSVolumesPattern = '^/Volumes/[^/]+';
|
||||||
|
final macOSVolumesRegex = RegExp(_macOSVolumesPattern);
|
@ -0,0 +1,19 @@
|
|||||||
|
/// RegExp to match Twelve Hour formats
|
||||||
|
/// Source: https://stackoverflow.com/a/33906224
|
||||||
|
///
|
||||||
|
/// Matches eg: "05:05 PM", "5:50 Pm", "10:59 am", etc.
|
||||||
|
///
|
||||||
|
const _twelveHourTimePattern =
|
||||||
|
r'\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))';
|
||||||
|
final twelveHourTimeRegex = RegExp(_twelveHourTimePattern);
|
||||||
|
bool isTwelveHourTime(String? time) => twelveHourTimeRegex.hasMatch(time ?? '');
|
||||||
|
|
||||||
|
/// RegExp to match Twenty Four Hour formats
|
||||||
|
/// Source: https://stackoverflow.com/a/7536768
|
||||||
|
///
|
||||||
|
/// Matches eg: "0:01", "04:59", "16:30", etc.
|
||||||
|
///
|
||||||
|
const _twentyFourHourtimePattern = r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$';
|
||||||
|
final tewentyFourHourTimeRegex = RegExp(_twentyFourHourtimePattern);
|
||||||
|
bool isTwentyFourHourTime(String? time) =>
|
||||||
|
tewentyFourHourTimeRegex.hasMatch(time ?? '');
|
@ -134,7 +134,8 @@ void _resolveCommonService(
|
|||||||
getIt.registerFactory<FlowyCacheManager>(
|
getIt.registerFactory<FlowyCacheManager>(
|
||||||
() => FlowyCacheManager()
|
() => FlowyCacheManager()
|
||||||
..registerCache(TemporaryDirectoryCache())
|
..registerCache(TemporaryDirectoryCache())
|
||||||
..registerCache(CustomImageCacheManager()),
|
..registerCache(CustomImageCacheManager())
|
||||||
|
..registerCache(FeatureFlagCache()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import 'dart:async';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:appflowy/env/cloud_env.dart';
|
import 'package:appflowy/env/cloud_env.dart';
|
||||||
|
import 'package:appflowy/startup/tasks/feature_flag_task.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -113,6 +114,7 @@ class FlowyRunner {
|
|||||||
// there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored.
|
// there's a flag named _enable in memory_leak_detector.dart. If it's false, the task will be ignored.
|
||||||
MemoryLeakDetectorTask(),
|
MemoryLeakDetectorTask(),
|
||||||
const DebugTask(),
|
const DebugTask(),
|
||||||
|
const FeatureFlagTask(),
|
||||||
|
|
||||||
// localization
|
// localization
|
||||||
const InitLocalizationTask(),
|
const InitLocalizationTask(),
|
||||||
|
@ -0,0 +1,21 @@
|
|||||||
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../startup.dart';
|
||||||
|
|
||||||
|
class FeatureFlagTask extends LaunchTask {
|
||||||
|
const FeatureFlagTask();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> initialize(LaunchContext context) async {
|
||||||
|
// the hotkey manager is not supported on mobile
|
||||||
|
if (!kDebugMode) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FeatureFlag.initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> dispose() async {}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
|
|
||||||
extension GoogleFontsParser on String {
|
extension GoogleFontsParser on String {
|
||||||
String parseFontFamilyName() {
|
String parseFontFamilyName() => replaceAll('_regular', '')
|
||||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
.replaceAllMapped(camelCaseRegex, (m) => ' ${m.group(0)}');
|
||||||
return replaceAll('_regular', '')
|
|
||||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
|
|
||||||
extension StringExtension on String {
|
extension StringExtension on String {
|
||||||
static const _specialCharacters = r'\/:*?"<>| ';
|
static const _specialCharacters = r'\/:*?"<>| ';
|
||||||
|
|
||||||
@ -31,8 +33,6 @@ extension StringExtension on String {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns if the string is a appflowy cloud url.
|
/// Returns true if the string is a appflowy cloud url.
|
||||||
bool get isAppFlowyCloudUrl {
|
bool get isAppFlowyCloudUrl => appflowyCloudUrlRegex.hasMatch(this);
|
||||||
return RegExp(r'^(https:\/\/)(.*)(\.appflowy\.cloud\/)(.*)').hasMatch(this);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'package:appflowy/core/config/kv.dart';
|
import 'package:appflowy/core/config/kv.dart';
|
||||||
import 'package:appflowy/core/config/kv_keys.dart';
|
import 'package:appflowy/core/config/kv_keys.dart';
|
||||||
|
import 'package:appflowy/shared/patterns/common_patterns.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy_backend/log.dart';
|
import 'package:appflowy_backend/log.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
import '../../../startup/tasks/prelude.dart';
|
import '../../../startup/tasks/prelude.dart';
|
||||||
@ -26,7 +28,7 @@ class ApplicationDataStorage {
|
|||||||
|
|
||||||
if (Platform.isMacOS) {
|
if (Platform.isMacOS) {
|
||||||
// remove the prefix `/Volumes/*`
|
// remove the prefix `/Volumes/*`
|
||||||
path = path.replaceFirst(RegExp('^/Volumes/[^/]+'), '');
|
path = path.replaceFirst(macOSVolumesRegex, '');
|
||||||
} else if (Platform.isWindows) {
|
} else if (Platform.isWindows) {
|
||||||
path = path.replaceAll('/', '\\');
|
path = path.replaceAll('/', '\\');
|
||||||
}
|
}
|
||||||
|
@ -1,18 +0,0 @@
|
|||||||
/// RegExp to match Twelve Hour formats
|
|
||||||
/// Source: https://stackoverflow.com/a/33906224
|
|
||||||
///
|
|
||||||
/// Matches eg: "05:05 PM", "5:50 Pm", "10:59 am", etc.
|
|
||||||
///
|
|
||||||
final _twelveHourTimePattern =
|
|
||||||
RegExp(r'\b((1[0-2]|0?[1-9]):([0-5][0-9]) ([AaPp][Mm]))');
|
|
||||||
bool isTwelveHourTime(String? time) =>
|
|
||||||
_twelveHourTimePattern.hasMatch(time ?? '');
|
|
||||||
|
|
||||||
/// RegExp to match Twenty Four Hour formats
|
|
||||||
/// Source: https://stackoverflow.com/a/7536768
|
|
||||||
///
|
|
||||||
/// Matches eg: "0:01", "04:59", "16:30", etc.
|
|
||||||
///
|
|
||||||
final _twentyFourHourtimePattern = RegExp(r'^([0-1]?[0-9]|2[0-3]):[0-5][0-9]$');
|
|
||||||
bool isTwentyFourHourTime(String? time) =>
|
|
||||||
_twentyFourHourtimePattern.hasMatch(time ?? '');
|
|
@ -17,6 +17,7 @@ enum SettingsPage {
|
|||||||
cloud,
|
cloud,
|
||||||
shortcuts,
|
shortcuts,
|
||||||
member,
|
member,
|
||||||
|
featureFlags,
|
||||||
}
|
}
|
||||||
|
|
||||||
class SettingsDialogBloc
|
class SettingsDialogBloc
|
||||||
|
@ -113,8 +113,7 @@ class WorkspaceMenuItem extends StatelessWidget {
|
|||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
WorkspaceMemberBloc(userProfile: userProfile, workspace: workspace)
|
WorkspaceMemberBloc(userProfile: userProfile, workspace: workspace)
|
||||||
..add(const WorkspaceMemberEvent.initial())
|
..add(const WorkspaceMemberEvent.initial()),
|
||||||
..add(const WorkspaceMemberEvent.getWorkspaceMembers()),
|
|
||||||
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final members = state.members;
|
final members = state.members;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||||
import 'package:appflowy/startup/startup.dart';
|
import 'package:appflowy/startup/startup.dart';
|
||||||
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dart';
|
||||||
|
import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/feature_flag_page.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/members/workspace_member_page.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
|
||||||
@ -114,6 +115,8 @@ class SettingsDialog extends StatelessWidget {
|
|||||||
return const SettingsCustomizeShortcutsWrapper();
|
return const SettingsCustomizeShortcutsWrapper();
|
||||||
case SettingsPage.member:
|
case SettingsPage.member:
|
||||||
return WorkspaceMembersPage(userProfile: user);
|
return WorkspaceMembersPage(userProfile: user);
|
||||||
|
case SettingsPage.featureFlags:
|
||||||
|
return const FeatureFlagsPage();
|
||||||
default:
|
default:
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
import 'package:appflowy/shared/feature_flags.dart';
|
||||||
|
import 'package:appflowy/startup/startup.dart';
|
||||||
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class FeatureFlagsPage extends StatelessWidget {
|
||||||
|
const FeatureFlagsPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: SeparatedColumn(
|
||||||
|
children: [
|
||||||
|
...FeatureFlag.data.entries.map(
|
||||||
|
(e) => _FeatureFlagItem(featureFlag: e.key),
|
||||||
|
),
|
||||||
|
FlowyTextButton(
|
||||||
|
'Restart the app to apply changes',
|
||||||
|
fontSize: 16.0,
|
||||||
|
fontColor: Colors.red,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0,
|
||||||
|
vertical: 12.0,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
await runAppFlowy();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeatureFlagItem extends StatefulWidget {
|
||||||
|
const _FeatureFlagItem({
|
||||||
|
required this.featureFlag,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FeatureFlag featureFlag;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<_FeatureFlagItem> createState() => _FeatureFlagItemState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _FeatureFlagItemState extends State<_FeatureFlagItem> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListTile(
|
||||||
|
title: FlowyText(
|
||||||
|
widget.featureFlag.name,
|
||||||
|
fontSize: 16.0,
|
||||||
|
),
|
||||||
|
subtitle: FlowyText.small(
|
||||||
|
widget.featureFlag.description,
|
||||||
|
maxLines: 3,
|
||||||
|
),
|
||||||
|
trailing: Switch(
|
||||||
|
value: widget.featureFlag.isOn,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
widget.featureFlag.update(value);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,8 @@ class WorkspaceMemberBloc
|
|||||||
workspaceId = '';
|
workspaceId = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
add(const WorkspaceMemberEvent.getWorkspaceMembers());
|
||||||
},
|
},
|
||||||
getWorkspaceMembers: () async {
|
getWorkspaceMembers: () async {
|
||||||
final members = await _getWorkspaceMembers();
|
final members = await _getWorkspaceMembers();
|
||||||
|
@ -27,9 +27,7 @@ class WorkspaceMembersPage extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider<WorkspaceMemberBloc>(
|
return BlocProvider<WorkspaceMemberBloc>(
|
||||||
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
create: (context) => WorkspaceMemberBloc(userProfile: userProfile)
|
||||||
..add(
|
..add(const WorkspaceMemberEvent.initial()),
|
||||||
const WorkspaceMemberEvent.getWorkspaceMembers(),
|
|
||||||
),
|
|
||||||
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
child: BlocBuilder<WorkspaceMemberBloc, WorkspaceMemberState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
|
@ -95,7 +95,7 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
|||||||
return FlowySettingValueDropDown(
|
return FlowySettingValueDropDown(
|
||||||
popoverKey: ThemeFontFamilySetting.popoverKey,
|
popoverKey: ThemeFontFamilySetting.popoverKey,
|
||||||
popoverController: widget.popoverController,
|
popoverController: widget.popoverController,
|
||||||
currentValue: parseFontFamilyName(widget.currentFontFamily),
|
currentValue: widget.currentFontFamily.parseFontFamilyName(),
|
||||||
onClose: () {
|
onClose: () {
|
||||||
query.value = '';
|
query.value = '';
|
||||||
widget.onClose?.call();
|
widget.onClose?.call();
|
||||||
@ -162,18 +162,11 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseFontFamilyName(String fontFamilyName) {
|
|
||||||
final camelCase = RegExp('(?<=[a-z])[A-Z]');
|
|
||||||
return fontFamilyName
|
|
||||||
.replaceAll('_regular', '')
|
|
||||||
.replaceAllMapped(camelCase, (m) => ' ${m.group(0)}');
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _fontFamilyItemButton(
|
Widget _fontFamilyItemButton(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
TextStyle style,
|
TextStyle style,
|
||||||
) {
|
) {
|
||||||
final buttonFontFamily = parseFontFamilyName(style.fontFamily!);
|
final buttonFontFamily = style.fontFamily!.parseFontFamilyName();
|
||||||
|
|
||||||
return Tooltip(
|
return Tooltip(
|
||||||
message: buttonFontFamily,
|
message: buttonFontFamily,
|
||||||
@ -184,21 +177,19 @@ class _FontFamilyDropDownState extends State<FontFamilyDropDown> {
|
|||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
onHover: (_) => FocusScope.of(context).unfocus(),
|
onHover: (_) => FocusScope.of(context).unfocus(),
|
||||||
text: FlowyText.medium(
|
text: FlowyText.medium(
|
||||||
parseFontFamilyName(style.fontFamily!),
|
buttonFontFamily,
|
||||||
fontFamily: style.fontFamily!,
|
fontFamily: style.fontFamily!,
|
||||||
),
|
),
|
||||||
rightIcon:
|
rightIcon:
|
||||||
buttonFontFamily == parseFontFamilyName(widget.currentFontFamily)
|
buttonFontFamily == widget.currentFontFamily.parseFontFamilyName()
|
||||||
? const FlowySvg(
|
? const FlowySvg(FlowySvgs.check_s)
|
||||||
FlowySvgs.check_s,
|
|
||||||
)
|
|
||||||
: null,
|
: null,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (widget.onFontFamilyChanged != null) {
|
if (widget.onFontFamilyChanged != null) {
|
||||||
widget.onFontFamilyChanged!(style.fontFamily!);
|
widget.onFontFamilyChanged!(style.fontFamily!);
|
||||||
} else {
|
} else {
|
||||||
final fontFamily = style.fontFamily!.parseFontFamilyName();
|
final fontFamily = style.fontFamily!.parseFontFamilyName();
|
||||||
if (parseFontFamilyName(widget.currentFontFamily) !=
|
if (widget.currentFontFamily.parseFontFamilyName() !=
|
||||||
buttonFontFamily) {
|
buttonFontFamily) {
|
||||||
context
|
context
|
||||||
.read<AppearanceSettingsCubit>()
|
.read<AppearanceSettingsCubit>()
|
||||||
|
@ -4,6 +4,7 @@ import 'package:appflowy/workspace/application/settings/settings_dialog_bloc.dar
|
|||||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SettingsMenu extends StatelessWidget {
|
class SettingsMenu extends StatelessWidget {
|
||||||
@ -79,6 +80,15 @@ class SettingsMenu extends StatelessWidget {
|
|||||||
icon: Icons.people,
|
icon: Icons.people,
|
||||||
changeSelectedPage: changeSelectedPage,
|
changeSelectedPage: changeSelectedPage,
|
||||||
),
|
),
|
||||||
|
if (kDebugMode)
|
||||||
|
SettingsMenuElement(
|
||||||
|
// no need to translate this page
|
||||||
|
page: SettingsPage.featureFlags,
|
||||||
|
selectedPage: currentPage,
|
||||||
|
label: 'Feature Flags',
|
||||||
|
icon: Icons.flag,
|
||||||
|
changeSelectedPage: changeSelectedPage,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -2,6 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
typedef SeparatorBuilder = Widget Function();
|
typedef SeparatorBuilder = Widget Function();
|
||||||
|
|
||||||
|
Widget _defaultColumnSeparatorBuilder() => const Divider();
|
||||||
|
Widget _defaultRowSeparatorBuilder() => const VerticalDivider();
|
||||||
|
|
||||||
class SeparatedColumn extends Column {
|
class SeparatedColumn extends Column {
|
||||||
SeparatedColumn({
|
SeparatedColumn({
|
||||||
super.key,
|
super.key,
|
||||||
@ -11,7 +14,7 @@ class SeparatedColumn extends Column {
|
|||||||
super.textBaseline,
|
super.textBaseline,
|
||||||
super.textDirection,
|
super.textDirection,
|
||||||
super.verticalDirection,
|
super.verticalDirection,
|
||||||
required SeparatorBuilder separatorBuilder,
|
SeparatorBuilder separatorBuilder = _defaultColumnSeparatorBuilder,
|
||||||
required List<Widget> children,
|
required List<Widget> children,
|
||||||
}) : super(children: _insertSeparators(children, separatorBuilder));
|
}) : super(children: _insertSeparators(children, separatorBuilder));
|
||||||
}
|
}
|
||||||
@ -25,7 +28,7 @@ class SeparatedRow extends Row {
|
|||||||
super.textBaseline,
|
super.textBaseline,
|
||||||
super.textDirection,
|
super.textDirection,
|
||||||
super.verticalDirection,
|
super.verticalDirection,
|
||||||
required SeparatorBuilder separatorBuilder,
|
SeparatorBuilder separatorBuilder = _defaultRowSeparatorBuilder,
|
||||||
required List<Widget> children,
|
required List<Widget> children,
|
||||||
}) : super(children: _insertSeparators(children, separatorBuilder));
|
}) : super(children: _insertSeparators(children, separatorBuilder));
|
||||||
}
|
}
|
||||||
|
@ -53,17 +53,17 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: d4d35c0
|
ref: ce391a8
|
||||||
resolved-ref: d4d35c0d103a5d1bddf68181fcfaf9f75b0fccb5
|
resolved-ref: ce391a8c0f492f7b5fdd8f44bbc89fc68882ff23
|
||||||
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.3.2"
|
version: "2.3.3"
|
||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: "0223cca"
|
ref: "8f238f2"
|
||||||
resolved-ref: "0223ccabe74b86092d3f3849b69026c89df3b236"
|
resolved-ref: "8f238f214de72e629fe2d90317518c5a0510cdc5"
|
||||||
url: "https://github.com/LucasXu0/appflowy_editor_plugins"
|
url: "https://github.com/LucasXu0/appflowy_editor_plugins"
|
||||||
source: git
|
source: git
|
||||||
version: "0.0.1"
|
version: "0.0.1"
|
||||||
|
@ -50,7 +50,7 @@ dependencies:
|
|||||||
appflowy_editor_plugins:
|
appflowy_editor_plugins:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/LucasXu0/appflowy_editor_plugins
|
url: https://github.com/LucasXu0/appflowy_editor_plugins
|
||||||
ref: "0223cca"
|
ref: "8f238f2"
|
||||||
|
|
||||||
appflowy_popover:
|
appflowy_popover:
|
||||||
path: packages/appflowy_popover
|
path: packages/appflowy_popover
|
||||||
@ -167,7 +167,7 @@ dependency_overrides:
|
|||||||
appflowy_editor:
|
appflowy_editor:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
url: https://github.com/AppFlowy-IO/appflowy-editor.git
|
||||||
ref: "d4d35c0"
|
ref: "ce391a8"
|
||||||
|
|
||||||
sheet:
|
sheet:
|
||||||
git:
|
git:
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
|
|
||||||
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
import 'package:appflowy/plugins/database/application/database_controller.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:appflowy/plugins/database/grid/application/grid_bloc.dart';
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
import 'util.dart';
|
import 'util.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
@ -17,7 +18,8 @@ void main() {
|
|||||||
context = await gridTest.createTestGrid();
|
context = await gridTest.createTestGrid();
|
||||||
});
|
});
|
||||||
|
|
||||||
// The initial number of rows is 3 for each grid.
|
// The initial number of rows is 3 for each grid
|
||||||
|
// We create one row so we expect 4 rows
|
||||||
blocTest<GridBloc, GridState>(
|
blocTest<GridBloc, GridState>(
|
||||||
"create a row",
|
"create a row",
|
||||||
build: () => GridBloc(
|
build: () => GridBloc(
|
||||||
|
@ -79,8 +79,8 @@
|
|||||||
"yjs": "^13.5.51"
|
"yjs": "^13.5.51"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@svgr/plugin-svgo": "^8.0.1",
|
|
||||||
"@tauri-apps/cli": "^1.5.6",
|
"@tauri-apps/cli": "^1.5.6",
|
||||||
|
"@svgr/plugin-svgo": "^8.0.1",
|
||||||
"@types/google-protobuf": "^3.15.12",
|
"@types/google-protobuf": "^3.15.12",
|
||||||
"@types/is-hotkey": "^0.1.7",
|
"@types/is-hotkey": "^0.1.7",
|
||||||
"@types/jest": "^29.5.3",
|
"@types/jest": "^29.5.3",
|
||||||
|
31
frontend/appflowy_tauri/scripts/update_version.cjs
Normal file
31
frontend/appflowy_tauri/scripts/update_version.cjs
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
if (process.argv.length < 3) {
|
||||||
|
console.error('Usage: node update-tauri-version.js <version>');
|
||||||
|
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.`);
|
||||||
|
});
|
||||||
|
});
|
22
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
22
frontend/appflowy_tauri/src-tauri/Cargo.lock
generated
@ -162,7 +162,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -714,7 +714,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -1313,7 +1313,7 @@ checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2587,7 +2587,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -2604,7 +2604,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -3059,7 +3059,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -4801,7 +4801,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "realtime-entity"
|
name = "realtime-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -4825,7 +4825,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "realtime-protocol"
|
name = "realtime-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -5497,7 +5497,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -6993,7 +6993,7 @@ checksum = "14247bb57be4f377dfb94c72830b8ce8fc6beac03cf4bf7b9732eadd414123fc"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "websocket"
|
name = "websocket"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -7456,7 +7456,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "workspace-template"
|
name = "workspace-template"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
@ -82,7 +82,7 @@ custom-protocol = ["tauri/custom-protocol"]
|
|||||||
# Run the script:
|
# Run the script:
|
||||||
# scripts/tool/update_client_api_rev.sh new_rev_id
|
# scripts/tool/update_client_api_rev.sh new_rev_id
|
||||||
# ⚠️⚠️⚠️️
|
# ⚠️⚠️⚠️️
|
||||||
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "eed20c25197109a3f799a5d18d0e38e9f5529d69" }
|
client-api = { git = "https://github.com/AppFlowy-IO/AppFlowy-Cloud", rev = "eb79b9f5e80846bd06b30b4f9c04039ce1452582" }
|
||||||
# Please use the following script to update collab.
|
# Please use the following script to update collab.
|
||||||
# Working directory: frontend
|
# Working directory: frontend
|
||||||
#
|
#
|
||||||
|
@ -99,4 +99,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -60,6 +60,7 @@ function DeleteConfirmDialog({ open, title, onOk, onCancel, onClose, okText, can
|
|||||||
<Button
|
<Button
|
||||||
className={'w-full'}
|
className={'w-full'}
|
||||||
variant={'outlined'}
|
variant={'outlined'}
|
||||||
|
color={'inherit'}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
onCancel?.();
|
onCancel?.();
|
||||||
onClose();
|
onClose();
|
||||||
|
@ -67,7 +67,7 @@ function RenameDialog({
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
<Divider className={'mb-1'} />
|
<Divider className={'mb-1'} />
|
||||||
<DialogActions className={'mb-1 px-4'}>
|
<DialogActions className={'mb-1 px-4'}>
|
||||||
<Button variant={'outlined'} onClick={onClose}>
|
<Button color={'inherit'} variant={'outlined'} onClick={onClose}>
|
||||||
{t('button.cancel')}
|
{t('button.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant={'contained'} onClick={onDone}>
|
<Button variant={'contained'} onClick={onDone}>
|
||||||
|
@ -49,6 +49,7 @@ export interface KeyboardNavigationProps<T> {
|
|||||||
onFocus?: () => void;
|
onFocus?: () => void;
|
||||||
onBlur?: () => void;
|
onBlur?: () => void;
|
||||||
itemClassName?: string;
|
itemClassName?: string;
|
||||||
|
itemStyle?: React.CSSProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
function KeyboardNavigation<T>({
|
function KeyboardNavigation<T>({
|
||||||
@ -67,6 +68,7 @@ function KeyboardNavigation<T>({
|
|||||||
onBlur,
|
onBlur,
|
||||||
onFocus,
|
onFocus,
|
||||||
itemClassName,
|
itemClassName,
|
||||||
|
itemStyle,
|
||||||
}: KeyboardNavigationProps<T>) {
|
}: KeyboardNavigationProps<T>) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -232,6 +234,7 @@ function KeyboardNavigation<T>({
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
selected={isFocused}
|
selected={isFocused}
|
||||||
|
style={itemStyle}
|
||||||
className={`ml-0 flex w-full items-center justify-start rounded-none px-2 py-1 text-xs ${
|
className={`ml-0 flex w-full items-center justify-start rounded-none px-2 py-1 text-xs ${
|
||||||
!isFocused ? 'hover:bg-transparent' : ''
|
!isFocused ? 'hover:bg-transparent' : ''
|
||||||
} ${itemClassName ?? ''}`}
|
} ${itemClassName ?? ''}`}
|
||||||
@ -246,7 +249,7 @@ function KeyboardNavigation<T>({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
[itemClassName, focusedKey, onConfirm, onFocus]
|
[itemClassName, focusedKey, onConfirm, onFocus, itemStyle]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -284,10 +287,16 @@ function KeyboardNavigation<T>({
|
|||||||
onBlur={(e) => {
|
onBlur={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
|
const target = e.relatedTarget as HTMLElement;
|
||||||
|
|
||||||
|
if (target?.closest('.keyboard-navigation')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
onBlur?.();
|
onBlur?.();
|
||||||
}}
|
}}
|
||||||
autoFocus={!disableFocus}
|
autoFocus={!disableFocus}
|
||||||
className={'flex w-full flex-col gap-1 outline-none'}
|
className={'keyboard-navigation flex w-full flex-col gap-1 outline-none'}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
>
|
>
|
||||||
{options.length > 0 ? (
|
{options.length > 0 ? (
|
||||||
|
@ -22,7 +22,7 @@ function ViewBanner({
|
|||||||
<div className={'view-banner flex w-full flex-col'}>
|
<div className={'view-banner flex w-full flex-col'}>
|
||||||
{showCover && cover && <ViewCover cover={cover} onUpdateCover={onUpdateCover} />}
|
{showCover && cover && <ViewCover cover={cover} onUpdateCover={onUpdateCover} />}
|
||||||
|
|
||||||
<div className={'relative min-h-[65px] px-16 pt-4'}>
|
<div className={`relative min-h-[65px] ${showCover ? 'px-16' : ''} pt-4`}>
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: icon ? 'flex' : 'none',
|
display: icon ? 'flex' : 'none',
|
||||||
|
@ -22,6 +22,9 @@ function ViewIcon({ icon, onUpdateIcon }: { icon?: PageIcon; onUpdateIcon: (icon
|
|||||||
const onEmojiSelect = useCallback(
|
const onEmojiSelect = useCallback(
|
||||||
(emoji: string) => {
|
(emoji: string) => {
|
||||||
onUpdateIcon(emoji);
|
onUpdateIcon(emoji);
|
||||||
|
if (!emoji) {
|
||||||
|
setAnchorPosition(undefined);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[onUpdateIcon]
|
[onUpdateIcon]
|
||||||
);
|
);
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { t } from 'i18next';
|
|
||||||
import { AppflowyLogo } from '../../_shared/svg/AppflowyLogo';
|
import { AppflowyLogo } from '../../_shared/svg/AppflowyLogo';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { useLogin } from '$app/components/auth/get_started/useLogin';
|
import { useLogin } from '$app/components/auth/get_started/useLogin';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const GetStarted = () => {
|
export const GetStarted = () => {
|
||||||
const { onAutoSignInClick } = useLogin();
|
const { onAutoSignInClick } = useLogin();
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -195,8 +195,8 @@ export const useConnectDatabase = (viewId: string) => {
|
|||||||
return database;
|
return database;
|
||||||
};
|
};
|
||||||
|
|
||||||
const DatabaseRenderedContext = createContext<() => void>(() => {
|
const DatabaseRenderedContext = createContext<(viewId: string) => void>(() => {
|
||||||
// do nothing
|
return;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const DatabaseRenderedProvider = DatabaseRenderedContext.Provider;
|
export const DatabaseRenderedProvider = DatabaseRenderedContext.Provider;
|
||||||
|
@ -58,65 +58,43 @@ export const Database = forwardRef<HTMLDivElement, Props>(({ selectedViewId, set
|
|||||||
}
|
}
|
||||||
}, [viewId]);
|
}, [viewId]);
|
||||||
|
|
||||||
|
const parentId = page?.parentId;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void handleGetPage();
|
void handleGetPage();
|
||||||
void handleResetDatabaseViews(viewId);
|
void handleResetDatabaseViews(viewId);
|
||||||
const unsubscribePromise = subscribeNotifications(
|
const unsubscribePromise = subscribeNotifications({
|
||||||
{
|
[FolderNotification.DidUpdateView]: (changeset) => {
|
||||||
[FolderNotification.DidUpdateView]: (changeset) => {
|
if (changeset.parent_view_id !== viewId && changeset.id !== viewId) return;
|
||||||
setChildViews((prev) => {
|
setChildViews((prev) => {
|
||||||
const index = prev.findIndex((view) => view.id === changeset.id);
|
const index = prev.findIndex((view) => view.id === changeset.id);
|
||||||
|
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
return prev;
|
return prev;
|
||||||
}
|
|
||||||
|
|
||||||
const newViews = [...prev];
|
|
||||||
|
|
||||||
newViews[index] = {
|
|
||||||
...newViews[index],
|
|
||||||
name: changeset.name,
|
|
||||||
};
|
|
||||||
|
|
||||||
return newViews;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
[FolderNotification.DidUpdateChildViews]: (changeset) => {
|
|
||||||
if (changeset.create_child_views.length === 0 && changeset.delete_child_views.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleResetDatabaseViews(viewId);
|
const newViews = [...prev];
|
||||||
},
|
|
||||||
|
newViews[index] = {
|
||||||
|
...newViews[index],
|
||||||
|
name: changeset.name,
|
||||||
|
};
|
||||||
|
|
||||||
|
return newViews;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
{
|
[FolderNotification.DidUpdateChildViews]: (changeset) => {
|
||||||
id: viewId,
|
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());
|
return () => void unsubscribePromise.then((unsubscribe) => unsubscribe());
|
||||||
}, [handleGetPage, handleResetDatabaseViews, viewId]);
|
}, [handleGetPage, handleResetDatabaseViews, viewId, parentId]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const parentId = page?.parentId;
|
|
||||||
|
|
||||||
if (!parentId) return;
|
|
||||||
|
|
||||||
const unsubscribePromise = subscribeNotifications(
|
|
||||||
{
|
|
||||||
[FolderNotification.DidUpdateChildViews]: (changeset) => {
|
|
||||||
if (changeset.delete_child_views.includes(viewId)) {
|
|
||||||
setNotFound(true);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: parentId,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return () => void unsubscribePromise.then((unsubscribe) => unsubscribe());
|
|
||||||
}, [page, viewId]);
|
|
||||||
|
|
||||||
const value = useMemo(() => {
|
const value = useMemo(() => {
|
||||||
return Math.max(
|
return Math.max(
|
||||||
@ -183,7 +161,13 @@ export const Database = forwardRef<HTMLDivElement, Props>(({ selectedViewId, set
|
|||||||
index={value}
|
index={value}
|
||||||
>
|
>
|
||||||
{childViews.map((view, index) => (
|
{childViews.map((view, index) => (
|
||||||
<TabPanel className={'flex h-full w-full flex-col'} key={view.id} index={index} value={value}>
|
<TabPanel
|
||||||
|
data-view-id={view.id}
|
||||||
|
className={'flex h-full w-full flex-col'}
|
||||||
|
key={view.id}
|
||||||
|
index={index}
|
||||||
|
value={value}
|
||||||
|
>
|
||||||
<DatabaseLoader viewId={view.id}>
|
<DatabaseLoader viewId={view.id}>
|
||||||
{selectedViewId === view.id && (
|
{selectedViewId === view.id && (
|
||||||
<>
|
<>
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import { FormEventHandler, useCallback } from 'react';
|
import { FormEventHandler, useCallback } from 'react';
|
||||||
import { t } from 'i18next';
|
|
||||||
import { useViewId } from '$app/hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
import { useAppDispatch, useAppSelector } from '$app/stores/store';
|
import { useAppDispatch, useAppSelector } from '$app/stores/store';
|
||||||
import { updatePageName } from '$app_reducers/pages/async_actions';
|
import { updatePageName } from '$app_reducers/pages/async_actions';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const DatabaseTitle = () => {
|
export const DatabaseTitle = () => {
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
|
const { t } = useTranslation();
|
||||||
const pageName = useAppSelector((state) => state.pages.pageMap[viewId]?.name || '');
|
const pageName = useAppSelector((state) => state.pages.pageMap[viewId]?.name || '');
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
@ -9,12 +9,12 @@ import Popover from '@mui/material/Popover';
|
|||||||
|
|
||||||
const initialAnchorOrigin: PopoverOrigin = {
|
const initialAnchorOrigin: PopoverOrigin = {
|
||||||
vertical: 'bottom',
|
vertical: 'bottom',
|
||||||
horizontal: 'left',
|
horizontal: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialTransformOrigin: PopoverOrigin = {
|
const initialTransformOrigin: PopoverOrigin = {
|
||||||
vertical: 'top',
|
vertical: 'top',
|
||||||
horizontal: 'left',
|
horizontal: 'center',
|
||||||
};
|
};
|
||||||
const SelectCellActions = lazy(
|
const SelectCellActions = lazy(
|
||||||
() => import('$app/components/database/components/field_types/select/select_cell_actions/SelectCellActions')
|
() => import('$app/components/database/components/field_types/select/select_cell_actions/SelectCellActions')
|
||||||
|
@ -18,7 +18,7 @@ function DatabaseSettings(props: Props) {
|
|||||||
<div className='flex h-[39px] items-center gap-2 border-b border-line-divider'>
|
<div className='flex h-[39px] items-center gap-2 border-b border-line-divider'>
|
||||||
<FilterSettings {...props} />
|
<FilterSettings {...props} />
|
||||||
<SortSettings {...props} />
|
<SortSettings {...props} />
|
||||||
<TextButton color='inherit' onClick={(e) => setSettingAnchorEl(e.currentTarget)}>
|
<TextButton className={'min-w-fit'} color='inherit' onClick={(e) => setSettingAnchorEl(e.currentTarget)}>
|
||||||
{t('settings.title')}
|
{t('settings.title')}
|
||||||
</TextButton>
|
</TextButton>
|
||||||
<SettingsMenu
|
<SettingsMenu
|
||||||
|
@ -23,7 +23,7 @@ function FilterSettings({ onToggleCollection }: { onToggleCollection: (forceOpen
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextButton onClick={handleClick} color={highlight ? 'primary' : 'inherit'}>
|
<TextButton className={'min-w-fit'} onClick={handleClick} color={highlight ? 'primary' : 'inherit'}>
|
||||||
{t('grid.settings.filter')}
|
{t('grid.settings.filter')}
|
||||||
</TextButton>
|
</TextButton>
|
||||||
<FilterFieldsMenu
|
<FilterFieldsMenu
|
||||||
|
@ -31,7 +31,7 @@ function SortSettings({ onToggleCollection }: Props) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TextButton className={'p-1'} color={highlight ? 'primary' : 'inherit'} onClick={handleClick}>
|
<TextButton className={'min-w-fit p-1'} color={highlight ? 'primary' : 'inherit'} onClick={handleClick}>
|
||||||
{t('grid.settings.sort')}
|
{t('grid.settings.sort')}
|
||||||
</TextButton>
|
</TextButton>
|
||||||
<SortFieldsMenu
|
<SortFieldsMenu
|
||||||
|
@ -5,6 +5,7 @@ import dayjs from 'dayjs';
|
|||||||
import { ReactComponent as LeftSvg } from '$app/assets/arrow-left.svg';
|
import { ReactComponent as LeftSvg } from '$app/assets/arrow-left.svg';
|
||||||
import { ReactComponent as RightSvg } from '$app/assets/arrow-right.svg';
|
import { ReactComponent as RightSvg } from '$app/assets/arrow-right.svg';
|
||||||
import { IconButton } from '@mui/material';
|
import { IconButton } from '@mui/material';
|
||||||
|
import './calendar.scss';
|
||||||
|
|
||||||
function CustomCalendar({
|
function CustomCalendar({
|
||||||
handleChange,
|
handleChange,
|
||||||
|
@ -0,0 +1,82 @@
|
|||||||
|
|
||||||
|
.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%;
|
||||||
|
}
|
@ -1,5 +1,4 @@
|
|||||||
import { FC, useMemo, useRef, useState } from 'react';
|
import { FC, useMemo, useRef, useState } from 'react';
|
||||||
import { t } from 'i18next';
|
|
||||||
import { Divider, ListSubheader, MenuItem, MenuList, MenuProps, OutlinedInput } from '@mui/material';
|
import { Divider, ListSubheader, MenuItem, MenuList, MenuProps, OutlinedInput } from '@mui/material';
|
||||||
import { SelectOptionColorPB } from '@/services/backend';
|
import { SelectOptionColorPB } from '@/services/backend';
|
||||||
import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg';
|
import { ReactComponent as DeleteSvg } from '$app/assets/delete.svg';
|
||||||
@ -14,6 +13,7 @@ import {
|
|||||||
import { useViewId } from '$app/hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
import Popover from '@mui/material/Popover';
|
import Popover from '@mui/material/Popover';
|
||||||
import debounce from 'lodash-es/debounce';
|
import debounce from 'lodash-es/debounce';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface SelectOptionMenuProps {
|
interface SelectOptionMenuProps {
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
@ -34,6 +34,7 @@ const Colors = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, option, MenuProps: menuProps }) => {
|
export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, option, MenuProps: menuProps }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const [tagName, setTagName] = useState(option.name);
|
const [tagName, setTagName] = useState(option.name);
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
@ -83,6 +84,9 @@ export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, opt
|
|||||||
horizontal: -32,
|
horizontal: -32,
|
||||||
}}
|
}}
|
||||||
{...menuProps}
|
{...menuProps}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onMouseDown={(e) => {
|
onMouseDown={(e) => {
|
||||||
const isInput = inputRef.current?.contains(e.target as Node);
|
const isInput = inputRef.current?.contains(e.target as Node);
|
||||||
@ -95,6 +99,9 @@ export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, opt
|
|||||||
<ListSubheader className='my-2 leading-tight'>
|
<ListSubheader className='my-2 leading-tight'>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
|
spellCheck={false}
|
||||||
|
autoCorrect={'off'}
|
||||||
|
autoCapitalize={'off'}
|
||||||
value={tagName}
|
value={tagName}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTagName(e.target.value);
|
setTagName(e.target.value);
|
||||||
@ -108,6 +115,9 @@ export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, opt
|
|||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
}}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
placeholder={t('grid.selectOption.tagName')}
|
placeholder={t('grid.selectOption.tagName')}
|
||||||
size='small'
|
size='small'
|
||||||
@ -139,6 +149,7 @@ export const SelectOptionModifyMenu: FC<SelectOptionMenuProps> = ({ fieldId, opt
|
|||||||
}}
|
}}
|
||||||
key={color}
|
key={color}
|
||||||
value={color}
|
value={color}
|
||||||
|
className={'px-1.5'}
|
||||||
>
|
>
|
||||||
<span className={`mr-2 inline-flex h-4 w-4 rounded-full ${SelectOptionColorMap[color]}`} />
|
<span className={`mr-2 inline-flex h-4 w-4 rounded-full ${SelectOptionColorMap[color]}`} />
|
||||||
<span className='flex-1'>{t(`grid.selectOption.${SelectOptionColorTextMap[color]}`)}</span>
|
<span className='flex-1'>{t(`grid.selectOption.${SelectOptionColorTextMap[color]}`)}</span>
|
||||||
|
@ -1,16 +0,0 @@
|
|||||||
import { MenuItem, MenuItemProps } from '@mui/material';
|
|
||||||
import { FC } from 'react';
|
|
||||||
import { Tag } from '../Tag';
|
|
||||||
|
|
||||||
export interface CreateOptionProps {
|
|
||||||
label: React.ReactNode;
|
|
||||||
onClick?: MenuItemProps['onClick'];
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CreateOption: FC<CreateOptionProps> = ({ label, onClick }) => {
|
|
||||||
return (
|
|
||||||
<MenuItem className='px-2' onClick={onClick}>
|
|
||||||
<Tag className='ml-2' size='small' label={label} />
|
|
||||||
</MenuItem>
|
|
||||||
);
|
|
||||||
};
|
|
@ -1,18 +1,17 @@
|
|||||||
import React, { FormEvent, useCallback } from 'react';
|
import React, { FormEvent, useCallback } from 'react';
|
||||||
import { OutlinedInput } from '@mui/material';
|
import { OutlinedInput } from '@mui/material';
|
||||||
import { t } from 'i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
function SearchInput({
|
function SearchInput({
|
||||||
setNewOptionName,
|
setNewOptionName,
|
||||||
newOptionName,
|
newOptionName,
|
||||||
onEnter,
|
inputRef,
|
||||||
onEscape,
|
|
||||||
}: {
|
}: {
|
||||||
newOptionName: string;
|
newOptionName: string;
|
||||||
setNewOptionName: (value: string) => void;
|
setNewOptionName: (value: string) => void;
|
||||||
onEnter: () => void;
|
inputRef?: React.RefObject<HTMLInputElement>;
|
||||||
onEscape?: () => void;
|
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleInput = useCallback(
|
const handleInput = useCallback(
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
const value = (event.target as HTMLInputElement).value;
|
const value = (event.target as HTMLInputElement).value;
|
||||||
@ -27,20 +26,10 @@ function SearchInput({
|
|||||||
size='small'
|
size='small'
|
||||||
className={'mx-4'}
|
className={'mx-4'}
|
||||||
autoFocus={true}
|
autoFocus={true}
|
||||||
|
inputRef={inputRef}
|
||||||
value={newOptionName}
|
value={newOptionName}
|
||||||
onInput={handleInput}
|
onInput={handleInput}
|
||||||
spellCheck={false}
|
spellCheck={false}
|
||||||
onKeyDown={(e) => {
|
|
||||||
if (e.key === 'Enter') {
|
|
||||||
onEnter();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
e.stopPropagation();
|
|
||||||
e.preventDefault();
|
|
||||||
onEscape?.();
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
placeholder={t('grid.selectOption.searchOrCreateOption')}
|
placeholder={t('grid.selectOption.searchOrCreateOption')}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
import React, { useCallback, useMemo, useState } from 'react';
|
import React, { useCallback, useMemo, useRef, useState } from 'react';
|
||||||
import { MenuItem } from '@mui/material';
|
|
||||||
import { t } from 'i18next';
|
|
||||||
import { CreateOption } from '$app/components/database/components/field_types/select/select_cell_actions/CreateOption';
|
|
||||||
import { SelectOptionItem } from '$app/components/database/components/field_types/select/select_cell_actions/SelectOptionItem';
|
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 { cellService, SelectCell as SelectCellType, SelectField, SelectTypeOption } from '$app/application/database';
|
||||||
import { useViewId } from '$app/hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
@ -12,6 +9,13 @@ import {
|
|||||||
import { FieldType } from '@/services/backend';
|
import { FieldType } from '@/services/backend';
|
||||||
import { useTypeOption } from '$app/components/database';
|
import { useTypeOption } from '$app/components/database';
|
||||||
import SearchInput from './SearchInput';
|
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({
|
function SelectCellActions({
|
||||||
field,
|
field,
|
||||||
@ -24,22 +28,43 @@ function SelectCellActions({
|
|||||||
onUpdated?: () => void;
|
onUpdated?: () => void;
|
||||||
onClose?: () => void;
|
onClose?: () => void;
|
||||||
}) {
|
}) {
|
||||||
|
const { t } = useTranslation();
|
||||||
const rowId = cell?.rowId;
|
const rowId = cell?.rowId;
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
const typeOption = useTypeOption<SelectTypeOption>(field.id);
|
const typeOption = useTypeOption<SelectTypeOption>(field.id);
|
||||||
const options = useMemo(() => typeOption.options ?? [], [typeOption.options]);
|
const options = useMemo(() => typeOption.options ?? [], [typeOption.options]);
|
||||||
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const selectedOptionIds = useMemo(() => cell?.data?.selectedOptionIds ?? [], [cell]);
|
const selectedOptionIds = useMemo(() => cell?.data?.selectedOptionIds ?? [], [cell]);
|
||||||
const [newOptionName, setNewOptionName] = useState('');
|
const [newOptionName, setNewOptionName] = useState('');
|
||||||
const filteredOptions = useMemo(
|
|
||||||
() =>
|
|
||||||
options.filter((option) => {
|
|
||||||
return option.name.toLowerCase().includes(newOptionName.toLowerCase());
|
|
||||||
}),
|
|
||||||
[options, newOptionName]
|
|
||||||
);
|
|
||||||
|
|
||||||
const shouldCreateOption = !!newOptionName && filteredOptions.length === 0;
|
const filteredOptions: KeyboardNavigationOption[] = useMemo(() => {
|
||||||
|
const result = options
|
||||||
|
.filter((option) => {
|
||||||
|
return option.name.toLowerCase().includes(newOptionName.toLowerCase());
|
||||||
|
})
|
||||||
|
.map((option) => ({
|
||||||
|
key: option.id,
|
||||||
|
content: (
|
||||||
|
<SelectOptionItem
|
||||||
|
isSelected={selectedOptionIds?.includes(option.id)}
|
||||||
|
fieldId={cell?.fieldId || ''}
|
||||||
|
option={option}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (result.length === 0) {
|
||||||
|
result.push({
|
||||||
|
key: CREATE_OPTION_KEY,
|
||||||
|
content: <Tag size='small' label={newOptionName} />,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}, [newOptionName, options, selectedOptionIds, cell?.fieldId]);
|
||||||
|
|
||||||
|
const shouldCreateOption = filteredOptions.length === 1 && filteredOptions[0].key === 'createOption';
|
||||||
|
|
||||||
const updateCell = useCallback(
|
const updateCell = useCallback(
|
||||||
async (optionIds: string[]) => {
|
async (optionIds: string[]) => {
|
||||||
@ -65,90 +90,67 @@ function SelectCellActions({
|
|||||||
return option;
|
return option;
|
||||||
}, [viewId, field.id, newOptionName]);
|
}, [viewId, field.id, newOptionName]);
|
||||||
|
|
||||||
const handleClickOption = useCallback(
|
const onConfirm = useCallback(
|
||||||
(optionId: string) => {
|
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) {
|
if (field.type === FieldType.SingleSelect) {
|
||||||
void updateCell([optionId]);
|
const newOptionIds = [optionId];
|
||||||
|
|
||||||
|
if (selectedOptionIds?.includes(optionId)) {
|
||||||
|
newOptionIds.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateCell(newOptionIds);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const prev = selectedOptionIds;
|
|
||||||
let newOptionIds = [];
|
let newOptionIds = [];
|
||||||
|
|
||||||
if (!prev) {
|
if (!selectedOptionIds) {
|
||||||
newOptionIds.push(optionId);
|
newOptionIds.push(optionId);
|
||||||
} else {
|
} else {
|
||||||
const isSelected = prev.includes(optionId);
|
const isSelected = selectedOptionIds.includes(optionId);
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
newOptionIds = prev.filter((id) => id !== optionId);
|
newOptionIds = selectedOptionIds.filter((id) => id !== optionId);
|
||||||
} else {
|
} else {
|
||||||
newOptionIds = [...prev, optionId];
|
newOptionIds = [...selectedOptionIds, optionId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCell(newOptionIds);
|
void updateCell(newOptionIds);
|
||||||
},
|
},
|
||||||
[field.type, selectedOptionIds, updateCell]
|
[createOption, field.type, selectedOptionIds, updateCell]
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNewTagClick = useCallback(async () => {
|
|
||||||
if (!cell || !rowId) return;
|
|
||||||
const option = await createOption();
|
|
||||||
|
|
||||||
if (!option) return;
|
|
||||||
handleClickOption(option.id);
|
|
||||||
}, [cell, createOption, handleClickOption, rowId]);
|
|
||||||
|
|
||||||
const handleEnter = useCallback(() => {
|
|
||||||
if (shouldCreateOption) {
|
|
||||||
void handleNewTagClick();
|
|
||||||
} else {
|
|
||||||
if (field.type === FieldType.SingleSelect) {
|
|
||||||
const firstOption = filteredOptions[0];
|
|
||||||
|
|
||||||
if (!firstOption) return;
|
|
||||||
|
|
||||||
void updateCell([firstOption.id]);
|
|
||||||
} else {
|
|
||||||
void updateCell(filteredOptions.map((option) => option.id));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setNewOptionName('');
|
|
||||||
}, [field.type, filteredOptions, handleNewTagClick, shouldCreateOption, updateCell]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'flex h-full flex-col overflow-hidden'}>
|
<div className={'flex h-full flex-col overflow-hidden'}>
|
||||||
<SearchInput
|
<SearchInput inputRef={inputRef} setNewOptionName={setNewOptionName} newOptionName={newOptionName} />
|
||||||
onEscape={onClose}
|
|
||||||
setNewOptionName={setNewOptionName}
|
|
||||||
newOptionName={newOptionName}
|
|
||||||
onEnter={handleEnter}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className='mx-4 mb-2 mt-4 text-xs'>
|
<div className='mx-4 mb-2 mt-4 text-xs'>
|
||||||
{shouldCreateOption ? t('grid.selectOption.createNew') : t('grid.selectOption.orSelectOne')}
|
{shouldCreateOption ? t('grid.selectOption.createNew') : t('grid.selectOption.orSelectOne')}
|
||||||
</div>
|
</div>
|
||||||
<div className={'mx-1 flex-1 overflow-y-auto overflow-x-hidden'}>
|
<div ref={scrollRef} className={'mx-1 flex-1 overflow-y-auto overflow-x-hidden px-1'}>
|
||||||
{shouldCreateOption ? (
|
<KeyboardNavigation
|
||||||
<CreateOption label={newOptionName} onClick={handleNewTagClick} />
|
scrollRef={scrollRef}
|
||||||
) : (
|
focusRef={inputRef}
|
||||||
<div className={' px-2'}>
|
options={filteredOptions}
|
||||||
{filteredOptions.map((option) => (
|
disableFocus={true}
|
||||||
<MenuItem className={'px-2'} key={option.id} value={option.id}>
|
onConfirm={onConfirm}
|
||||||
<SelectOptionItem
|
onEscape={onClose}
|
||||||
onClick={() => {
|
itemStyle={{
|
||||||
handleClickOption(option.id);
|
borderRadius: '4px',
|
||||||
}}
|
}}
|
||||||
isSelected={selectedOptionIds?.includes(option.id)}
|
/>
|
||||||
fieldId={cell?.fieldId || ''}
|
|
||||||
option={option}
|
|
||||||
/>
|
|
||||||
</MenuItem>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -10,10 +10,9 @@ export interface SelectOptionItemProps {
|
|||||||
option: SelectOption;
|
option: SelectOption;
|
||||||
fieldId: string;
|
fieldId: string;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
onClick?: () => void;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SelectOptionItem: FC<SelectOptionItemProps> = ({ onClick, isSelected, fieldId, option }) => {
|
export const SelectOptionItem: FC<SelectOptionItemProps> = ({ isSelected, fieldId, option }) => {
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const anchorEl = useRef<HTMLDivElement | null>(null);
|
const anchorEl = useRef<HTMLDivElement | null>(null);
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
@ -25,7 +24,6 @@ export const SelectOptionItem: FC<SelectOptionItemProps> = ({ onClick, isSelecte
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
onClick={onClick}
|
|
||||||
ref={anchorEl}
|
ref={anchorEl}
|
||||||
className={'flex w-full items-center justify-between'}
|
className={'flex w-full items-center justify-between'}
|
||||||
onMouseEnter={() => setHovered(true)}
|
onMouseEnter={() => setHovered(true)}
|
||||||
|
@ -15,12 +15,12 @@ export interface FieldProps {
|
|||||||
|
|
||||||
const initialAnchorOrigin: PopoverOrigin = {
|
const initialAnchorOrigin: PopoverOrigin = {
|
||||||
vertical: 'bottom',
|
vertical: 'bottom',
|
||||||
horizontal: 'left',
|
horizontal: 'right',
|
||||||
};
|
};
|
||||||
|
|
||||||
const initialTransformOrigin: PopoverOrigin = {
|
const initialTransformOrigin: PopoverOrigin = {
|
||||||
vertical: 'top',
|
vertical: 'top',
|
||||||
horizontal: 'left',
|
horizontal: 'center',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Property: FC<FieldProps> = ({ field, onCloseMenu, className, menuOpened }) => {
|
export const Property: FC<FieldProps> = ({ field, onCloseMenu, className, menuOpened }) => {
|
||||||
@ -54,7 +54,7 @@ export const Property: FC<FieldProps> = ({ field, onCloseMenu, className, menuOp
|
|||||||
}, [menuOpened]);
|
}, [menuOpened]);
|
||||||
|
|
||||||
const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({
|
const { paperHeight, paperWidth, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({
|
||||||
initialPaperWidth: 369,
|
initialPaperWidth: 300,
|
||||||
initialPaperHeight: 400,
|
initialPaperHeight: 400,
|
||||||
anchorPosition,
|
anchorPosition,
|
||||||
initialAnchorOrigin,
|
initialAnchorOrigin,
|
||||||
@ -81,7 +81,7 @@ export const Property: FC<FieldProps> = ({ field, onCloseMenu, className, menuOp
|
|||||||
PaperProps={{
|
PaperProps={{
|
||||||
style: {
|
style: {
|
||||||
maxHeight: paperHeight,
|
maxHeight: paperHeight,
|
||||||
maxWidth: paperWidth,
|
width: paperWidth,
|
||||||
height: 'auto',
|
height: 'auto',
|
||||||
},
|
},
|
||||||
className: 'flex h-full flex-col overflow-hidden',
|
className: 'flex h-full flex-col overflow-hidden',
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import { t } from 'i18next';
|
|
||||||
import { FC, useMemo, useRef, useState } from 'react';
|
import { FC, useMemo, useRef, useState } from 'react';
|
||||||
import { SortConditionPB } from '@/services/backend';
|
import { SortConditionPB } from '@/services/backend';
|
||||||
import KeyboardNavigation, {
|
import KeyboardNavigation, {
|
||||||
@ -6,11 +5,13 @@ import KeyboardNavigation, {
|
|||||||
} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation';
|
} from '$app/components/_shared/keyboard_navigation/KeyboardNavigation';
|
||||||
import { Popover } from '@mui/material';
|
import { Popover } from '@mui/material';
|
||||||
import { ReactComponent as DropDownSvg } from '$app/assets/more.svg';
|
import { ReactComponent as DropDownSvg } from '$app/assets/more.svg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
export const SortConditionSelect: FC<{
|
export const SortConditionSelect: FC<{
|
||||||
onChange?: (value: SortConditionPB) => void;
|
onChange?: (value: SortConditionPB) => void;
|
||||||
value?: SortConditionPB;
|
value?: SortConditionPB;
|
||||||
}> = ({ onChange, value }) => {
|
}> = ({ onChange, value }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
@ -28,7 +29,7 @@ export const SortConditionSelect: FC<{
|
|||||||
content: t('grid.sort.descending'),
|
content: t('grid.sort.descending'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}, []);
|
}, [t]);
|
||||||
|
|
||||||
const onConfirm = (optionKey: SortConditionPB) => {
|
const onConfirm = (optionKey: SortConditionPB) => {
|
||||||
onChange?.(optionKey);
|
onChange?.(optionKey);
|
||||||
|
@ -84,6 +84,7 @@ function ViewActions({ view, pageId, ...props }: { pageId: string; view: Page }
|
|||||||
updatePageName({
|
updatePageName({
|
||||||
id: viewId,
|
id: viewId,
|
||||||
name: val,
|
name: val,
|
||||||
|
immediate: true,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
setOpenRenameDialog(false);
|
setOpenRenameDialog(false);
|
||||||
|
@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import { useDatabaseVisibilityRows } from '$app/components/database';
|
import { useDatabaseVisibilityRows } from '$app/components/database';
|
||||||
import { Field } from '$app/application/database';
|
import { Field } from '$app/application/database';
|
||||||
import { DEFAULT_FIELD_WIDTH, GRID_ACTIONS_WIDTH } from '$app/components/database/grid/constants';
|
import { DEFAULT_FIELD_WIDTH, GRID_ACTIONS_WIDTH } from '$app/components/database/grid/constants';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
field: Field;
|
field: Field;
|
||||||
@ -13,6 +14,7 @@ export function GridCalculate({ field, index }: Props) {
|
|||||||
const rowMetas = useDatabaseVisibilityRows();
|
const rowMetas = useDatabaseVisibilityRows();
|
||||||
const count = rowMetas.length;
|
const count = rowMetas.length;
|
||||||
const width = index === 0 ? GRID_ACTIONS_WIDTH : field.width ?? DEFAULT_FIELD_WIDTH;
|
const width = index === 0 ? GRID_ACTIONS_WIDTH : field.width ?? DEFAULT_FIELD_WIDTH;
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -23,7 +25,7 @@ export function GridCalculate({ field, index }: Props) {
|
|||||||
>
|
>
|
||||||
{field.isPrimary ? (
|
{field.isPrimary ? (
|
||||||
<>
|
<>
|
||||||
<span className={'mr-2 text-text-caption'}>Count</span>
|
<span className={'mr-2 text-text-caption'}>{t('grid.calculationTypeLabel.count')}</span>
|
||||||
<span>{count}</span>
|
<span>{count}</span>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -133,6 +133,10 @@ export const GridField: FC<GridFieldProps> = memo(
|
|||||||
className='relative flex h-full w-full items-center rounded-none px-0'
|
className='relative flex h-full w-full items-center rounded-none px-0'
|
||||||
disableRipple
|
disableRipple
|
||||||
onContextMenu={(event) => {
|
onContextMenu={(event) => {
|
||||||
|
if (propertyMenuOpened || menuOpened || open) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
handleClick();
|
handleClick();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { rowService } from '$app/application/database';
|
import { rowService } from '$app/application/database';
|
||||||
import { useViewId } from '$app/hooks';
|
import { useViewId } from '$app/hooks';
|
||||||
import { t } from 'i18next';
|
|
||||||
import { ReactComponent as AddSvg } from '$app/assets/add.svg';
|
import { ReactComponent as AddSvg } from '$app/assets/add.svg';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
index: number;
|
index: number;
|
||||||
@ -15,6 +15,7 @@ const CSS_HIGHLIGHT_PROPERTY = 'bg-content-blue-50';
|
|||||||
function GridNewRow({ index, groupId, getContainerRef }: Props) {
|
function GridNewRow({ index, groupId, getContainerRef }: Props) {
|
||||||
const viewId = useViewId();
|
const viewId = useViewId();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
const handleClick = useCallback(() => {
|
const handleClick = useCallback(() => {
|
||||||
void rowService.createRow(viewId, {
|
void rowService.createRow(viewId, {
|
||||||
groupId,
|
groupId,
|
||||||
|
@ -10,6 +10,7 @@ import { useGridColumn, useGridRow } from './GridTable.hooks';
|
|||||||
import GridStickyHeader from '$app/components/database/grid/grid_sticky_header/GridStickyHeader';
|
import GridStickyHeader from '$app/components/database/grid/grid_sticky_header/GridStickyHeader';
|
||||||
import GridTableOverlay from '$app/components/database/grid/grid_overlay/GridTableOverlay';
|
import GridTableOverlay from '$app/components/database/grid/grid_overlay/GridTableOverlay';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
|
import { useViewId } from '$app/hooks';
|
||||||
|
|
||||||
export interface GridTableProps {
|
export interface GridTableProps {
|
||||||
onEditRecord: (rowId: string) => void;
|
onEditRecord: (rowId: string) => void;
|
||||||
@ -30,6 +31,7 @@ export const GridTable: FC<GridTableProps> = React.memo(({ onEditRecord }) => {
|
|||||||
columns,
|
columns,
|
||||||
ref as React.MutableRefObject<Grid<GridColumn[] | { columns: GridColumn[]; renderRows: RenderRow[] }> | null>
|
ref as React.MutableRefObject<Grid<GridColumn[] | { columns: GridColumn[]; renderRows: RenderRow[] }> | null>
|
||||||
);
|
);
|
||||||
|
const viewId = useViewId();
|
||||||
const { rowHeight } = useGridRow();
|
const { rowHeight } = useGridRow();
|
||||||
const onRendered = useDatabaseRendered();
|
const onRendered = useDatabaseRendered();
|
||||||
|
|
||||||
@ -139,7 +141,7 @@ export const GridTable: FC<GridTableProps> = React.memo(({ onEditRecord }) => {
|
|||||||
className={'grid-scroll-container'}
|
className={'grid-scroll-container'}
|
||||||
outerRef={(el) => {
|
outerRef={(el) => {
|
||||||
scrollElementRef.current = el;
|
scrollElementRef.current = el;
|
||||||
onRendered();
|
onRendered(viewId);
|
||||||
}}
|
}}
|
||||||
innerRef={containerRef}
|
innerRef={containerRef}
|
||||||
>
|
>
|
||||||
|
@ -3,8 +3,19 @@ import { ViewLayoutPB } from '@/services/backend';
|
|||||||
|
|
||||||
export function useLoadDatabaseList({ searchText, layout }: { searchText: string; layout: ViewLayoutPB }) {
|
export function useLoadDatabaseList({ searchText, layout }: { searchText: string; layout: ViewLayoutPB }) {
|
||||||
const list = useAppSelector((state) => {
|
const list = useAppSelector((state) => {
|
||||||
|
const workspaces = state.workspace.workspaces.map((item) => item.id) ?? [];
|
||||||
|
|
||||||
return Object.values(state.pages.pageMap).filter((page) => {
|
return Object.values(state.pages.pageMap).filter((page) => {
|
||||||
if (page.layout !== layout) return false;
|
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 page.name.toLowerCase().includes(searchText.toLowerCase());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -7,17 +7,19 @@ function GridView({ viewId }: { viewId: string }) {
|
|||||||
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
const [rendered, setRendered] = useState(false);
|
const [rendered, setRendered] = useState<{ viewId: string; rendered: boolean } | undefined>(undefined);
|
||||||
|
|
||||||
// delegate wheel event to layout when grid is scrolled to top or bottom
|
// delegate wheel event to layout when grid is scrolled to top or bottom
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const element = ref.current;
|
const element = ref.current;
|
||||||
|
|
||||||
if (!element) {
|
const viewId = rendered?.viewId;
|
||||||
|
|
||||||
|
if (!viewId || !element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const gridScroller = element.querySelector('.grid-scroll-container') as HTMLDivElement;
|
const gridScroller = element.querySelector(`[data-view-id="${viewId}"] .grid-scroll-container`) as HTMLDivElement;
|
||||||
|
|
||||||
const scrollLayout = gridScroller?.closest('.appflowy-scroll-container') as HTMLDivElement;
|
const scrollLayout = gridScroller?.closest('.appflowy-scroll-container') as HTMLDivElement;
|
||||||
|
|
||||||
@ -29,7 +31,7 @@ function GridView({ viewId }: { viewId: string }) {
|
|||||||
const deltaY = event.deltaY;
|
const deltaY = event.deltaY;
|
||||||
const deltaX = event.deltaX;
|
const deltaX = event.deltaX;
|
||||||
|
|
||||||
if (deltaX > 10) {
|
if (Math.abs(deltaX) > 8) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,8 +52,11 @@ function GridView({ viewId }: { viewId: string }) {
|
|||||||
};
|
};
|
||||||
}, [rendered]);
|
}, [rendered]);
|
||||||
|
|
||||||
const onRendered = useCallback(() => {
|
const onRendered = useCallback((viewId: string) => {
|
||||||
setRendered(true);
|
setRendered({
|
||||||
|
viewId,
|
||||||
|
rendered: true,
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -133,9 +133,9 @@ function LinkEditContent({ onClose, defaultHref }: { onClose: () => void; defaul
|
|||||||
<div ref={scrollRef} className={'mt-1 flex w-full flex-col items-start'}>
|
<div ref={scrollRef} className={'mt-1 flex w-full flex-col items-start'}>
|
||||||
{isActivated && (
|
{isActivated && (
|
||||||
<KeyboardNavigation
|
<KeyboardNavigation
|
||||||
|
options={editOptions}
|
||||||
disableFocus={!focusMenu}
|
disableFocus={!focusMenu}
|
||||||
scrollRef={scrollRef}
|
scrollRef={scrollRef}
|
||||||
options={editOptions}
|
|
||||||
onConfirm={onConfirm}
|
onConfirm={onConfirm}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setFocusMenu(true);
|
setFocusMenu(true);
|
||||||
@ -143,8 +143,8 @@ function LinkEditContent({ onClose, defaultHref }: { onClose: () => void; defaul
|
|||||||
onBlur={() => {
|
onBlur={() => {
|
||||||
setFocusMenu(false);
|
setFocusMenu(false);
|
||||||
}}
|
}}
|
||||||
onEscape={onClose}
|
|
||||||
disableSelect={!focusMenu}
|
disableSelect={!focusMenu}
|
||||||
|
onEscape={onClose}
|
||||||
onKeyDown={(e) => {
|
onKeyDown={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (isHotkey('Tab', e)) {
|
if (isHotkey('Tab', e)) {
|
||||||
|
@ -40,7 +40,7 @@ export function LinkEditPopover({
|
|||||||
initialAnchorOrigin,
|
initialAnchorOrigin,
|
||||||
initialTransformOrigin,
|
initialTransformOrigin,
|
||||||
initialPaperWidth: 340,
|
initialPaperWidth: 340,
|
||||||
initialPaperHeight: 180,
|
initialPaperHeight: 200,
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -160,7 +160,7 @@ export function ColorPicker({ onEscape, onChange, disableFocus }: ColorPickerPro
|
|||||||
}, [renderColorItem, t]);
|
}, [renderColorItem, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={ref} className={'flex h-full max-h-[360px] w-full flex-col overflow-y-auto'}>
|
<div ref={ref} className={'flex h-full max-h-[420px] w-full flex-col overflow-y-auto'}>
|
||||||
<KeyboardNavigation
|
<KeyboardNavigation
|
||||||
disableFocus={disableFocus}
|
disableFocus={disableFocus}
|
||||||
onPressLeft={onEscape}
|
onPressLeft={onEscape}
|
||||||
|
@ -14,9 +14,13 @@ const initialOrigin: {
|
|||||||
anchorOrigin?: PopoverOrigin;
|
anchorOrigin?: PopoverOrigin;
|
||||||
} = {
|
} = {
|
||||||
anchorOrigin: {
|
anchorOrigin: {
|
||||||
vertical: 'top',
|
vertical: 'center',
|
||||||
horizontal: 'right',
|
horizontal: 'right',
|
||||||
},
|
},
|
||||||
|
transformOrigin: {
|
||||||
|
vertical: 'center',
|
||||||
|
horizontal: 'left',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function Color({
|
export function Color({
|
||||||
|
@ -12,7 +12,7 @@ const ActionButton = forwardRef<
|
|||||||
} & IconButtonProps
|
} & IconButtonProps
|
||||||
>(({ tooltip, onClick, disabled, children, active, className, ...props }, ref) => {
|
>(({ tooltip, onClick, disabled, children, active, className, ...props }, ref) => {
|
||||||
return (
|
return (
|
||||||
<Tooltip placement={'top'} title={tooltip}>
|
<Tooltip disableInteractive={true} placement={'top'} title={tooltip}>
|
||||||
<IconButton
|
<IconButton
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
|
@ -47,7 +47,7 @@ function ColorPopover({
|
|||||||
|
|
||||||
const { paperHeight, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({
|
const { paperHeight, transformOrigin, anchorOrigin, isEntered } = usePopoverAutoPosition({
|
||||||
initialPaperWidth: 200,
|
initialPaperWidth: 200,
|
||||||
initialPaperHeight: 360,
|
initialPaperHeight: 420,
|
||||||
anchorEl,
|
anchorEl,
|
||||||
initialAnchorOrigin: initialOrigin.anchorOrigin,
|
initialAnchorOrigin: initialOrigin.anchorOrigin,
|
||||||
initialTransformOrigin: initialOrigin.transformOrigin,
|
initialTransformOrigin: initialOrigin.transformOrigin,
|
||||||
|
@ -112,13 +112,14 @@ export function withPasted(editor: ReactEditor) {
|
|||||||
if (isText && parent) {
|
if (isText && parent) {
|
||||||
const [parentNode, parentPath] = parent as NodeEntry<Element>;
|
const [parentNode, parentPath] = parent as NodeEntry<Element>;
|
||||||
const pastedNodeIsPage = parentNode.type === EditorNodeType.Page;
|
const pastedNodeIsPage = parentNode.type === EditorNodeType.Page;
|
||||||
|
const pastedNodeIsNotList = !LIST_TYPES.includes(parentNode.type as EditorNodeType);
|
||||||
const clonedFragment = transFragment(editor, fragment);
|
const clonedFragment = transFragment(editor, fragment);
|
||||||
|
|
||||||
const [firstNode, ...otherNodes] = clonedFragment;
|
const [firstNode, ...otherNodes] = clonedFragment;
|
||||||
const lastNode = getLastNode(otherNodes[otherNodes.length - 1]);
|
const lastNode = getLastNode(otherNodes[otherNodes.length - 1]);
|
||||||
const firstIsEmbed = editor.isEmbed(firstNode);
|
const firstIsEmbed = editor.isEmbed(firstNode);
|
||||||
const insertNodes: Element[] = [...otherNodes];
|
const insertNodes: Element[] = [...otherNodes];
|
||||||
const needMoveChildren = parentNode.children.length > 1 && !pastedNodeIsPage;
|
const needMoveChildren = parentNode.children.length > 1 && !pastedNodeIsPage && !pastedNodeIsNotList;
|
||||||
let moveStartIndex = 0;
|
let moveStartIndex = 0;
|
||||||
|
|
||||||
if (firstIsEmbed) {
|
if (firstIsEmbed) {
|
||||||
@ -138,7 +139,7 @@ export function withPasted(editor: ReactEditor) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (children.length > 0) {
|
if (children.length > 0) {
|
||||||
if (pastedNodeIsPage) {
|
if (pastedNodeIsPage || pastedNodeIsNotList) {
|
||||||
// lift the children of the first fragment node to current node
|
// lift the children of the first fragment node to current node
|
||||||
insertNodes.unshift(...children);
|
insertNodes.unshift(...children);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useCallback, useMemo } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
import { useLoadExpandedPages } from '$app/components/layout/bread_crumb/Breadcrumb.hooks';
|
import { useLoadExpandedPages } from '$app/components/layout/bread_crumb/Breadcrumb.hooks';
|
||||||
import Breadcrumbs from '@mui/material/Breadcrumbs';
|
import Breadcrumbs from '@mui/material/Breadcrumbs';
|
||||||
import Link from '@mui/material/Link';
|
import Link from '@mui/material/Link';
|
||||||
@ -13,7 +13,6 @@ function Breadcrumb() {
|
|||||||
const { isTrash, pagePath, currentPage } = useLoadExpandedPages();
|
const { isTrash, pagePath, currentPage } = useLoadExpandedPages();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const parentPages = useMemo(() => pagePath.slice(1, -1).filter(Boolean) as Page[], [pagePath]);
|
|
||||||
const navigateToPage = useCallback(
|
const navigateToPage = useCallback(
|
||||||
(page: Page) => {
|
(page: Page) => {
|
||||||
const pageType = pageTypeMap[page.layout];
|
const pageType = pageTypeMap[page.layout];
|
||||||
@ -33,26 +32,32 @@ function Breadcrumb() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Breadcrumbs aria-label='breadcrumb'>
|
<Breadcrumbs aria-label='breadcrumb'>
|
||||||
{parentPages?.map((page: Page) => (
|
{pagePath?.map((page: Page, index) => {
|
||||||
<Link
|
if (index === pagePath.length - 1) {
|
||||||
key={page.id}
|
return (
|
||||||
className={'flex cursor-pointer select-none gap-1'}
|
<div key={page.id} className={'flex select-none gap-1 text-text-title'}>
|
||||||
underline='hover'
|
<div className={'select-none'}>{getPageIcon(page)}</div>
|
||||||
color='inherit'
|
{page.name || t('menuAppHeader.defaultNewPageName')}
|
||||||
onClick={() => {
|
</div>
|
||||||
navigateToPage(page);
|
);
|
||||||
}}
|
}
|
||||||
>
|
|
||||||
<div>{getPageIcon(page)}</div>
|
|
||||||
|
|
||||||
{page.name || t('document.title.placeholder')}
|
return (
|
||||||
</Link>
|
<Link
|
||||||
))}
|
key={page.id}
|
||||||
|
className={'flex cursor-pointer select-none gap-1'}
|
||||||
|
underline='hover'
|
||||||
|
color='inherit'
|
||||||
|
onClick={() => {
|
||||||
|
navigateToPage(page);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div>{getPageIcon(page)}</div>
|
||||||
|
|
||||||
<div className={'flex select-none gap-1 text-text-title'}>
|
{page.name || t('document.title.placeholder')}
|
||||||
<div className={'select-none'}>{getPageIcon(currentPage)}</div>
|
</Link>
|
||||||
{currentPage.name || t('menuAppHeader.defaultNewPageName')}
|
);
|
||||||
</div>
|
})}
|
||||||
</Breadcrumbs>
|
</Breadcrumbs>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,72 +1,34 @@
|
|||||||
import { useAppSelector } from '$app/stores/store';
|
import { useAppSelector } from '$app/stores/store';
|
||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useParams, useLocation } from 'react-router-dom';
|
import { useParams, useLocation } from 'react-router-dom';
|
||||||
import { Page } from '$app_reducers/pages/slice';
|
import { Page } from '$app_reducers/pages/slice';
|
||||||
import { getPage } from '$app/application/folder/page.service';
|
|
||||||
|
|
||||||
export function useLoadExpandedPages() {
|
export function useLoadExpandedPages() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isTrash = useMemo(() => location.pathname.includes('trash'), [location.pathname]);
|
const isTrash = useMemo(() => location.pathname.includes('trash'), [location.pathname]);
|
||||||
const currentPageId = params.id;
|
const currentPageId = params.id;
|
||||||
const pageMap = useAppSelector((state) => state.pages.pageMap);
|
const currentPage = useAppSelector((state) => (currentPageId ? state.pages.pageMap[currentPageId] : undefined));
|
||||||
const currentPage = currentPageId ? pageMap[currentPageId] : null;
|
|
||||||
|
|
||||||
const [pagePath, setPagePath] = useState<
|
const pagePath = useAppSelector((state) => {
|
||||||
(
|
const result: Page[] = [];
|
||||||
| Page
|
|
||||||
| {
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
)[]
|
|
||||||
>([]);
|
|
||||||
|
|
||||||
const loadPagePath = useCallback(
|
if (!currentPage) return result;
|
||||||
async (pageId: string) => {
|
|
||||||
let page = pageMap[pageId];
|
|
||||||
|
|
||||||
if (!page) {
|
const findParent = (page: Page) => {
|
||||||
try {
|
if (!page.parentId) return;
|
||||||
page = await getPage(pageId);
|
const parent = state.pages.pageMap[page.parentId];
|
||||||
} catch (e) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!page) {
|
if (parent) {
|
||||||
return;
|
result.unshift(parent);
|
||||||
}
|
findParent(parent);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setPagePath((prev) => {
|
findParent(currentPage);
|
||||||
return [page, ...prev];
|
result.push(currentPage);
|
||||||
});
|
return result;
|
||||||
|
});
|
||||||
if (page.parentId) {
|
|
||||||
await loadPagePath(page.parentId);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[pageMap]
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPagePath([]);
|
|
||||||
if (!currentPageId) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
void loadPagePath(currentPageId);
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [currentPageId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setPagePath((prev) => {
|
|
||||||
return prev.map((page, index) => {
|
|
||||||
if (!page) return page;
|
|
||||||
if (index === 0) return page;
|
|
||||||
return 'id' in page && page.id ? pageMap[page.id] : page;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, [pageMap]);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
pagePath,
|
pagePath,
|
||||||
|
@ -1,7 +1,34 @@
|
|||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 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 {
|
.appflowy-scroll-container {
|
||||||
&::-webkit-scrollbar {
|
&::-webkit-scrollbar {
|
||||||
|
@ -12,7 +12,16 @@ function NestedPage({ pageId }: { pageId: string }) {
|
|||||||
const { toggleCollapsed, collapsed, childPages } = useLoadChildPages(pageId);
|
const { toggleCollapsed, collapsed, childPages } = useLoadChildPages(pageId);
|
||||||
const { onAddPage, onPageClick, onDeletePage, onDuplicatePage, onRenamePage } = usePageActions(pageId);
|
const { onAddPage, onPageClick, onDeletePage, onDuplicatePage, onRenamePage } = usePageActions(pageId);
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const page = useAppSelector((state) => state.pages.pageMap[pageId]);
|
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) => {
|
const disableChildren = useAppSelector((state) => {
|
||||||
if (!page) return true;
|
if (!page) return true;
|
||||||
const layout = state.pages.pageMap[page.parentId]?.layout;
|
const layout = state.pages.pageMap[page.parentId]?.layout;
|
||||||
@ -65,6 +74,9 @@ function NestedPage({ pageId }: { pageId: string }) {
|
|||||||
}
|
}
|
||||||
}, [dropPosition, isDragging, isDraggingOver, page?.layout]);
|
}, [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 (
|
return (
|
||||||
<div
|
<div
|
||||||
className={className}
|
className={className}
|
||||||
@ -73,7 +85,7 @@ function NestedPage({ pageId }: { pageId: string }) {
|
|||||||
onDragOver={onDragOver}
|
onDragOver={onDragOver}
|
||||||
onDragEnd={onDragEnd}
|
onDragEnd={onDragEnd}
|
||||||
onDrop={onDrop}
|
onDrop={onDrop}
|
||||||
draggable={true}
|
draggable={draggable}
|
||||||
data-drop-enabled={page?.layout === ViewLayoutPB.Document}
|
data-drop-enabled={page?.layout === ViewLayoutPB.Document}
|
||||||
data-dragging={isDragging}
|
data-dragging={isDragging}
|
||||||
data-page-id={pageId}
|
data-page-id={pageId}
|
||||||
|
@ -2,6 +2,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
|
|||||||
import { RootState } from '$app/stores/store';
|
import { RootState } from '$app/stores/store';
|
||||||
import { pagesActions } from '$app_reducers/pages/slice';
|
import { pagesActions } from '$app_reducers/pages/slice';
|
||||||
import { movePage, updatePage } from '$app/application/folder/page.service';
|
import { movePage, updatePage } from '$app/application/folder/page.service';
|
||||||
|
import debounce from 'lodash-es/debounce';
|
||||||
|
|
||||||
export const movePageThunk = createAsyncThunk(
|
export const movePageThunk = createAsyncThunk(
|
||||||
'pages/movePage',
|
'pages/movePage',
|
||||||
@ -61,12 +62,14 @@ export const movePageThunk = createAsyncThunk(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const debounceUpdateName = debounce(updatePage, 1000);
|
||||||
|
|
||||||
export const updatePageName = createAsyncThunk(
|
export const updatePageName = createAsyncThunk(
|
||||||
'pages/updateName',
|
'pages/updateName',
|
||||||
async (payload: { id: string; name: string }, thunkAPI) => {
|
async (payload: { id: string; name: string; immediate?: boolean }, thunkAPI) => {
|
||||||
const { dispatch, getState } = thunkAPI;
|
const { dispatch, getState } = thunkAPI;
|
||||||
const { pageMap } = (getState() as RootState).pages;
|
const { pageMap } = (getState() as RootState).pages;
|
||||||
const { id, name } = payload;
|
const { id, name, immediate } = payload;
|
||||||
const page = pageMap[id];
|
const page = pageMap[id];
|
||||||
|
|
||||||
if (name === page.name) return;
|
if (name === page.name) return;
|
||||||
@ -78,9 +81,13 @@ export const updatePageName = createAsyncThunk(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await updatePage({
|
if (immediate) {
|
||||||
id,
|
await updatePage({ id, name });
|
||||||
name,
|
} else {
|
||||||
});
|
await debounceUpdateName({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
@import './variables/index.css';
|
@import './variables/index.css';
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* stop body from scrolling */
|
/* stop body from scrolling */
|
||||||
html,
|
html,
|
||||||
body {
|
body {
|
||||||
@ -53,114 +58,3 @@ th {
|
|||||||
@apply text-left font-normal;
|
@apply text-left font-normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.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%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.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%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-sticky-header::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
.grid-scroll-container::-webkit-scrollbar {
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
20
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
20
frontend/appflowy_web/wasm-libs/Cargo.lock
generated
@ -221,7 +221,7 @@ checksum = "080e9890a082662b09c1ad45f567faeeb47f22b5fb23895fbe1e651e718e25ca"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "app-error"
|
name = "app-error"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -545,7 +545,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "client-api"
|
name = "client-api"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"again",
|
"again",
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@ -946,7 +946,7 @@ checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "database-entity"
|
name = "database-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -1700,7 +1700,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue"
|
name = "gotrue"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
@ -1717,7 +1717,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "gotrue-entity"
|
name = "gotrue-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -2051,7 +2051,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "infra"
|
name = "infra"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
@ -3295,7 +3295,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "realtime-entity"
|
name = "realtime-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -3319,7 +3319,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "realtime-protocol"
|
name = "realtime-protocol"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"bincode",
|
"bincode",
|
||||||
@ -3772,7 +3772,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "shared-entity"
|
name = "shared-entity"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"app-error",
|
"app-error",
|
||||||
@ -4714,7 +4714,7 @@ checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "websocket"
|
name = "websocket"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eed20c25197109a3f799a5d18d0e38e9f5529d69#eed20c25197109a3f799a5d18d0e38e9f5529d69"
|
source = "git+https://github.com/AppFlowy-IO/AppFlowy-Cloud?rev=eb79b9f5e80846bd06b30b4f9c04039ce1452582#eb79b9f5e80846bd06b30b4f9c04039ce1452582"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user