mirror of
https://github.com/spacedriveapp/spacedrive
synced 2024-07-04 14:33:34 +00:00
Merge branch 'main' into mob-70-update-folder-names-when-renamed-on-ios
This commit is contained in:
commit
4a55a2fae1
8
.github/actions/publish-artifacts/index.ts
vendored
8
.github/actions/publish-artifacts/index.ts
vendored
|
@ -7,7 +7,7 @@ type OS = 'darwin' | 'windows' | 'linux';
|
||||||
type Arch = 'x64' | 'arm64';
|
type Arch = 'x64' | 'arm64';
|
||||||
type TargetConfig = { bundle: string; ext: string };
|
type TargetConfig = { bundle: string; ext: string };
|
||||||
type BuildTarget = {
|
type BuildTarget = {
|
||||||
updater: { bundle: string; bundleExt: string; archiveExt: string; };
|
updater: { bundle: string; bundleExt: string; archiveExt: string };
|
||||||
standalone: Array<TargetConfig>;
|
standalone: Array<TargetConfig>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -31,7 +31,7 @@ const OS_TARGETS = {
|
||||||
linux: {
|
linux: {
|
||||||
updater: {
|
updater: {
|
||||||
bundle: 'appimage',
|
bundle: 'appimage',
|
||||||
bundleExt: "AppImage",
|
bundleExt: 'AppImage',
|
||||||
archiveExt: 'tar.gz'
|
archiveExt: 'tar.gz'
|
||||||
},
|
},
|
||||||
standalone: [
|
standalone: [
|
||||||
|
@ -57,8 +57,8 @@ async function globFiles(pattern: string) {
|
||||||
return await globber.glob();
|
return await globber.glob();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function uploadUpdater({ bundle, bundleExt, archiveExt }: BuildTarget["updater"]) {
|
async function uploadUpdater({ bundle, bundleExt, archiveExt }: BuildTarget['updater']) {
|
||||||
const fullExt = `${bundleExt}.${archiveExt}`
|
const fullExt = `${bundleExt}.${archiveExt}`;
|
||||||
const files = await globFiles(`${BUNDLE_DIR}/${bundle}/*.${fullExt}*`);
|
const files = await globFiles(`${BUNDLE_DIR}/${bundle}/*.${fullExt}*`);
|
||||||
|
|
||||||
const updaterPath = files.find((file) => file.endsWith(fullExt));
|
const updaterPath = files.find((file) => file.endsWith(fullExt));
|
||||||
|
|
2
.github/actions/setup-pnpm/action.yml
vendored
2
.github/actions/setup-pnpm/action.yml
vendored
|
@ -11,7 +11,7 @@ runs:
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v3
|
uses: pnpm/action-setup@v3
|
||||||
with:
|
with:
|
||||||
version: 8.x.x
|
version: 9.0.6
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|
25
.github/actions/setup-rust/action.yaml
vendored
25
.github/actions/setup-rust/action.yaml
vendored
|
@ -67,11 +67,32 @@ runs:
|
||||||
working-directory: core
|
working-directory: core
|
||||||
if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }}
|
if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: cargo prisma generate
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
cargo prisma generate
|
||||||
|
|
||||||
|
# Check if a new migration should be created due to changes in the schema
|
||||||
|
cargo prisma migrate dev -n test --create-only --skip-generate
|
||||||
|
_new_migrations="$(
|
||||||
|
git ls-files --others --exclude-standard \
|
||||||
|
| { grep '^prisma/migrations/' || true; } \
|
||||||
|
| xargs sh -euxc '[ "$#" -lt 2 ] || grep -L "$@" || true' sh 'This is an empty migration' \
|
||||||
|
| wc -l \
|
||||||
|
| awk '{$1=$1};1'
|
||||||
|
)"
|
||||||
|
if [ "$_new_migrations" -gt 0 ]; then
|
||||||
|
echo "::error file=core/prisma/schema.prisma,title=Missing migration::New migration should be generated due to changes in prisma schema"
|
||||||
|
case "$GITHUB_REF" in
|
||||||
|
"refs/heads/main" | "refs/heads/gh-readonly-queue/main/"* | "refs/tags/"*)
|
||||||
|
# Fail action if we are on main or a release tag, to avoid releasing an app with a broken db
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Save Prisma codegen
|
- name: Save Prisma codegen
|
||||||
id: cache-prisma-save
|
id: cache-prisma-save
|
||||||
if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }}
|
if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' && (github.ref == 'refs/heads/main' || inputs.save-cache == 'true') }}
|
||||||
uses: actions/cache/save@v4
|
uses: actions/cache/save@v4
|
||||||
with:
|
with:
|
||||||
key: ${{ steps.cache-prisma-restore.outputs.cache-primary-key }}
|
key: ${{ steps.cache-prisma-restore.outputs.cache-primary-key }}
|
||||||
|
|
18
.github/actions/setup-system/action.yml
vendored
18
.github/actions/setup-system/action.yml
vendored
|
@ -42,6 +42,11 @@ runs:
|
||||||
key: ${{ steps.cache-llvm-restore.outputs.cache-primary-key }}
|
key: ${{ steps.cache-llvm-restore.outputs.cache-primary-key }}
|
||||||
path: C:/Program Files/LLVM
|
path: C:/Program Files/LLVM
|
||||||
|
|
||||||
|
- name: Install current Bash on macOS
|
||||||
|
shell: bash
|
||||||
|
if: runner.os == 'macOS'
|
||||||
|
run: brew install bash
|
||||||
|
|
||||||
- name: Install Nasm
|
- name: Install Nasm
|
||||||
if: ${{ runner.os != 'Linux' }}
|
if: ${{ runner.os != 'Linux' }}
|
||||||
uses: ilammy/setup-nasm@v1
|
uses: ilammy/setup-nasm@v1
|
||||||
|
@ -53,6 +58,17 @@ runs:
|
||||||
curl -L# 'https://github.com/rui314/mold/releases/download/v2.4.0/mold-2.4.0-x86_64-linux.tar.gz' \
|
curl -L# 'https://github.com/rui314/mold/releases/download/v2.4.0/mold-2.4.0-x86_64-linux.tar.gz' \
|
||||||
| sudo tar -xzf- -C /usr/local
|
| sudo tar -xzf- -C /usr/local
|
||||||
|
|
||||||
|
- name: Remove 32-bit libs and incompatible pre-installed pkgs from Runner
|
||||||
|
shell: bash
|
||||||
|
if: ${{ runner.os == 'Linux' }}
|
||||||
|
run: |
|
||||||
|
dpkg -l | grep i386
|
||||||
|
sudo apt-get purge --allow-remove-essential libc6-i386 ".*:i386"
|
||||||
|
sudo dpkg --remove-architecture i386
|
||||||
|
|
||||||
|
# https://github.com/actions/runner-images/issues/9546#issuecomment-2014940361
|
||||||
|
sudo apt-get remove libunwind-*
|
||||||
|
|
||||||
- name: Setup Rust and Dependencies
|
- name: Setup Rust and Dependencies
|
||||||
uses: ./.github/actions/setup-rust
|
uses: ./.github/actions/setup-rust
|
||||||
with:
|
with:
|
||||||
|
@ -78,4 +94,4 @@ runs:
|
||||||
pushd scripts
|
pushd scripts
|
||||||
npm i --production
|
npm i --production
|
||||||
popd
|
popd
|
||||||
node scripts/preprep.mjs
|
env NODE_ENV=debug node scripts/preprep.mjs
|
||||||
|
|
26
.github/workflows/ci.yml
vendored
26
.github/workflows/ci.yml
vendored
|
@ -11,6 +11,7 @@ env:
|
||||||
CARGO_NET_RETRY: 10
|
CARGO_NET_RETRY: 10
|
||||||
RUST_BACKTRACE: short
|
RUST_BACKTRACE: short
|
||||||
RUSTUP_MAX_RETRIES: 10
|
RUSTUP_MAX_RETRIES: 10
|
||||||
|
SD_AUTH: disabled
|
||||||
|
|
||||||
# Cancel previous runs of the same workflow on the same branch.
|
# Cancel previous runs of the same workflow on the same branch.
|
||||||
concurrency:
|
concurrency:
|
||||||
|
@ -66,17 +67,28 @@ jobs:
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: Install Cypress
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
pnpm exec cypress install
|
||||||
|
rm -rf /Users/runner/.cache/Cypress
|
||||||
|
ln -sf /Users/runner/Library/Caches/Cypress /Users/runner/.cache/Cypress
|
||||||
|
|
||||||
- name: Setup Cypress
|
- name: Setup Cypress
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@v6
|
||||||
with:
|
with:
|
||||||
runTests: false
|
runTests: false
|
||||||
working-directory: .
|
working-directory: .
|
||||||
|
|
||||||
|
- name: Download test data
|
||||||
|
run: pnpm test-data small
|
||||||
|
|
||||||
- name: E2E test
|
- name: E2E test
|
||||||
uses: cypress-io/github-action@v6
|
uses: cypress-io/github-action@v6
|
||||||
with:
|
with:
|
||||||
|
build: npx cypress info
|
||||||
install: false
|
install: false
|
||||||
command: pnpm test:e2e
|
command: env CI=true pnpm test:e2e
|
||||||
working-directory: apps/web
|
working-directory: apps/web
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
|
@ -119,6 +131,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: dorny/paths-filter@v3
|
- uses: dorny/paths-filter@v3
|
||||||
|
continue-on-error: true
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
|
@ -135,13 +148,13 @@ jobs:
|
||||||
- 'Cargo.lock'
|
- 'Cargo.lock'
|
||||||
|
|
||||||
- name: Setup Rust and Prisma
|
- name: Setup Rust and Prisma
|
||||||
if: steps.filter.outputs.changes == 'true'
|
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||||
uses: ./.github/actions/setup-rust
|
uses: ./.github/actions/setup-rust
|
||||||
with:
|
with:
|
||||||
restore-cache: 'false'
|
restore-cache: 'false'
|
||||||
|
|
||||||
- name: Run rustfmt
|
- name: Run rustfmt
|
||||||
if: steps.filter.outputs.changes == 'true'
|
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||||
run: cargo fmt --all -- --check
|
run: cargo fmt --all -- --check
|
||||||
|
|
||||||
clippy:
|
clippy:
|
||||||
|
@ -173,6 +186,7 @@ jobs:
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: dorny/paths-filter@v3
|
- uses: dorny/paths-filter@v3
|
||||||
|
continue-on-error: true
|
||||||
id: filter
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
|
@ -191,16 +205,16 @@ jobs:
|
||||||
- 'Cargo.lock'
|
- 'Cargo.lock'
|
||||||
|
|
||||||
- name: Setup System and Rust
|
- name: Setup System and Rust
|
||||||
if: steps.filter.outputs.changes == 'true'
|
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||||
uses: ./.github/actions/setup-system
|
uses: ./.github/actions/setup-system
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Run Clippy
|
- name: Run Clippy
|
||||||
if: steps.filter.outputs.changes == 'true'
|
if: steps.filter.outcome != 'success' || steps.filter.outputs.changes == 'true'
|
||||||
uses: actions-rs-plus/clippy-check@v2
|
uses: actions-rs-plus/clippy-check@v2
|
||||||
with:
|
with:
|
||||||
args: --workspace --all-features
|
args: --workspace --all-features --locked
|
||||||
|
|
||||||
# test:
|
# test:
|
||||||
# name: Test (${{ matrix.platform }})
|
# name: Test (${{ matrix.platform }})
|
||||||
|
|
25
.github/workflows/mobile-ci.yml
vendored
25
.github/workflows/mobile-ci.yml
vendored
|
@ -138,15 +138,15 @@ jobs:
|
||||||
|
|
||||||
ios:
|
ios:
|
||||||
name: iOS
|
name: iOS
|
||||||
runs-on: macos-14
|
runs-on: macos-12
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install Xcode
|
# - name: Install Xcode
|
||||||
uses: maxim-lobanov/setup-xcode@v1
|
# uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
# with:
|
||||||
xcode-version: latest-stable
|
# xcode-version: latest-stable
|
||||||
|
|
||||||
- name: Setup System and Rust
|
- name: Setup System and Rust
|
||||||
uses: ./.github/actions/setup-system
|
uses: ./.github/actions/setup-system
|
||||||
|
@ -189,9 +189,6 @@ jobs:
|
||||||
run: xcodebuild -workspace ./Spacedrive.xcworkspace -scheme Spacedrive -configuration Release -sdk iphonesimulator -derivedDataPath build -arch "$(uname -m)"
|
run: xcodebuild -workspace ./Spacedrive.xcworkspace -scheme Spacedrive -configuration Release -sdk iphonesimulator -derivedDataPath build -arch "$(uname -m)"
|
||||||
|
|
||||||
- name: Install Maestro
|
- name: Install Maestro
|
||||||
env:
|
|
||||||
# Workaround: https://github.com/mobile-dev-inc/maestro/issues/1585
|
|
||||||
MAESTRO_VERSION: '1.33.1'
|
|
||||||
run: |
|
run: |
|
||||||
curl -Ls "https://get.maestro.mobile.dev" | bash
|
curl -Ls "https://get.maestro.mobile.dev" | bash
|
||||||
brew tap facebook/fb
|
brew tap facebook/fb
|
||||||
|
@ -199,16 +196,12 @@ jobs:
|
||||||
echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
|
echo "${HOME}/.maestro/bin" >> $GITHUB_PATH
|
||||||
|
|
||||||
- name: Run Simulator
|
- name: Run Simulator
|
||||||
|
id: run_simulator
|
||||||
uses: futureware-tech/simulator-action@v3
|
uses: futureware-tech/simulator-action@v3
|
||||||
with:
|
with:
|
||||||
model: 'iPhone 15'
|
model: 'iPhone 14'
|
||||||
os_version: 17
|
os_version: 16
|
||||||
erase_before_boot: false
|
erase_before_boot: false
|
||||||
|
|
||||||
- name: Run Tests
|
- name: Run Tests
|
||||||
env:
|
run: ./apps/mobile/scripts/run-maestro-tests.sh ios ${{ steps.run_simulator.outputs.udid }}
|
||||||
# https://github.com/expo/expo/blob/339fa68/apps/bare-expo/scripts/start-ios-e2e-test.ts#L12
|
|
||||||
MAESTRO_DRIVER_STARTUP_TIMEOUT: 120000
|
|
||||||
run: |
|
|
||||||
xcrun simctl install booted apps/mobile/ios/build/Build/Products/Release-iphonesimulator/Spacedrive.app
|
|
||||||
./apps/mobile/scripts/run-maestro-tests.sh ios
|
|
||||||
|
|
7
.github/workflows/release.yml
vendored
7
.github/workflows/release.yml
vendored
|
@ -68,13 +68,6 @@ jobs:
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Remove 32-bit libs
|
|
||||||
if: ${{ runner.os == 'Linux' }}
|
|
||||||
run: |
|
|
||||||
dpkg -l | grep i386
|
|
||||||
sudo apt-get purge --allow-remove-essential libc6-i386 ".*:i386"
|
|
||||||
sudo dpkg --remove-architecture i386
|
|
||||||
|
|
||||||
- name: Install Apple API key
|
- name: Install Apple API key
|
||||||
if: ${{ runner.os == 'macOS' }}
|
if: ${{ runner.os == 'macOS' }}
|
||||||
run: |
|
run: |
|
||||||
|
|
32
.github/workflows/server.yml
vendored
32
.github/workflows/server.yml
vendored
|
@ -30,13 +30,38 @@ jobs:
|
||||||
- name: Update buildah
|
- name: Update buildah
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
wget -O- 'https://github.com/HeavenVolkoff/buildah-static/releases/latest/download/buildah-amd64.tar.gz' \
|
wget -O- 'https://github.com/HeavenVolkoff/buildah-static/releases/latest/download/buildah-amd64.tar.gz' \
|
||||||
| sudo tar -xzf- -C /usr/local/bin
|
| sudo tar -xzf- -C /usr/local/bin
|
||||||
|
|
||||||
|
- name: Install netavark
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
sudo mkdir -p /usr/local/lib/podman
|
||||||
|
sudo wget -O- 'https://github.com/containers/netavark/releases/latest/download/netavark.gz' \
|
||||||
|
| gunzip | sudo dd status=none of=/usr/local/lib/podman/netavark
|
||||||
|
sudo chmod +x /usr/local/lib/podman/netavark
|
||||||
|
|
||||||
|
- name: Install passt
|
||||||
|
shell: bash
|
||||||
|
working-directory: /tmp
|
||||||
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
|
|
||||||
|
deb="$(
|
||||||
|
curl -SsL https://passt.top/builds/latest/x86_64 \
|
||||||
|
| grep -oP 'passt[^\.<>'\''"]+\.deb' | sort -u | head -n1
|
||||||
|
)"
|
||||||
|
|
||||||
|
curl -SsJLO "https://passt.top/builds/latest/x86_64/${deb}"
|
||||||
|
sudo dpkg -i "${deb}"
|
||||||
|
|
||||||
- name: Determine image name & tag
|
- name: Determine image name & tag
|
||||||
id: image_info
|
id: image_info
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
set -euxo pipefail
|
||||||
if [ "$GITHUB_EVENT_NAME" == "release" ]; then
|
if [ "$GITHUB_EVENT_NAME" == "release" ]; then
|
||||||
IMAGE_TAG="${GITHUB_REF##*/}"
|
IMAGE_TAG="${GITHUB_REF##*/}"
|
||||||
else
|
else
|
||||||
|
@ -54,8 +79,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build image
|
- name: Build image
|
||||||
id: build-image
|
id: build-image
|
||||||
# TODO: Change to stable version when available
|
uses: redhat-actions/buildah-build@v2
|
||||||
uses: redhat-actions/buildah-build@c79846fb306beeba490e89fb75d2d1af95831e79
|
|
||||||
with:
|
with:
|
||||||
tags: ${{ steps.image_info.outputs.tag }} ${{ github.event_name == 'release' && 'latest' || 'staging' }}
|
tags: ${{ steps.image_info.outputs.tag }} ${{ github.event_name == 'release' && 'latest' || 'staging' }}
|
||||||
archs: amd64
|
archs: amd64
|
||||||
|
@ -69,9 +93,7 @@ jobs:
|
||||||
./apps/server/docker/Dockerfile
|
./apps/server/docker/Dockerfile
|
||||||
|
|
||||||
- name: Push image to ghcr.io
|
- name: Push image to ghcr.io
|
||||||
# TODO: Restore redhat-actions/push-to-registry after PR is merged:
|
uses: redhat-actions/push-to-registry@v2
|
||||||
# https://github.com/redhat-actions/push-to-registry/pull/93
|
|
||||||
uses: Eusebiotrigo/push-to-registry@5acfa470857b62a053884f7214581d55ffeb54ac
|
|
||||||
with:
|
with:
|
||||||
tags: ${{ steps.build-image.outputs.tags }}
|
tags: ${{ steps.build-image.outputs.tags }}
|
||||||
image: ${{ steps.build-image.outputs.image }}
|
image: ${{ steps.build-image.outputs.image }}
|
||||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -83,3 +83,6 @@ spacedrive
|
||||||
.github/scripts/deps
|
.github/scripts/deps
|
||||||
.vite-inspect
|
.vite-inspect
|
||||||
vite.config.ts.*
|
vite.config.ts.*
|
||||||
|
|
||||||
|
/test-data
|
||||||
|
/config.json
|
||||||
|
|
|
@ -32,3 +32,6 @@ interface/components/TextViewer/prism-lazy.ts
|
||||||
|
|
||||||
# Stops from constant package.json changes showing up in commits
|
# Stops from constant package.json changes showing up in commits
|
||||||
package*.json
|
package*.json
|
||||||
|
|
||||||
|
# Dont format locales json
|
||||||
|
interface/locales
|
||||||
|
|
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
|
@ -7,6 +7,7 @@
|
||||||
"bradlc.vscode-tailwindcss", // Provides Tailwind CSS IntelliSense
|
"bradlc.vscode-tailwindcss", // Provides Tailwind CSS IntelliSense
|
||||||
"prisma.prisma", // Prisma is an open-source database toolkit
|
"prisma.prisma", // Prisma is an open-source database toolkit
|
||||||
"dbaeumer.vscode-eslint", // Integrates ESLint JavaScript into VS Code
|
"dbaeumer.vscode-eslint", // Integrates ESLint JavaScript into VS Code
|
||||||
"esbenp.prettier-vscode" // Code formatter using prettier
|
"esbenp.prettier-vscode", // Code formatter using prettier,
|
||||||
|
"lokalise.i18n-ally" // i18n-ally is an all-in-one i18n (internationalization) extension for VS Code
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
115
.vscode/i18n-ally-reviews.yml
vendored
Normal file
115
.vscode/i18n-ally-reviews.yml
vendored
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
# Review comments generated by i18n-ally. Please commit this file.
|
||||||
|
|
||||||
|
reviews:
|
||||||
|
about_vision_text:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: OS2GadFYJi0w8WbQ1KpUe
|
||||||
|
type: approve
|
||||||
|
comment: 疑似翻译腔。这个地方不太好译。
|
||||||
|
time: '2024-04-16T02:03:55.931Z'
|
||||||
|
all_jobs_have_been_cleared:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: hwThsx7VP-THpRXov2MB6
|
||||||
|
type: comment
|
||||||
|
comment: 要不要把“清除”改为“完成”?
|
||||||
|
time: '2024-04-16T10:56:22.929Z'
|
||||||
|
archive_info:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: pW79_SMSNiOyRj94kdSZO
|
||||||
|
type: comment
|
||||||
|
comment: 不太通顺。“位置”是否要加定语修饰?
|
||||||
|
time: '2024-04-16T11:03:10.218Z'
|
||||||
|
changelog_page_description:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: JN3YruMypxX5wuaMjD8Hu
|
||||||
|
type: comment
|
||||||
|
comment: 口语化显得更自然些。
|
||||||
|
time: '2024-04-16T11:05:27.478Z'
|
||||||
|
clouds:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: ebAW-cnfA4llVgee6CRmF
|
||||||
|
type: comment
|
||||||
|
comment: 一个字太少。
|
||||||
|
time: '2024-04-16T11:06:06.594Z'
|
||||||
|
coordinates:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: HJLIcCmrHV1ZwCsAJOSiS
|
||||||
|
type: comment
|
||||||
|
comment: 有可能应该改成“地理坐标”。
|
||||||
|
time: '2024-04-16T11:07:21.331Z'
|
||||||
|
create_library_description:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: N01f9vhjfYidHDnkhVV4o
|
||||||
|
type: comment
|
||||||
|
comment: >-
|
||||||
|
“libraries are
|
||||||
|
databases”这一句并不容易翻译,这里把英文原文放上去的方式我觉得并不妥当,但是我想不到更好的译法了。定语往后放到谓语的位置。同时添加必要的助词。
|
||||||
|
time: '2024-04-16T11:13:48.568Z'
|
||||||
|
create_new_library_description:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: Wb89DhKwsCB9vGBDUIgsj
|
||||||
|
type: comment
|
||||||
|
comment: 见“create_library_description”。
|
||||||
|
time: '2024-04-16T11:14:21.837Z'
|
||||||
|
creating_your_library:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: 6q9xmFoeVizgSTBbBey9O
|
||||||
|
type: comment
|
||||||
|
comment: “您的库”是典型的翻译腔。
|
||||||
|
time: '2024-04-16T11:15:52.949Z'
|
||||||
|
delete_warning:
|
||||||
|
locales:
|
||||||
|
zh-CN:
|
||||||
|
comments:
|
||||||
|
- user:
|
||||||
|
name: Heavysnowjakarta
|
||||||
|
email: heavysnowjakarta@gmail.com
|
||||||
|
id: 5oa5lvp8PkJDRceIenfne
|
||||||
|
type: comment
|
||||||
|
comment: 我不确定 `{{type}}` 是中文还是英文。如果是英文,前面应该加空格。
|
||||||
|
time: '2024-04-16T11:24:52.250Z'
|
16
.vscode/settings.json
vendored
16
.vscode/settings.json
vendored
|
@ -88,5 +88,19 @@
|
||||||
"rust-analyzer.linkedProjects": [],
|
"rust-analyzer.linkedProjects": [],
|
||||||
"rust-analyzer.cargo.extraEnv": {},
|
"rust-analyzer.cargo.extraEnv": {},
|
||||||
"rust-analyzer.check.targets": null,
|
"rust-analyzer.check.targets": null,
|
||||||
"rust-analyzer.showUnlinkedFileNotification": false
|
"rust-analyzer.showUnlinkedFileNotification": false,
|
||||||
|
"i18n-ally.localesPaths": [
|
||||||
|
"interface/locales",
|
||||||
|
"apps/mobile/ios/Pods/RCT-Folly/folly/lang",
|
||||||
|
"apps/mobile/ios/Pods/boost/boost/locale",
|
||||||
|
"apps/mobile/ios/Pods/boost/boost/predef/language"
|
||||||
|
],
|
||||||
|
"i18n-ally.enabledParsers": ["ts", "json"],
|
||||||
|
"i18n-ally.sortKeys": true,
|
||||||
|
"i18n-ally.namespace": true,
|
||||||
|
"i18n-ally.pathMatcher": "{locale}/common.json",
|
||||||
|
"i18n-ally.enabledFrameworks": ["react"],
|
||||||
|
"i18n-ally.keystyle": "flat",
|
||||||
|
// You need to add this to your locale settings file "i18n-ally.translate.google.apiKey": "xxx"
|
||||||
|
"i18n-ally.translate.engines": ["google"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,6 +58,13 @@ To quickly run only the desktop app after `prep`, you can use:
|
||||||
Also, the react-devtools can be launched using `pnpm dlx react-devtools`.
|
Also, the react-devtools can be launched using `pnpm dlx react-devtools`.
|
||||||
However, it must be executed before starting the desktop app for it to connect.
|
However, it must be executed before starting the desktop app for it to connect.
|
||||||
|
|
||||||
|
You can download a bundle with sample files to test the app by running:
|
||||||
|
|
||||||
|
- `pnpm test-data`
|
||||||
|
|
||||||
|
Only for Linux and macOS (Requires curl and tar).
|
||||||
|
The test files will be located in a directory called `test-data` in the root of the spacedrive repo.
|
||||||
|
|
||||||
To run the web app:
|
To run the web app:
|
||||||
|
|
||||||
- `pnpm dev:web`
|
- `pnpm dev:web`
|
||||||
|
@ -68,15 +75,23 @@ You can launch these individually if you'd prefer:
|
||||||
- `cargo run -p sd-server` (server)
|
- `cargo run -p sd-server` (server)
|
||||||
- `pnpm web dev` (web interface)
|
- `pnpm web dev` (web interface)
|
||||||
|
|
||||||
|
To run the e2e tests for the web app:
|
||||||
|
|
||||||
|
- `pnpm web test:e2e`
|
||||||
|
|
||||||
|
If you are developing a new test, you can execute Cypress in interactive mode with:
|
||||||
|
|
||||||
|
- `pnpm web test:interactive`
|
||||||
|
|
||||||
To run the landing page:
|
To run the landing page:
|
||||||
|
|
||||||
- `pnpm landing dev`
|
- `pnpm landing dev`
|
||||||
|
|
||||||
If you encounter any issues, ensure that you are using the following versions of Rust, Node and Pnpm:
|
If you encounter any issues, ensure that you are using the following versions of Rust, Node and Pnpm:
|
||||||
|
|
||||||
- Rust version: **1.75.0**
|
- Rust version: **1.75**
|
||||||
- Node version: **18.17**
|
- Node version: **18.18**
|
||||||
- Pnpm version: **8.0.0**
|
- Pnpm version: **9.0.6**
|
||||||
|
|
||||||
After cleaning out your build artifacts using `pnpm clean`, `git clean`, or `cargo clean`, it is necessary to re-run the `setup-system` script.
|
After cleaning out your build artifacts using `pnpm clean`, `git clean`, or `cargo clean`, it is necessary to re-run the `setup-system` script.
|
||||||
|
|
||||||
|
|
199
Cargo.lock
generated
199
Cargo.lock
generated
|
@ -1106,15 +1106,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "bincode"
|
|
||||||
version = "1.3.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
|
|
||||||
dependencies = [
|
|
||||||
"serde",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bincode"
|
name = "bincode"
|
||||||
version = "2.0.0-rc.3"
|
version = "2.0.0-rc.3"
|
||||||
|
@ -6463,7 +6454,9 @@ version = "0.10.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"phf_macros 0.10.0",
|
||||||
"phf_shared 0.10.0",
|
"phf_shared 0.10.0",
|
||||||
|
"proc-macro-hack",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -6540,6 +6533,20 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_macros"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator 0.10.0",
|
||||||
|
"phf_shared 0.10.0",
|
||||||
|
"proc-macro-hack",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 1.0.109",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "phf_macros"
|
name = "phf_macros"
|
||||||
version = "0.11.2"
|
version = "0.11.2"
|
||||||
|
@ -8124,8 +8131,9 @@ dependencies = [
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rmp-serde",
|
"rmp-serde",
|
||||||
"rmpv",
|
"rmpv",
|
||||||
|
"sd-core-file-path-helper",
|
||||||
|
"sd-core-prisma-helpers",
|
||||||
"sd-core-sync",
|
"sd-core-sync",
|
||||||
"sd-file-path-helper",
|
|
||||||
"sd-prisma",
|
"sd-prisma",
|
||||||
"sd-sync",
|
"sd-sync",
|
||||||
"sd-utils",
|
"sd-utils",
|
||||||
|
@ -8179,7 +8187,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sd-core"
|
name = "sd-core"
|
||||||
version = "0.2.5"
|
version = "0.2.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aovec",
|
"aovec",
|
||||||
"async-channel",
|
"async-channel",
|
||||||
|
@ -8229,11 +8237,14 @@ dependencies = [
|
||||||
"sd-ai",
|
"sd-ai",
|
||||||
"sd-cache",
|
"sd-cache",
|
||||||
"sd-cloud-api",
|
"sd-cloud-api",
|
||||||
|
"sd-core-file-path-helper",
|
||||||
|
"sd-core-heavy-lifting",
|
||||||
|
"sd-core-indexer-rules",
|
||||||
|
"sd-core-prisma-helpers",
|
||||||
"sd-core-sync",
|
"sd-core-sync",
|
||||||
"sd-crypto",
|
"sd-crypto",
|
||||||
"sd-ffmpeg",
|
"sd-ffmpeg",
|
||||||
"sd-file-ext",
|
"sd-file-ext",
|
||||||
"sd-file-path-helper",
|
|
||||||
"sd-images",
|
"sd-images",
|
||||||
"sd-media-metadata",
|
"sd-media-metadata",
|
||||||
"sd-p2p",
|
"sd-p2p",
|
||||||
|
@ -8265,10 +8276,99 @@ dependencies = [
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"tracing-test",
|
"tracing-test",
|
||||||
|
"trash",
|
||||||
"uuid",
|
"uuid",
|
||||||
"webp",
|
"webp",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sd-core-file-path-helper"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"prisma-client-rust",
|
||||||
|
"regex",
|
||||||
|
"sd-core-prisma-helpers",
|
||||||
|
"sd-prisma",
|
||||||
|
"sd-utils",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sd-core-heavy-lifting"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"async-channel",
|
||||||
|
"async-trait",
|
||||||
|
"blake3",
|
||||||
|
"chrono",
|
||||||
|
"futures",
|
||||||
|
"futures-concurrency",
|
||||||
|
"globset",
|
||||||
|
"itertools 0.12.0",
|
||||||
|
"lending-stream",
|
||||||
|
"prisma-client-rust",
|
||||||
|
"rmp-serde",
|
||||||
|
"rmpv",
|
||||||
|
"rspc",
|
||||||
|
"sd-core-file-path-helper",
|
||||||
|
"sd-core-indexer-rules",
|
||||||
|
"sd-core-prisma-helpers",
|
||||||
|
"sd-core-sync",
|
||||||
|
"sd-file-ext",
|
||||||
|
"sd-prisma",
|
||||||
|
"sd-sync",
|
||||||
|
"sd-task-system",
|
||||||
|
"sd-utils",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"specta",
|
||||||
|
"static_assertions",
|
||||||
|
"strum",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
"tracing",
|
||||||
|
"tracing-test",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sd-core-indexer-rules"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"futures-concurrency",
|
||||||
|
"globset",
|
||||||
|
"prisma-client-rust",
|
||||||
|
"rmp-serde",
|
||||||
|
"rspc",
|
||||||
|
"sd-prisma",
|
||||||
|
"sd-utils",
|
||||||
|
"serde",
|
||||||
|
"specta",
|
||||||
|
"tempfile",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sd-core-prisma-helpers"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"prisma-client-rust",
|
||||||
|
"sd-cache",
|
||||||
|
"sd-prisma",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sd-core-sync"
|
name = "sd-core-sync"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -8295,7 +8395,7 @@ dependencies = [
|
||||||
"aes-gcm-siv",
|
"aes-gcm-siv",
|
||||||
"argon2",
|
"argon2",
|
||||||
"balloon-hash",
|
"balloon-hash",
|
||||||
"bincode 2.0.0-rc.3",
|
"bincode",
|
||||||
"blake3",
|
"blake3",
|
||||||
"chacha20poly1305",
|
"chacha20poly1305",
|
||||||
"cmov",
|
"cmov",
|
||||||
|
@ -8336,7 +8436,7 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sd-desktop"
|
name = "sd-desktop"
|
||||||
version = "0.2.5"
|
version = "0.2.13"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
"directories 5.0.1",
|
"directories 5.0.1",
|
||||||
|
@ -8357,7 +8457,6 @@ dependencies = [
|
||||||
"specta",
|
"specta",
|
||||||
"tauri",
|
"tauri",
|
||||||
"tauri-build",
|
"tauri-build",
|
||||||
"tauri-plugin-window-state",
|
|
||||||
"tauri-specta",
|
"tauri-specta",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -8424,27 +8523,11 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "sd-file-path-helper"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"prisma-client-rust",
|
|
||||||
"regex",
|
|
||||||
"sd-prisma",
|
|
||||||
"sd-utils",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tracing",
|
|
||||||
"winapi-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sd-images"
|
name = "sd-images"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bincode 2.0.0-rc.3",
|
"bincode",
|
||||||
"image",
|
"image",
|
||||||
"libheif-rs",
|
"libheif-rs",
|
||||||
"libheif-sys",
|
"libheif-sys",
|
||||||
|
@ -8601,11 +8684,13 @@ name = "sd-server"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"base64 0.21.7",
|
||||||
"http",
|
"http",
|
||||||
"include_dir",
|
"include_dir",
|
||||||
"mime_guess",
|
"mime_guess",
|
||||||
"rspc",
|
"rspc",
|
||||||
"sd-core",
|
"sd-core",
|
||||||
|
"secstr",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
@ -8703,6 +8788,15 @@ dependencies = [
|
||||||
"zbus",
|
"zbus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "secstr"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e04f657244f605c4cf38f6de5993e8bd050c8a303f86aeabff142d5c7c113e12"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.9.2"
|
version = "2.9.2"
|
||||||
|
@ -9460,6 +9554,7 @@ version = "0.25.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"phf 0.10.1",
|
||||||
"strum_macros",
|
"strum_macros",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -9817,21 +9912,6 @@ dependencies = [
|
||||||
"tauri-utils 1.5.1",
|
"tauri-utils 1.5.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tauri-plugin-window-state"
|
|
||||||
version = "0.1.1"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fa47eaa4047a7b51064caff32f0c6282e2c5adc6ceacdd493ecf1b01fa4b0eaa"
|
|
||||||
dependencies = [
|
|
||||||
"bincode 1.3.3",
|
|
||||||
"bitflags 2.4.1",
|
|
||||||
"log",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"tauri",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tauri-runtime"
|
name = "tauri-runtime"
|
||||||
version = "0.14.1"
|
version = "0.14.1"
|
||||||
|
@ -10456,6 +10536,22 @@ dependencies = [
|
||||||
"syn 1.0.109",
|
"syn 1.0.109",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "trash"
|
||||||
|
version = "4.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6a1a7a9a17d3b004898be42be29a4c18d5a4cf008b5cdf72d69b1945dfcb158a"
|
||||||
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"objc",
|
||||||
|
"once_cell",
|
||||||
|
"scopeguard",
|
||||||
|
"url",
|
||||||
|
"windows 0.44.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "treediff"
|
name = "treediff"
|
||||||
version = "4.0.2"
|
version = "4.0.2"
|
||||||
|
@ -11192,6 +11288,15 @@ dependencies = [
|
||||||
"windows_x86_64_msvc 0.39.0",
|
"windows_x86_64_msvc 0.39.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows"
|
||||||
|
version = "0.44.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets 0.42.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.48.0"
|
version = "0.48.0"
|
||||||
|
|
|
@ -59,9 +59,12 @@ chrono = "0.4.31"
|
||||||
clap = "4.4.7"
|
clap = "4.4.7"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
futures-concurrency = "7.4.3"
|
futures-concurrency = "7.4.3"
|
||||||
|
globset = "^0.4.13"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
http = "0.2.9"
|
http = "0.2.9"
|
||||||
image = "0.24.7"
|
image = "0.24.7"
|
||||||
|
itertools = "0.12.0"
|
||||||
|
lending-stream = "1.0.0"
|
||||||
normpath = "1.1.1"
|
normpath = "1.1.1"
|
||||||
once_cell = "1.18.0"
|
once_cell = "1.18.0"
|
||||||
pin-project-lite = "0.2.13"
|
pin-project-lite = "0.2.13"
|
||||||
|
@ -73,6 +76,7 @@ rmp-serde = "1.1.2"
|
||||||
rmpv = { version = "^1.0.1", features = ["with-serde"] }
|
rmpv = { version = "^1.0.1", features = ["with-serde"] }
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
static_assertions = "1.1.0"
|
||||||
strum = "0.25"
|
strum = "0.25"
|
||||||
strum_macros = "0.25"
|
strum_macros = "0.25"
|
||||||
tempfile = "3.8.1"
|
tempfile = "3.8.1"
|
||||||
|
@ -84,6 +88,9 @@ uhlc = "=0.5.2"
|
||||||
uuid = "1.5.0"
|
uuid = "1.5.0"
|
||||||
webp = "0.2.6"
|
webp = "0.2.6"
|
||||||
|
|
||||||
|
[workspace.dev-dependencies]
|
||||||
|
tracing-test = { version = "^0.2.4" }
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
# Proper IOS Support
|
# Proper IOS Support
|
||||||
if-watch = { git = "https://github.com/oscartbeaumont/if-watch.git", rev = "a92c17d3f85c1c6fb0afeeaf6c2b24d0b147e8c3" }
|
if-watch = { git = "https://github.com/oscartbeaumont/if-watch.git", rev = "a92c17d3f85c1c6fb0afeeaf6c2b24d0b147e8c3" }
|
||||||
|
|
|
@ -6,6 +6,7 @@ repository = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Spacedrive Sub-crates
|
||||||
sd-crypto = { path = "../../crates/crypto" }
|
sd-crypto = { path = "../../crates/crypto" }
|
||||||
|
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
[package]
|
[package]
|
||||||
name = "sd-desktop"
|
name = "sd-desktop"
|
||||||
version = "0.2.5"
|
version = "0.2.13"
|
||||||
description = "The universal file manager."
|
description = "The universal file manager."
|
||||||
authors = ["Spacedrive Technology Inc <support@spacedrive.com>"]
|
authors = ["Spacedrive Technology Inc <support@spacedrive.com>"]
|
||||||
default-run = "sd-desktop"
|
default-run = "sd-desktop"
|
||||||
|
@ -9,6 +9,7 @@ repository = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Spacedrive Sub-crates
|
||||||
sd-core = { path = "../../../core", features = [
|
sd-core = { path = "../../../core", features = [
|
||||||
"ffmpeg",
|
"ffmpeg",
|
||||||
"heif",
|
"heif",
|
||||||
|
@ -33,20 +34,19 @@ thiserror.workspace = true
|
||||||
|
|
||||||
opener = { version = "0.6.1", features = ["reveal"] }
|
opener = { version = "0.6.1", features = ["reveal"] }
|
||||||
tauri = { version = "=1.5.3", features = [
|
tauri = { version = "=1.5.3", features = [
|
||||||
"macos-private-api",
|
"macos-private-api",
|
||||||
"path-all",
|
"path-all",
|
||||||
"protocol-all",
|
"protocol-all",
|
||||||
"os-all",
|
"os-all",
|
||||||
"shell-all",
|
"shell-all",
|
||||||
"dialog-all",
|
"dialog-all",
|
||||||
"linux-protocol-headers",
|
"linux-protocol-headers",
|
||||||
"updater",
|
"updater",
|
||||||
"window-all",
|
"window-all",
|
||||||
"native-tls-vendored",
|
"native-tls-vendored",
|
||||||
"tracing",
|
"tracing",
|
||||||
] }
|
] }
|
||||||
directories = "5.0.1"
|
directories = "5.0.1"
|
||||||
tauri-plugin-window-state = "0.1.1"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
sd-desktop-linux = { path = "../crates/linux" }
|
sd-desktop-linux = { path = "../crates/linux" }
|
||||||
|
@ -63,6 +63,6 @@ webview2-com = "0.19.1"
|
||||||
tauri-build = "1.5.0"
|
tauri-build = "1.5.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["ai-models", "custom-protocol"]
|
default = ["custom-protocol"]
|
||||||
ai-models = ["sd-core/ai"]
|
ai-models = ["sd-core/ai"]
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
|
|
|
@ -7,6 +7,7 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
fs,
|
fs,
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
process::Command,
|
||||||
sync::{Arc, Mutex, PoisonError},
|
sync::{Arc, Mutex, PoisonError},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
@ -149,6 +150,47 @@ async fn open_logs_dir(node: tauri::State<'_, Arc<Node>>) -> Result<(), ()> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command(async)]
|
||||||
|
#[specta::specta]
|
||||||
|
async fn open_trash_in_os_explorer() -> Result<(), ()> {
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
{
|
||||||
|
let full_path = format!("{}/.Trash/", std::env::var("HOME").unwrap());
|
||||||
|
|
||||||
|
Command::new("open")
|
||||||
|
.arg(full_path)
|
||||||
|
.spawn()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
{
|
||||||
|
Command::new("explorer")
|
||||||
|
.arg("shell:RecycleBinFolder")
|
||||||
|
.spawn()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "linux")]
|
||||||
|
{
|
||||||
|
Command::new("xdg-open")
|
||||||
|
.arg("~/.local/share/Trash/")
|
||||||
|
.spawn()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?
|
||||||
|
.wait()
|
||||||
|
.map_err(|err| error!("Error opening trash: {err:#?}"))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)]
|
#[derive(Debug, Clone, Serialize, Deserialize, specta::Type, tauri_specta::Event)]
|
||||||
#[serde(tag = "type")]
|
#[serde(tag = "type")]
|
||||||
pub enum DragAndDropEvent {
|
pub enum DragAndDropEvent {
|
||||||
|
@ -218,6 +260,7 @@ async fn main() -> tauri::Result<()> {
|
||||||
reload_webview,
|
reload_webview,
|
||||||
set_menu_bar_item_state,
|
set_menu_bar_item_state,
|
||||||
request_fda_macos,
|
request_fda_macos,
|
||||||
|
open_trash_in_os_explorer,
|
||||||
file::open_file_paths,
|
file::open_file_paths,
|
||||||
file::open_ephemeral_files,
|
file::open_ephemeral_files,
|
||||||
file::get_file_path_open_with_apps,
|
file::get_file_path_open_with_apps,
|
||||||
|
@ -241,7 +284,7 @@ async fn main() -> tauri::Result<()> {
|
||||||
let file_drop_status = Arc::new(Mutex::new(DragAndDropState::default()));
|
let file_drop_status = Arc::new(Mutex::new(DragAndDropState::default()));
|
||||||
let app = app
|
let app = app
|
||||||
.plugin(updater::plugin())
|
.plugin(updater::plugin())
|
||||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
// .plugin(tauri_plugin_window_state::Builder::default().build())
|
||||||
.plugin(specta_builder)
|
.plugin(specta_builder)
|
||||||
.setup(move |app| {
|
.setup(move |app| {
|
||||||
let app = app.handle();
|
let app = app.handle();
|
||||||
|
|
|
@ -12,6 +12,8 @@
|
||||||
"macOSPrivateApi": true,
|
"macOSPrivateApi": true,
|
||||||
"bundle": {
|
"bundle": {
|
||||||
"active": true,
|
"active": true,
|
||||||
|
"publisher": "Spacedrive Technology Inc.",
|
||||||
|
"category": "Productivity",
|
||||||
"targets": ["deb", "msi", "dmg", "updater"],
|
"targets": ["deb", "msi", "dmg", "updater"],
|
||||||
"identifier": "com.spacedrive.desktop",
|
"identifier": "com.spacedrive.desktop",
|
||||||
"icon": [
|
"icon": [
|
||||||
|
@ -24,7 +26,7 @@
|
||||||
"resources": {},
|
"resources": {},
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"copyright": "Spacedrive Technology Inc.",
|
"copyright": "Spacedrive Technology Inc.",
|
||||||
"shortDescription": "File explorer from the future.",
|
"shortDescription": "Spacedrive",
|
||||||
"longDescription": "Cross-platform universal file explorer, powered by an open-source virtual distributed filesystem.",
|
"longDescription": "Cross-platform universal file explorer, powered by an open-source virtual distributed filesystem.",
|
||||||
"deb": {
|
"deb": {
|
||||||
"files": {
|
"files": {
|
||||||
|
|
|
@ -78,18 +78,28 @@ const cache = createCache();
|
||||||
|
|
||||||
const routes = createRoutes(platform, cache);
|
const routes = createRoutes(platform, cache);
|
||||||
|
|
||||||
|
type redirect = { pathname: string; search: string | undefined };
|
||||||
|
|
||||||
function AppInner() {
|
function AppInner() {
|
||||||
const [tabs, setTabs] = useState(() => [createTab()]);
|
const [tabs, setTabs] = useState(() => [createTab()]);
|
||||||
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
const [selectedTabIndex, setSelectedTabIndex] = useState(0);
|
||||||
|
|
||||||
const selectedTab = tabs[selectedTabIndex]!;
|
const selectedTab = tabs[selectedTabIndex]!;
|
||||||
|
|
||||||
function createTab() {
|
function createTab(redirect?: redirect) {
|
||||||
const history = createMemoryHistory();
|
const history = createMemoryHistory();
|
||||||
const router = createMemoryRouterWithHistory({ routes, history });
|
const router = createMemoryRouterWithHistory({ routes, history });
|
||||||
|
|
||||||
const id = Math.random().toString();
|
const id = Math.random().toString();
|
||||||
|
|
||||||
|
// for "Open in new tab"
|
||||||
|
if (redirect) {
|
||||||
|
router.navigate({
|
||||||
|
pathname: redirect.pathname,
|
||||||
|
search: redirect.search
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const dispose = router.subscribe((event) => {
|
const dispose = router.subscribe((event) => {
|
||||||
// we don't care about non-idle events as those are artifacts of form mutations + suspense
|
// we don't care about non-idle events as those are artifacts of form mutations + suspense
|
||||||
if (event.navigation.state !== 'idle') return;
|
if (event.navigation.state !== 'idle') return;
|
||||||
|
@ -165,13 +175,13 @@ function AppInner() {
|
||||||
tabIndex: selectedTabIndex,
|
tabIndex: selectedTabIndex,
|
||||||
setTabIndex: setSelectedTabIndex,
|
setTabIndex: setSelectedTabIndex,
|
||||||
tabs: tabs.map(({ router, title }) => ({ router, title })),
|
tabs: tabs.map(({ router, title }) => ({ router, title })),
|
||||||
createTab() {
|
createTab(redirect?: redirect) {
|
||||||
createTabPromise.current = createTabPromise.current.then(
|
createTabPromise.current = createTabPromise.current.then(
|
||||||
() =>
|
() =>
|
||||||
new Promise((res) => {
|
new Promise((res) => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
setTabs((tabs) => {
|
setTabs((tabs) => {
|
||||||
const newTab = createTab();
|
const newTab = createTab(redirect);
|
||||||
const newTabs = [...tabs, newTab];
|
const newTabs = [...tabs, newTab];
|
||||||
|
|
||||||
setSelectedTabIndex(newTabs.length - 1);
|
setSelectedTabIndex(newTabs.length - 1);
|
||||||
|
|
|
@ -41,6 +41,17 @@ export const commands = {
|
||||||
async requestFdaMacos(): Promise<null> {
|
async requestFdaMacos(): Promise<null> {
|
||||||
return await TAURI_INVOKE('plugin:tauri-specta|request_fda_macos');
|
return await TAURI_INVOKE('plugin:tauri-specta|request_fda_macos');
|
||||||
},
|
},
|
||||||
|
async openTrashInOsExplorer(): Promise<__Result__<null, null>> {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
status: 'ok',
|
||||||
|
data: await TAURI_INVOKE('plugin:tauri-specta|open_trash_in_os_explorer')
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof Error) throw e;
|
||||||
|
else return { status: 'error', error: e as any };
|
||||||
|
}
|
||||||
|
},
|
||||||
async openFilePaths(
|
async openFilePaths(
|
||||||
library: string,
|
library: string,
|
||||||
ids: number[]
|
ids: number[]
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
"@octokit/openapi-types": "^20.0.0",
|
"@octokit/openapi-types": "^20.0.0",
|
||||||
"@sd/config": "workspace:*",
|
"@sd/config": "workspace:*",
|
||||||
"@svgr/webpack": "^8.1.0",
|
"@svgr/webpack": "^8.1.0",
|
||||||
"@types/node": ">18.x",
|
"@types/node": ">18.18.x",
|
||||||
"@types/react": "^18.2.67",
|
"@types/react": "^18.2.67",
|
||||||
"@types/react-burger-menu": "^2.8.7",
|
"@types/react-burger-menu": "^2.8.7",
|
||||||
"@types/react-dom": "^18.2.22",
|
"@types/react-dom": "^18.2.22",
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function DockerDialog({
|
||||||
<Dialog.Root open={open} onOpenChange={setOpen}>
|
<Dialog.Root open={open} onOpenChange={setOpen}>
|
||||||
<Dialog.Portal>
|
<Dialog.Portal>
|
||||||
<Dialog.Overlay className="fixed inset-0 z-50 bg-app/80 backdrop-blur-sm radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-open:animate-in radix-state-open:fade-in-0" />
|
<Dialog.Overlay className="fixed inset-0 z-50 bg-app/80 backdrop-blur-sm radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-open:animate-in radix-state-open:fade-in-0" />
|
||||||
<Dialog.Content className="fixed left-[50%] top-[50%] z-50 w-[500px] translate-x-[-50%] translate-y-[-50%] overflow-hidden rounded-md border border-app-line bg-app shadow-lg outline-none duration-200 radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-closed:zoom-out-95 radix-state-closed:slide-out-to-left-1/2 radix-state-closed:slide-out-to-top-[48%] radix-state-open:animate-in radix-state-open:fade-in-0 radix-state-open:zoom-in-95 radix-state-open:slide-in-from-left-1/2 radix-state-open:slide-in-from-top-[48%]">
|
<Dialog.Content className="fixed left-1/2 top-1/2 z-50 w-[500px] translate-x-[-1/2] translate-y-[-1/2] overflow-hidden rounded-md border border-app-line bg-app shadow-lg outline-none duration-200 radix-state-closed:animate-out radix-state-closed:fade-out-0 radix-state-closed:zoom-out-95 radix-state-closed:slide-out-to-left-1/2 radix-state-closed:slide-out-to-top-[48%] radix-state-open:animate-in radix-state-open:fade-in-0 radix-state-open:zoom-in-95 radix-state-open:slide-in-from-left-1/2 radix-state-open:slide-in-from-top-[48%]">
|
||||||
<div className="p-3 pt-0">
|
<div className="p-3 pt-0">
|
||||||
<h2 className="py-2 text-center text-lg font-semibold text-ink">Docker</h2>
|
<h2 className="py-2 text-center text-lg font-semibold text-ink">Docker</h2>
|
||||||
{/* Link */}
|
{/* Link */}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export const platforms = {
|
||||||
name: 'Linux',
|
name: 'Linux',
|
||||||
os: 'linux',
|
os: 'linux',
|
||||||
icon: LinuxLogo,
|
icon: LinuxLogo,
|
||||||
version: 'AppImage',
|
version: 'deb',
|
||||||
links: [{ name: 'x86_64', arch: 'x86_64' }]
|
links: [{ name: 'x86_64', arch: 'x86_64' }]
|
||||||
},
|
},
|
||||||
docker: { name: 'Docker', icon: Docker },
|
docker: { name: 'Docker', icon: Docker },
|
||||||
|
@ -96,7 +96,7 @@ export function Platform({ platform, ...props }: ComponentProps<'a'> & PlatformP
|
||||||
<Tooltip label={platform.name}>
|
<Tooltip label={platform.name}>
|
||||||
<Outer {...props}>
|
<Outer {...props}>
|
||||||
<Icon
|
<Icon
|
||||||
className={`h-[24px] w-[24px] text-white ${
|
className={`size-[24px] text-white ${
|
||||||
platform.disabled ? 'opacity-20' : 'opacity-90'
|
platform.disabled ? 'opacity-20' : 'opacity-90'
|
||||||
}`}
|
}`}
|
||||||
weight="fill"
|
weight="fill"
|
||||||
|
|
|
@ -30,7 +30,7 @@ export async function Footer() {
|
||||||
/>
|
/>
|
||||||
<div className="m-auto grid min-h-64 max-w-[100rem] grid-cols-2 gap-6 p-8 pb-20 pt-10 text-white sm:grid-cols-2 lg:grid-cols-6">
|
<div className="m-auto grid min-h-64 max-w-[100rem] grid-cols-2 gap-6 p-8 pb-20 pt-10 text-white sm:grid-cols-2 lg:grid-cols-6">
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<Image alt="Spacedrive logo" src={Logo} className="mb-5 h-10 w-10" />
|
<Image alt="Spacedrive logo" src={Logo} className="mb-5 size-10" />
|
||||||
|
|
||||||
<h1 className="mb-1 text-xl font-bold">Spacedrive</h1>
|
<h1 className="mb-1 text-xl font-bold">Spacedrive</h1>
|
||||||
<p className="text-sm text-gray-350 opacity-50">
|
<p className="text-sm text-gray-350 opacity-50">
|
||||||
|
@ -38,31 +38,31 @@ export async function Footer() {
|
||||||
</p>
|
</p>
|
||||||
<div className="mb-10 mt-12 flex flex-row space-x-3">
|
<div className="mb-10 mt-12 flex flex-row space-x-3">
|
||||||
<FooterLink link="https://x.com/spacedriveapp">
|
<FooterLink link="https://x.com/spacedriveapp">
|
||||||
<Twitter className="h-6 w-6" />
|
<Twitter className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
<FooterLink aria-label="discord" link="https://discord.gg/gTaF2Z44f5">
|
<FooterLink aria-label="discord" link="https://discord.gg/gTaF2Z44f5">
|
||||||
<Discord className="h-6 w-6" />
|
<Discord className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
<FooterLink
|
<FooterLink
|
||||||
aria-label="instagram"
|
aria-label="instagram"
|
||||||
link="https://instagram.com/spacedriveapp"
|
link="https://instagram.com/spacedriveapp"
|
||||||
>
|
>
|
||||||
<Instagram className="h-6 w-6" />
|
<Instagram className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
<FooterLink aria-label="github" link="https://github.com/spacedriveapp">
|
<FooterLink aria-label="github" link="https://github.com/spacedriveapp">
|
||||||
<Github className="h-6 w-6" />
|
<Github className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
<FooterLink
|
<FooterLink
|
||||||
aria-label="open collective"
|
aria-label="open collective"
|
||||||
link="https://opencollective.com/spacedrive"
|
link="https://opencollective.com/spacedrive"
|
||||||
>
|
>
|
||||||
<Opencollective className="h-6 w-6" />
|
<Opencollective className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
<FooterLink
|
<FooterLink
|
||||||
aria-label="twitch stream"
|
aria-label="twitch stream"
|
||||||
link="https://twitch.tv/jamiepinelive"
|
link="https://twitch.tv/jamiepinelive"
|
||||||
>
|
>
|
||||||
<Twitch className="h-6 w-6" />
|
<Twitch className="size-6" />
|
||||||
</FooterLink>
|
</FooterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -141,8 +141,8 @@ export async function Footer() {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute top-0 flex h-1 w-full flex-row items-center justify-center opacity-100">
|
<div className="absolute top-0 flex h-1 w-full flex-row items-center justify-center opacity-100">
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -20,7 +20,7 @@ export function MobileDropdown() {
|
||||||
<Dropdown.Root
|
<Dropdown.Root
|
||||||
button={
|
button={
|
||||||
<Button aria-label="mobile-menu" className="hover:!bg-transparent" size="icon">
|
<Button aria-label="mobile-menu" className="hover:!bg-transparent" size="icon">
|
||||||
<DotsThreeVertical weight="bold" className="h-6 w-6 " />
|
<DotsThreeVertical weight="bold" className="size-6 " />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
className="right-4 top-2 block text-white lg:hidden"
|
className="right-4 top-2 block text-white lg:hidden"
|
||||||
|
|
|
@ -12,7 +12,7 @@ export function NavBar() {
|
||||||
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
|
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
|
||||||
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
|
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
|
||||||
<Link href="/" className="absolute flex flex-row items-center">
|
<Link href="/" className="absolute flex flex-row items-center">
|
||||||
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 h-8 w-8" />
|
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 size-8" />
|
||||||
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
|
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ export function NavBar() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Discord className="h-6 w-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
<Discord className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
aria-label="github"
|
aria-label="github"
|
||||||
|
@ -48,13 +48,13 @@ export function NavBar() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Github className="h-6 w-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
<Github className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
|
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,7 +24,7 @@ export function NewBanner(props: NewBannerProps) {
|
||||||
<Newspaper weight="fill" className="text-white " size={20} />
|
<Newspaper weight="fill" className="text-white " size={20} />
|
||||||
<p className="font-regular truncate text-white">{headline}</p>
|
<p className="font-regular truncate text-white">{headline}</p>
|
||||||
</div>
|
</div>
|
||||||
<div role="separator" className="h-22 mx-4 w-[1px] bg-zinc-700/70" />
|
<div role="separator" className="h-22 mx-4 w-px bg-zinc-700/70" />
|
||||||
<span className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600">
|
<span className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600">
|
||||||
{link} <span aria-hidden="true">→</span>
|
{link} <span aria-hidden="true">→</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -7,7 +7,7 @@ const tauriTarget = z.union([z.literal('linux'), z.literal('windows'), z.literal
|
||||||
const tauriArch = z.union([z.literal('x86_64'), z.literal('aarch64')]);
|
const tauriArch = z.union([z.literal('x86_64'), z.literal('aarch64')]);
|
||||||
|
|
||||||
const extensions = {
|
const extensions = {
|
||||||
linux: 'AppImage',
|
linux: 'deb',
|
||||||
windows: 'msi',
|
windows: 'msi',
|
||||||
darwin: 'dmg'
|
darwin: 'dmg'
|
||||||
} as const satisfies Record<z.infer<typeof tauriTarget>, string>;
|
} as const satisfies Record<z.infer<typeof tauriTarget>, string>;
|
||||||
|
|
|
@ -13,7 +13,7 @@ export function Breadcrumbs() {
|
||||||
<div className="flex flex-row items-center gap-1">
|
<div className="flex flex-row items-center gap-1">
|
||||||
{slug.map((item, index) => (
|
{slug.map((item, index) => (
|
||||||
<Fragment key={index}>
|
<Fragment key={index}>
|
||||||
{index > 0 && <CaretRight className="h-4 w-4" />}
|
{index > 0 && <CaretRight className="size-4" />}
|
||||||
<span className="px-1 text-sm">{toTitleCase(item)}</span>
|
<span className="px-1 text-sm">{toTitleCase(item)}</span>
|
||||||
</Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
|
|
|
@ -36,7 +36,7 @@ export function OpenMobileSidebarButton() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className="ml-1 !border-none !px-2" onClick={() => menu.setOpen((o) => !o)}>
|
<Button className="ml-1 !border-none !px-2" onClick={() => menu.setOpen((o) => !o)}>
|
||||||
<List weight="bold" className="h-6 w-6" />
|
<List weight="bold" className="size-6" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ export function MobileSidebarWrapper({ children }: PropsWithChildren) {
|
||||||
onClick={() => menu.setOpen((o) => !o)}
|
onClick={() => menu.setOpen((o) => !o)}
|
||||||
className="-ml-0.5 mb-3 !border-none !px-1"
|
className="-ml-0.5 mb-3 !border-none !px-1"
|
||||||
>
|
>
|
||||||
<X weight="bold" className="h-6 w-6" />
|
<X weight="bold" className="size-6" />
|
||||||
</Button>
|
</Button>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -58,7 +58,7 @@ export async function Sidebar() {
|
||||||
slug={section.slug}
|
slug={section.slug}
|
||||||
>
|
>
|
||||||
<div className="mr-4 rounded-lg border-t border-gray-400/20 bg-gray-500 p-1">
|
<div className="mr-4 rounded-lg border-t border-gray-400/20 bg-gray-500 p-1">
|
||||||
<Icon weight="bold" className="h-4 w-4 text-white opacity-80" />
|
<Icon weight="bold" className="size-4 text-white opacity-80" />
|
||||||
</div>
|
</div>
|
||||||
{toTitleCase(section.slug)}
|
{toTitleCase(section.slug)}
|
||||||
</SectionLink>
|
</SectionLink>
|
||||||
|
|
|
@ -16,7 +16,7 @@ export default function NotFound() {
|
||||||
<Markdown classNames="flex w-full justify-center">
|
<Markdown classNames="flex w-full justify-center">
|
||||||
<div className="m-auto flex flex-col items-center ">
|
<div className="m-auto flex flex-col items-center ">
|
||||||
<div className="h-32" />
|
<div className="h-32" />
|
||||||
<SmileyXEyes className="mb-3 h-44 w-44" />
|
<SmileyXEyes className="mb-3 size-44" />
|
||||||
<h1 className="mb-2 text-center">
|
<h1 className="mb-2 text-center">
|
||||||
In the quantum realm this page potentially exists.
|
In the quantum realm this page potentially exists.
|
||||||
</h1>
|
</h1>
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ArrowUp } from '@phosphor-icons/react/dist/ssr';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import CyclingImage from '~/components/CyclingImage';
|
import CyclingImage from '~/components/CyclingImage';
|
||||||
import { toTitleCase } from '~/utils/util';
|
import { toTitleCase } from '~/utils/util';
|
||||||
|
@ -77,34 +78,40 @@ export default async function Page() {
|
||||||
src="/images/app/gradient.webp"
|
src="/images/app/gradient.webp"
|
||||||
/>
|
/>
|
||||||
<div className="relative m-auto mt-10 flex w-full max-w-7xl overflow-hidden rounded-lg transition-transform duration-700 ease-in-out hover:-translate-y-4 hover:scale-[1.02] md:mt-0">
|
<div className="relative m-auto mt-10 flex w-full max-w-7xl overflow-hidden rounded-lg transition-transform duration-700 ease-in-out hover:-translate-y-4 hover:scale-[1.02] md:mt-0">
|
||||||
<div className="z-30 flex w-full rounded-lg border-t border-app-line/50 backdrop-blur">
|
<div className="flex flex-col items-center justify-center">
|
||||||
<CyclingImage
|
<div className="z-30 flex w-full rounded-lg border-t border-app-line/50 backdrop-blur">
|
||||||
loading="eager"
|
<CyclingImage
|
||||||
width={1278}
|
loading="eager"
|
||||||
height={626}
|
width={1278}
|
||||||
alt="spacedrive app"
|
height={626}
|
||||||
className="rounded-lg"
|
alt="spacedrive app"
|
||||||
images={[
|
className="rounded-lg"
|
||||||
'/images/app/1.webp',
|
images={[
|
||||||
'/images/app/2.webp',
|
'/images/app/1.webp',
|
||||||
'/images/app/3.webp',
|
'/images/app/2.webp',
|
||||||
'/images/app/4.webp',
|
'/images/app/3.webp',
|
||||||
'/images/app/5.webp',
|
'/images/app/4.webp',
|
||||||
'/images/app/10.webp',
|
'/images/app/5.webp',
|
||||||
'/images/app/6.webp',
|
'/images/app/10.webp',
|
||||||
'/images/app/7.webp',
|
'/images/app/6.webp',
|
||||||
'/images/app/8.webp',
|
'/images/app/7.webp',
|
||||||
'/images/app/9.webp'
|
'/images/app/8.webp',
|
||||||
]}
|
'/images/app/9.webp'
|
||||||
/>
|
]}
|
||||||
<Image
|
/>
|
||||||
loading="eager"
|
<Image
|
||||||
className="pointer-events-none absolute opacity-100 transition-opacity duration-1000 ease-in-out hover:opacity-0 md:w-auto"
|
loading="eager"
|
||||||
width={2278}
|
className="pointer-events-none absolute opacity-100 transition-opacity duration-1000 ease-in-out hover:opacity-0 md:w-auto"
|
||||||
height={626}
|
width={2278}
|
||||||
alt="l"
|
height={626}
|
||||||
src="/images/app/gradient-overlay.png"
|
alt="l"
|
||||||
/>
|
src="/images/app/gradient-overlay.png"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<ArrowUp className="invisible size-7 pt-2 text-white/40 md:visible" />
|
||||||
|
<p className="invisible pt-2 text-xs text-white/40 md:visible">
|
||||||
|
Hover to see more
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -103,14 +103,14 @@ const PackageCard = ({ features, name, price, toggle, subTitle }: Props) => {
|
||||||
<p className="text-md mb-4 uppercase text-[#A7ADD2]">{name}</p>
|
<p className="text-md mb-4 uppercase text-[#A7ADD2]">{name}</p>
|
||||||
{price && (
|
{price && (
|
||||||
<>
|
<>
|
||||||
<p className="text-2xl font-bold leading-[1] text-white">
|
<p className="text-2xl font-bold leading-none text-white">
|
||||||
${toggle ? price.yearly : price.monthly}
|
${toggle ? price.yearly : price.monthly}
|
||||||
</p>
|
</p>
|
||||||
<p className="text-md text-[#A7ADD2]">per {duration}</p>
|
<p className="text-md text-[#A7ADD2]">per {duration}</p>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{subTitle && (
|
{subTitle && (
|
||||||
<p className="text-2xl font-bold leading-[1] text-white">{subTitle}</p>
|
<p className="text-2xl font-bold leading-none text-white">{subTitle}</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -128,7 +128,7 @@ const PackageCard = ({ features, name, price, toggle, subTitle }: Props) => {
|
||||||
)}
|
)}
|
||||||
{features.map((feature, index) => (
|
{features.map((feature, index) => (
|
||||||
<div key={index} className="flex items-center justify-center gap-2.5">
|
<div key={index} className="flex items-center justify-center gap-2.5">
|
||||||
<div className="flex h-5 w-5 items-center justify-center rounded-full border border-[#353252] bg-[#2A2741]">
|
<div className="flex size-5 items-center justify-center rounded-full border border-[#353252] bg-[#2A2741]">
|
||||||
<Check weight="bold" size={12} color="white" />
|
<Check weight="bold" size={12} color="white" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm text-white">{feature}</p>
|
<p className="text-sm text-white">{feature}</p>
|
||||||
|
|
|
@ -170,6 +170,11 @@ export const items = [
|
||||||
description:
|
description:
|
||||||
'Automatically save versions of files when they change, with a timeline view and the ability to restore.'
|
'Automatically save versions of files when they change, with a timeline view and the ability to restore.'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'Local Server Protection',
|
||||||
|
description:
|
||||||
|
"Protect local instances of Spacedrive's server from other clients on your network."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
when: '0.5 Beta',
|
when: '0.5 Beta',
|
||||||
subtext: 'June 2024',
|
subtext: 'June 2024',
|
||||||
|
|
|
@ -28,7 +28,7 @@ export default function Page() {
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<Fragment key={i}>
|
<Fragment key={i}>
|
||||||
{/* Using span so i can use the group-last-of-type selector */}
|
{/* Using span so i can use the group-last-of-type selector */}
|
||||||
<span className="group flex max-w-[10rem] items-start justify-end gap-4 first:items-start">
|
<span className="group flex max-w-40 items-start justify-end gap-4 first:items-start">
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'flex flex-col items-end',
|
'flex flex-col items-end',
|
||||||
|
|
|
@ -76,32 +76,32 @@ export function TeamMember(props: TeamMemberProps) {
|
||||||
<div className="mt-3 flex flex-row space-x-2">
|
<div className="mt-3 flex flex-row space-x-2">
|
||||||
{props.socials?.twitter && (
|
{props.socials?.twitter && (
|
||||||
<Link href={props.socials.twitter}>
|
<Link href={props.socials.twitter}>
|
||||||
<Twitter className="h-[20px] w-[20px]" />
|
<Twitter className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.socials?.github && (
|
{props.socials?.github && (
|
||||||
<Link href={props.socials.github}>
|
<Link href={props.socials.github}>
|
||||||
<Github className="h-[20px] w-[20px]" />
|
<Github className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.socials?.gitlab && (
|
{props.socials?.gitlab && (
|
||||||
<Link href={props.socials.gitlab}>
|
<Link href={props.socials.gitlab}>
|
||||||
<Gitlab className="h-[20px] w-[20px]" />
|
<Gitlab className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.socials?.twitch && (
|
{props.socials?.twitch && (
|
||||||
<Link href={props.socials.twitch}>
|
<Link href={props.socials.twitch}>
|
||||||
<Twitch className="h-[20px] w-[20px]" />
|
<Twitch className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.socials?.dribbble && (
|
{props.socials?.dribbble && (
|
||||||
<Link href={props.socials.dribbble}>
|
<Link href={props.socials.dribbble}>
|
||||||
<Dribbble className="h-[20px] w-[20px]" />
|
<Dribbble className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{props.socials?.website && (
|
{props.socials?.website && (
|
||||||
<Link href={props.socials.website}>
|
<Link href={props.socials.website}>
|
||||||
<Website className="h-[20px] w-[20px]" />
|
<Website className="size-[20px]" />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -56,7 +56,7 @@ const BentoBoxes = () => {
|
||||||
lg:grid lg:grid-cols-6"
|
lg:grid lg:grid-cols-6"
|
||||||
>
|
>
|
||||||
<BentoBox colSpan={4} className="p-6" bgUrl="images/bento/encrypt-bg.webp">
|
<BentoBox colSpan={4} className="p-6" bgUrl="images/bento/encrypt-bg.webp">
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
<div className="relative z-20">
|
<div className="relative z-20">
|
||||||
<Heading>Encryption</Heading>
|
<Heading>Encryption</Heading>
|
||||||
<Text className="mx-auto max-w-[417px]">
|
<Text className="mx-auto max-w-[417px]">
|
||||||
|
@ -64,7 +64,7 @@ const BentoBoxes = () => {
|
||||||
unauthorized access and guaranteed protection.
|
unauthorized access and guaranteed protection.
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex h-[80%] w-auto items-start justify-center">
|
<div className="flex h-4/5 w-auto items-start justify-center">
|
||||||
<Image
|
<Image
|
||||||
className="mx-auto"
|
className="mx-auto"
|
||||||
alt="Encryption"
|
alt="Encryption"
|
||||||
|
@ -77,7 +77,7 @@ const BentoBoxes = () => {
|
||||||
</div>
|
</div>
|
||||||
</BentoBox>
|
</BentoBox>
|
||||||
<BentoBox colSpan={2} className="p-6">
|
<BentoBox colSpan={2} className="p-6">
|
||||||
<div className="flex h-[75%] w-auto items-center justify-center">
|
<div className="flex h-3/4 w-auto items-center justify-center">
|
||||||
<Image
|
<Image
|
||||||
className="mx-auto mt-3 brightness-125"
|
className="mx-auto mt-3 brightness-125"
|
||||||
alt="Powerful tags"
|
alt="Powerful tags"
|
||||||
|
@ -88,9 +88,9 @@ const BentoBoxes = () => {
|
||||||
src="/images/bento/tags.webp"
|
src="/images/bento/tags.webp"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
|
|
||||||
<div className="relative z-[40] mt-2 md:mt-7">
|
<div className="relative z-40 mt-2 md:mt-7">
|
||||||
<Heading>Powerful tags</Heading>
|
<Heading>Powerful tags</Heading>
|
||||||
<Text>
|
<Text>
|
||||||
Create and apply tags to your files and folders, and instantly locate
|
Create and apply tags to your files and folders, and instantly locate
|
||||||
|
@ -105,8 +105,8 @@ const BentoBoxes = () => {
|
||||||
Easily find your files and folders through our search
|
Easily find your files and folders through our search
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
<div className="flex h-[80%] w-auto items-start justify-center">
|
<div className="flex h-4/5 w-auto items-start justify-center">
|
||||||
<Image
|
<Image
|
||||||
className="mx-auto brightness-110"
|
className="mx-auto brightness-110"
|
||||||
alt="Search"
|
alt="Search"
|
||||||
|
@ -119,8 +119,8 @@ const BentoBoxes = () => {
|
||||||
</div>
|
</div>
|
||||||
</BentoBox>
|
</BentoBox>
|
||||||
<BentoBox colSpan={2} className="p-6">
|
<BentoBox colSpan={2} className="p-6">
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
<div className="flex h-[80%] w-auto items-center justify-center">
|
<div className="flex h-4/5 w-auto items-center justify-center">
|
||||||
<Image
|
<Image
|
||||||
className="mx-auto brightness-125"
|
className="mx-auto brightness-125"
|
||||||
alt="Library"
|
alt="Library"
|
||||||
|
@ -143,8 +143,8 @@ const BentoBoxes = () => {
|
||||||
Send files to other devices quickly and easily
|
Send files to other devices quickly and easily
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
<div className="flex h-[80%] w-auto items-center justify-center">
|
<div className="flex h-4/5 w-auto items-center justify-center">
|
||||||
<div
|
<div
|
||||||
style={
|
style={
|
||||||
{
|
{
|
||||||
|
@ -211,7 +211,7 @@ const BentoBoxes = () => {
|
||||||
Windows, macOS, Linux, iOS, Android, and the web. Spacedrive is everywhere.
|
Windows, macOS, Linux, iOS, Android, and the web. Spacedrive is everywhere.
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 h-full w-full" />
|
<div className="bento-radial-gradient-fade absolute right-0 top-0 z-20 size-full" />
|
||||||
</BentoBox>
|
</BentoBox>
|
||||||
</MagicContainer>
|
</MagicContainer>
|
||||||
);
|
);
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default function NavBar() {
|
||||||
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
|
<div className="navbar-blur fixed z-[55] h-16 w-full !bg-black/10 px-2 transition">
|
||||||
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
|
<div className="relative m-auto flex h-full max-w-[100rem] items-center p-5">
|
||||||
<Link href="/" className="absolute flex flex-row items-center">
|
<Link href="/" className="absolute flex flex-row items-center">
|
||||||
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 h-8 w-8" />
|
<Image alt="Spacedrive logo" src={Logo} className="z-30 mr-3 size-8" />
|
||||||
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
|
<h3 className="text-xl font-bold text-white">Spacedrive</h3>
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
|
@ -88,7 +88,7 @@ export default function NavBar() {
|
||||||
className="ml-[140px] hover:!bg-transparent"
|
className="ml-[140px] hover:!bg-transparent"
|
||||||
size="icon"
|
size="icon"
|
||||||
>
|
>
|
||||||
<DotsThreeVertical weight="bold" className="h-6 w-6 " />
|
<DotsThreeVertical weight="bold" className="size-6 " />
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
className="right-4 top-2 block h-6 w-44 text-white lg:hidden"
|
className="right-4 top-2 block h-6 w-44 text-white lg:hidden"
|
||||||
|
@ -145,7 +145,7 @@ export default function NavBar() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Discord className="h-6 w-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
<Discord className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
aria-label="github"
|
aria-label="github"
|
||||||
|
@ -153,13 +153,13 @@ export default function NavBar() {
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noreferrer"
|
rel="noreferrer"
|
||||||
>
|
>
|
||||||
<Github className="h-6 w-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
<Github className="size-6 text-white opacity-100 duration-300 hover:opacity-50" />
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
|
<div className="absolute bottom-0 flex h-1 w-full flex-row items-center justify-center pt-4 opacity-100">
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-r from-transparent to-white/10"></div>
|
||||||
<div className="h-[1px] w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
<div className="h-px w-1/2 bg-gradient-to-l from-transparent to-white/10"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -24,7 +24,7 @@ const NewBanner: React.FC<NewBannerProps> = (props) => {
|
||||||
<Newspaper weight="fill" className="text-white " size={20} />
|
<Newspaper weight="fill" className="text-white " size={20} />
|
||||||
<p className="font-regular truncate text-white">{headline}</p>
|
<p className="font-regular truncate text-white">{headline}</p>
|
||||||
</div>
|
</div>
|
||||||
<div role="separator" className="h-22 mx-4 w-[1px] bg-zinc-700/70" />
|
<div role="separator" className="h-22 mx-4 w-px bg-zinc-700/70" />
|
||||||
<Link
|
<Link
|
||||||
href={href}
|
href={href}
|
||||||
className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600"
|
className="font-regular shrink-0 bg-gradient-to-r from-violet-400 to-fuchsia-400 bg-clip-text text-transparent decoration-primary-600"
|
||||||
|
|
|
@ -14,7 +14,7 @@ const WormHole = () => {
|
||||||
className="absolute top-[-150px] w-full max-w-[450px] rotate-[300deg] sm:top-[-200px]
|
className="absolute top-[-150px] w-full max-w-[450px] rotate-[300deg] sm:top-[-200px]
|
||||||
sm:max-w-[500px] md:top-[-200px] lg:top-auto lg:mr-[250px] lg:max-w-full lg:rotate-0"
|
sm:max-w-[500px] md:top-[-200px] lg:top-auto lg:mr-[250px] lg:max-w-full lg:rotate-0"
|
||||||
>
|
>
|
||||||
<div className="absolute left-[200px] top-[50px] z-10 h-full w-full">
|
<div className="absolute left-[200px] top-[50px] z-10 size-full">
|
||||||
<Image
|
<Image
|
||||||
width={30}
|
width={30}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -24,7 +24,7 @@ const WormHole = () => {
|
||||||
src="/images/icons/heart.svg"
|
src="/images/icons/heart.svg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-[200px] top-[50px] z-10 h-full w-full">
|
<div className="absolute left-[200px] top-[50px] z-10 size-full">
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -36,8 +36,8 @@ const WormHole = () => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute top-[-100px] z-10
|
className="absolute top-[-100px] z-10
|
||||||
h-full w-full
|
size-full sm:left-[200px]
|
||||||
sm:left-[200px] sm:top-[10px]"
|
sm:top-[10px]"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
|
@ -50,8 +50,8 @@ const WormHole = () => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute left-[120px] top-[-50px]
|
className="absolute left-[120px] top-[-50px]
|
||||||
z-10 h-full w-full
|
z-10 size-full sm:left-[200px]
|
||||||
sm:left-[200px] sm:top-[-10px]"
|
sm:top-[-10px]"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
|
@ -63,8 +63,8 @@ const WormHole = () => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute left-[200px] top-[350px] z-10 h-full
|
className="absolute left-[200px] top-[350px] z-10 size-full
|
||||||
w-full lg:left-[200px] lg:top-[300px]"
|
lg:left-[200px] lg:top-[300px]"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
|
@ -75,7 +75,7 @@ const WormHole = () => {
|
||||||
src="/images/icons/video.svg"
|
src="/images/icons/video.svg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-[200px] top-[150px] z-10 h-full w-full">
|
<div className="absolute left-[200px] top-[150px] z-10 size-full">
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -85,7 +85,7 @@ const WormHole = () => {
|
||||||
src="/images/icons/application.svg"
|
src="/images/icons/application.svg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-[120px] top-[50px] z-10 h-full w-full lg:left-[200px] lg:top-[120px]">
|
<div className="absolute left-[120px] top-[50px] z-10 size-full lg:left-[200px] lg:top-[120px]">
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -95,7 +95,7 @@ const WormHole = () => {
|
||||||
src="/images/icons/collection.svg"
|
src="/images/icons/collection.svg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-[120px] top-[50px] z-10 h-full w-full sm:left-[200px] sm:top-[300px] lg:left-[200px] lg:top-[420px]">
|
<div className="absolute left-[120px] top-[50px] z-10 size-full sm:left-[200px] sm:top-[300px] lg:left-[200px] lg:top-[420px]">
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -108,7 +108,7 @@ const WormHole = () => {
|
||||||
<div
|
<div
|
||||||
className="absolute
|
className="absolute
|
||||||
left-[60px] top-[-190px]
|
left-[60px] top-[-190px]
|
||||||
z-10 h-full w-full sm:left-[50px] sm:top-[50px] lg:left-[200px] lg:top-[490px]"
|
z-10 size-full sm:left-[50px] sm:top-[50px] lg:left-[200px] lg:top-[490px]"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
|
@ -121,7 +121,7 @@ const WormHole = () => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className="absolute left-[120px] top-[50px]
|
className="absolute left-[120px] top-[50px]
|
||||||
z-10 h-full w-full md:left-[200px] md:top-[350px]"
|
z-10 size-full md:left-[200px] md:top-[350px]"
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
|
@ -132,7 +132,7 @@ const WormHole = () => {
|
||||||
src="/images/icons/database.svg"
|
src="/images/icons/database.svg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="absolute left-[100px] top-[-200px] z-10 h-full w-full sm:left-[150px] sm:top-[50px]">
|
<div className="absolute left-[100px] top-[-200px] z-10 size-full sm:left-[150px] sm:top-[50px]">
|
||||||
<Image
|
<Image
|
||||||
width={40}
|
width={40}
|
||||||
height={45}
|
height={45}
|
||||||
|
@ -153,7 +153,7 @@ const WormHole = () => {
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'worm-hole-border-gradient relative top-[100px] z-[20] flex w-full max-w-[500px] flex-col rounded-lg',
|
'worm-hole-border-gradient relative top-[100px] z-20 flex w-full max-w-[500px] flex-col rounded-lg',
|
||||||
'items-center justify-center gap-2 bg-gradient-to-r from-[#080710]/0 to-[#080710]/50 p-8 backdrop-blur-sm'
|
'items-center justify-center gap-2 bg-gradient-to-r from-[#080710]/0 to-[#080710]/50 p-8 backdrop-blur-sm'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -21,7 +21,7 @@ export default function Notice({ text, type, title }: NoticeProps) {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-row items-center gap-x-1">
|
<div className="flex flex-row items-center gap-x-1">
|
||||||
<Icon className="my-0 h-5 w-5 text-white" />
|
<Icon className="my-0 size-5 text-white" />
|
||||||
<h5 className="m-0 text-sm font-bold uppercase text-white">{title || type}</h5>
|
<h5 className="m-0 text-sm font-bold uppercase text-white">{title || type}</h5>
|
||||||
</div>
|
</div>
|
||||||
<p className="mx-0 my-1 mb-0 text-white">
|
<p className="mx-0 my-1 mb-0 text-white">
|
||||||
|
|
38
apps/landing/src/components/mdx/Pre.tsx
Normal file
38
apps/landing/src/components/mdx/Pre.tsx
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import { Check } from '@phosphor-icons/react';
|
||||||
|
import { Copy } from '@phosphor-icons/react/dist/ssr';
|
||||||
|
import { FC, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
const Pre: FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
|
const textInput = useRef<HTMLDivElement | null>(null);
|
||||||
|
const [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
|
const onCopy = () => {
|
||||||
|
setCopied(true);
|
||||||
|
navigator.clipboard.writeText(textInput.current!.textContent ?? '');
|
||||||
|
setTimeout(() => {
|
||||||
|
setCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={textInput} className="relative">
|
||||||
|
<button
|
||||||
|
aria-label="Copy code"
|
||||||
|
type="button"
|
||||||
|
className="absolute right-2 top-2 z-10 rounded-md bg-app-box p-3 text-white/60 transition-colors duration-200 ease-in-out hover:bg-app-darkBox"
|
||||||
|
onClick={onCopy}
|
||||||
|
>
|
||||||
|
{copied ? (
|
||||||
|
<Check className="my-0 size-5 text-green-400/60" />
|
||||||
|
) : (
|
||||||
|
<Copy className="my-0 size-5 text-white" />
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
<pre className="language-container">{children}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Pre;
|
|
@ -3,6 +3,7 @@ import NextImage, { ImageProps } from 'next/image';
|
||||||
import { env } from '~/env';
|
import { env } from '~/env';
|
||||||
|
|
||||||
import Notice from './Notice';
|
import Notice from './Notice';
|
||||||
|
import Pre from './Pre';
|
||||||
import Video from './Video';
|
import Video from './Video';
|
||||||
|
|
||||||
const Image = (props: ImageProps) => (
|
const Image = (props: ImageProps) => (
|
||||||
|
@ -15,8 +16,9 @@ const Image = (props: ImageProps) => (
|
||||||
|
|
||||||
export const BlogMDXComponents = {
|
export const BlogMDXComponents = {
|
||||||
img: Image, // we remap 'img' to 'Image'
|
img: Image, // we remap 'img' to 'Image'
|
||||||
|
pre: Pre,
|
||||||
Image,
|
Image,
|
||||||
Video
|
Video
|
||||||
} as MDXComponents;
|
} as MDXComponents;
|
||||||
|
|
||||||
export const DocMDXComponents = { img: Image, Image, Notice, Video } as MDXComponents;
|
export const DocMDXComponents = { img: Image, Image, Notice, Video, pre: Pre } as MDXComponents;
|
||||||
|
|
|
@ -74,6 +74,13 @@ pre[class*='language-'] {
|
||||||
border-radius: 0.3em;
|
border-radius: 0.3em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pre.language-container {
|
||||||
|
padding: 1.1em;
|
||||||
|
margin: 0.5em 0;
|
||||||
|
overflow: auto;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
|
||||||
/* Inline code */
|
/* Inline code */
|
||||||
:not(pre) > code[class*='language-'] {
|
:not(pre) > code[class*='language-'] {
|
||||||
padding: 0.2em 0.3em;
|
padding: 0.2em 0.3em;
|
||||||
|
|
|
@ -22,6 +22,11 @@ module.exports = {
|
||||||
importNames: ['SafeAreaView'],
|
importNames: ['SafeAreaView'],
|
||||||
message: 'Import SafeAreaView from react-native-safe-area-context instead'
|
message: 'Import SafeAreaView from react-native-safe-area-context instead'
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'react-native',
|
||||||
|
importNames: ['Image', 'ImageProps', 'ImageBackground'],
|
||||||
|
message: 'Import it from expo-image instead'
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'react-native-toast-message',
|
name: 'react-native-toast-message',
|
||||||
message: 'Import it from components instead'
|
message: 'Import it from components instead'
|
||||||
|
|
|
@ -11,6 +11,7 @@ edition = { workspace = true }
|
||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Spacedrive Sub-crates
|
||||||
sd-mobile-core = { path = "../../core" }
|
sd-mobile-core = { path = "../../core" }
|
||||||
|
|
||||||
# FFI
|
# FFI
|
||||||
|
|
|
@ -7,6 +7,7 @@ repository = { workspace = true }
|
||||||
edition = { workspace = true }
|
edition = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Spacedrive Sub-crates
|
||||||
sd-core = { path = "../../../../../core", features = [
|
sd-core = { path = "../../../../../core", features = [
|
||||||
"mobile",
|
"mobile",
|
||||||
], default-features = false }
|
], default-features = false }
|
||||||
|
|
|
@ -74,10 +74,15 @@ pub fn handle_core_msg(
|
||||||
None => {
|
None => {
|
||||||
let _guard = Node::init_logger(&data_dir);
|
let _guard = Node::init_logger(&data_dir);
|
||||||
|
|
||||||
// TODO: probably don't unwrap
|
let new_node = match Node::new(data_dir, sd_core::Env::new(CLIENT_ID)).await {
|
||||||
let new_node = Node::new(data_dir, sd_core::Env::new(CLIENT_ID))
|
Ok(node) => node,
|
||||||
.await
|
Err(err) => {
|
||||||
.unwrap();
|
error!("failed to initialise node: {}", err);
|
||||||
|
callback(Err(query));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
node.replace(new_node.clone());
|
node.replace(new_node.clone());
|
||||||
new_node
|
new_node
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,4 +14,5 @@ edition = { workspace = true }
|
||||||
crate-type = ["staticlib"]
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
# Spacedrive Sub-crates
|
||||||
sd-mobile-core = { path = "../../core" }
|
sd-mobile-core = { path = "../../core" }
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
"expo-av": "^13.10.5",
|
"expo-av": "^13.10.5",
|
||||||
"expo-blur": "^12.9.2",
|
"expo-blur": "^12.9.2",
|
||||||
"expo-build-properties": "~0.11.1",
|
"expo-build-properties": "~0.11.1",
|
||||||
|
"expo-image": "^1.10.6",
|
||||||
"expo-linking": "~6.2.2",
|
"expo-linking": "~6.2.2",
|
||||||
"expo-media-library": "~15.9.1",
|
"expo-media-library": "~15.9.1",
|
||||||
"expo-splash-screen": "~0.26.4",
|
"expo-splash-screen": "~0.26.4",
|
||||||
|
|
|
@ -2,23 +2,70 @@
|
||||||
|
|
||||||
set -eEuo pipefail
|
set -eEuo pipefail
|
||||||
|
|
||||||
if [ "${CI:-}" = "true" ]; then
|
|
||||||
set -x
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Script root
|
# Script root
|
||||||
_root="$(CDPATH='' cd -- "$(dirname "$0")" && pwd -P)"
|
_root="$(CDPATH='' cd -- "$(dirname "$0")" && pwd -P)"
|
||||||
_test_dir="$(CDPATH='' cd -- "${_root}/../tests" && pwd -P)"
|
_test_dir="$(CDPATH='' cd -- "${_root}/../tests" && pwd -P)"
|
||||||
|
|
||||||
PLATFORM=${1:-}
|
PLATFORM="${1:-}"
|
||||||
|
DEVICE_ID=""
|
||||||
|
IOS_APP_BIN_PATH="${_root}/../ios/build/Build/Products/Release-iphonesimulator/Spacedrive.app"
|
||||||
case $PLATFORM in
|
case $PLATFORM in
|
||||||
ios | android) ;;
|
ios)
|
||||||
|
DEVICE_ID="${2:-}"
|
||||||
|
if [ -z "$DEVICE_ID" ]; then
|
||||||
|
echo "Empty IOS emulator UUID" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! [ -e "$IOS_APP_BIN_PATH" ]; then
|
||||||
|
echo "Invalid IOS app binary path" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
android)
|
||||||
|
echo 'Android tests are not implemented yet' >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
*)
|
*)
|
||||||
echo "Usage: run-maestro-tests.sh <android|ios>" >&2
|
echo "Usage: run-maestro-tests.sh <android|ios>" >&2
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
start_app() {
|
||||||
|
case $PLATFORM in
|
||||||
|
ios)
|
||||||
|
xcrun simctl bootstatus "$DEVICE_ID" -b
|
||||||
|
open -a Simulator --args -CurrentDeviceUDID "$DEVICE_ID"
|
||||||
|
xcrun simctl install "$DEVICE_ID" "${_root}/../ios/build/Build/Products/Release-iphonesimulator/Spacedrive.app"
|
||||||
|
# ¯\_(ツ)_/¯
|
||||||
|
sleep 10
|
||||||
|
;;
|
||||||
|
android)
|
||||||
|
echo 'Android tests are not implemented yet' >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
# https://stackoverflow.com/q/11027679#answer-59592881
|
||||||
|
# SYNTAX:
|
||||||
|
# catch STDOUT_VARIABLE STDERR_VARIABLE COMMAND [ARG1[ ARG2[ ...[ ARGN]]]]
|
||||||
|
catch() {
|
||||||
|
{
|
||||||
|
IFS=$'\n' read -r -d '' "${1}"
|
||||||
|
IFS=$'\n' read -r -d '' "${2}"
|
||||||
|
(
|
||||||
|
IFS=$'\n' read -r -d '' _ERRNO_
|
||||||
|
return "$_ERRNO_"
|
||||||
|
)
|
||||||
|
} < <((printf '\0%s\0%d\0' "$( ( ( ({
|
||||||
|
shift 2
|
||||||
|
"${@}"
|
||||||
|
echo "${?}" 1>&3-
|
||||||
|
} | tr -d '\0' 1>&4-) 4>&2- 2>&1- | tr -d '\0' 1>&4-) 3>&1- | exit "$(cat)") 4>&1-)" "${?}" 1>&2) 2>&1)
|
||||||
|
}
|
||||||
|
|
||||||
run_maestro_test() {
|
run_maestro_test() {
|
||||||
if [ $# -ne 1 ]; then
|
if [ $# -ne 1 ]; then
|
||||||
echo "Usage: run_maestro_test <test_file>" >&2
|
echo "Usage: run_maestro_test <test_file>" >&2
|
||||||
|
@ -26,15 +73,52 @@ run_maestro_test() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local i
|
local i
|
||||||
|
local retry_failed=0
|
||||||
local retry_seconds
|
local retry_seconds
|
||||||
for i in {1..6}; do
|
for i in {1..6}; do
|
||||||
if maestro test "$1"; then
|
_maestro_out=''
|
||||||
|
_maestro_err=''
|
||||||
|
|
||||||
|
# https://github.com/expo/expo/blob/339fa68/apps/bare-expo/scripts/start-ios-e2e-test.ts#L12
|
||||||
|
if catch _maestro_out _maestro_err \
|
||||||
|
env MAESTRO_DRIVER_STARTUP_TIMEOUT=120000 maestro --device "$DEVICE_ID" test "$1"; then
|
||||||
# Test succeeded
|
# Test succeeded
|
||||||
|
printf '%s' "$_maestro_out"
|
||||||
|
printf '%s' "$_maestro_err" >&2
|
||||||
return
|
return
|
||||||
else
|
elif echo "$_maestro_err" | grep 'TimeoutException'; then
|
||||||
|
# Test timed out
|
||||||
|
# Kill maestro processes
|
||||||
|
pgrep -fi maestro | xargs kill -KILL
|
||||||
|
|
||||||
|
# Restart app if necessary
|
||||||
|
case $PLATFORM in
|
||||||
|
ios)
|
||||||
|
if ! { xcrun simctl listapps booted | grep CFBundleIdentifier | grep Spacedrive; }; then
|
||||||
|
start_app
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
android)
|
||||||
|
echo 'Android tests are not implemented yet' >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
# Retry
|
||||||
retry_seconds=$((20 * i))
|
retry_seconds=$((20 * i))
|
||||||
echo "Test $1 failed. Retrying in $retry_seconds seconds..."
|
echo "Test $1 timed out. Retrying in $retry_seconds seconds..."
|
||||||
sleep $retry_seconds
|
sleep $retry_seconds
|
||||||
|
else
|
||||||
|
# Test failed
|
||||||
|
printf '%s' "$_maestro_out"
|
||||||
|
printf '%s' "$_maestro_err" >&2
|
||||||
|
if [ $retry_failed -eq 0 ]; then
|
||||||
|
retry_failed=1
|
||||||
|
echo "Test $1 failed. Retrying once more in 10 seconds..."
|
||||||
|
sleep 10
|
||||||
|
else
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
@ -57,6 +141,9 @@ else
|
||||||
)
|
)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Start Spacedrive in the device emulator
|
||||||
|
start_app
|
||||||
|
|
||||||
# Run onboarding first
|
# Run onboarding first
|
||||||
onboardingFile="${_test_dir}/onboarding.yml"
|
onboardingFile="${_test_dir}/onboarding.yml"
|
||||||
if ! run_maestro_test "$onboardingFile"; then
|
if ! run_maestro_test "$onboardingFile"; then
|
||||||
|
|
|
@ -39,7 +39,7 @@ import { useTheme } from './hooks/useTheme';
|
||||||
import { changeTwTheme, tw } from './lib/tailwind';
|
import { changeTwTheme, tw } from './lib/tailwind';
|
||||||
import RootNavigator from './navigation';
|
import RootNavigator from './navigation';
|
||||||
import OnboardingNavigator from './navigation/OnboardingNavigator';
|
import OnboardingNavigator from './navigation/OnboardingNavigator';
|
||||||
import { P2P } from './screens/p2p';
|
import { P2P } from './screens/p2p/P2P';
|
||||||
import { currentLibraryStore } from './utils/nav';
|
import { currentLibraryStore } from './utils/nav';
|
||||||
|
|
||||||
LogBox.ignoreLogs(['Sending `onAnimatedValueUpdate` with no listeners registered.']);
|
LogBox.ignoreLogs(['Sending `onAnimatedValueUpdate` with no listeners registered.']);
|
||||||
|
|
|
@ -17,7 +17,7 @@ export const ProgressBar = memo((props: ProgressBarProps) => {
|
||||||
return (
|
return (
|
||||||
<View style={tw`h-1 overflow-hidden rounded-full bg-app-button`}>
|
<View style={tw`h-1 overflow-hidden rounded-full bg-app-button`}>
|
||||||
<MotiView
|
<MotiView
|
||||||
style={tw`h-full w-[50%] bg-accent`}
|
style={tw`h-full w-1/2 bg-accent`}
|
||||||
from={{ left: '-50%' }}
|
from={{ left: '-50%' }}
|
||||||
animate={{ left: '100%' }}
|
animate={{ left: '100%' }}
|
||||||
transition={{ type: 'timing', duration: 1500, loop: true }}
|
transition={{ type: 'timing', duration: 1500, loop: true }}
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Button } from '../primitive/Button';
|
||||||
import LibraryItem from './LibraryItem';
|
import LibraryItem from './LibraryItem';
|
||||||
|
|
||||||
const iconStyle = tw`text-ink-faint`;
|
const iconStyle = tw`text-ink-faint`;
|
||||||
const iconSize = 28;
|
const iconSize = 24;
|
||||||
export const CATEGORIES_LIST = [
|
export const CATEGORIES_LIST = [
|
||||||
{ name: 'Albums', icon: <Images size={iconSize} style={iconStyle} /> },
|
{ name: 'Albums', icon: <Images size={iconSize} style={iconStyle} /> },
|
||||||
{ name: 'Places', icon: <MapPin size={iconSize} style={iconStyle} /> },
|
{ name: 'Places', icon: <MapPin size={iconSize} style={iconStyle} /> },
|
||||||
|
@ -26,7 +26,7 @@ export const CATEGORIES_LIST = [
|
||||||
{ name: 'Projects', icon: <Briefcase size={iconSize} style={iconStyle} /> },
|
{ name: 'Projects', icon: <Briefcase size={iconSize} style={iconStyle} /> },
|
||||||
{ name: 'Favorites', icon: <Heart size={iconSize} style={iconStyle} /> },
|
{ name: 'Favorites', icon: <Heart size={iconSize} style={iconStyle} /> },
|
||||||
{ name: 'Recents', icon: <Clock size={iconSize} style={iconStyle} /> },
|
{ name: 'Recents', icon: <Clock size={iconSize} style={iconStyle} /> },
|
||||||
{ name: 'Labels', icon: <Tag size={iconSize} style={iconStyle} /> },
|
// { name: 'Labels', icon: <Tag size={iconSize} style={iconStyle} /> },
|
||||||
{ name: 'Imports', icon: <ArchiveBox size={iconSize} style={iconStyle} /> }
|
{ name: 'Imports', icon: <ArchiveBox size={iconSize} style={iconStyle} /> }
|
||||||
];
|
];
|
||||||
const BrowseCategories = () => {
|
const BrowseCategories = () => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ const Jobs = () => {
|
||||||
return (
|
return (
|
||||||
<View style={tw`gap-3`}>
|
<View style={tw`gap-3`}>
|
||||||
<View style={tw`w-full flex-row items-center justify-between px-5`}>
|
<View style={tw`w-full flex-row items-center justify-between px-5`}>
|
||||||
<Text style={tw`text-lg font-bold text-white`}>Jobs</Text>
|
<Text style={tw`text-lg font-bold text-white`}>Active Jobs</Text>
|
||||||
</View>
|
</View>
|
||||||
<Fade color="black" height="100%" width={30}>
|
<Fade color="black" height="100%" width={30}>
|
||||||
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
|
||||||
|
@ -67,7 +67,7 @@ const Job = ({ progress, message, error }: JobProps) => {
|
||||||
backgroundColor={tw.color('ink-light/5')}
|
backgroundColor={tw.color('ink-light/5')}
|
||||||
>
|
>
|
||||||
{(fill) => (
|
{(fill) => (
|
||||||
<View style={tw`flex-row items-end gap-[1px]`}>
|
<View style={tw`flex-row items-end gap-px`}>
|
||||||
<Text style={tw`text-lg font-bold text-white`}>
|
<Text style={tw`text-lg font-bold text-white`}>
|
||||||
{error ? '0' : fill.toFixed(0)}
|
{error ? '0' : fill.toFixed(0)}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -79,7 +79,7 @@ const Job = ({ progress, message, error }: JobProps) => {
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</AnimatedCircularProgress>
|
</AnimatedCircularProgress>
|
||||||
<Text style={tw`w-[60%] text-sm leading-5 text-ink-dull`}>{message}</Text>
|
<Text style={tw`w-3/5 text-sm leading-5 text-ink-dull`}>{message}</Text>
|
||||||
</View>
|
</View>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,12 +12,12 @@ interface CategoryProps {
|
||||||
const ListLibraryItem = ({ name, icon }: CategoryProps) => {
|
const ListLibraryItem = ({ name, icon }: CategoryProps) => {
|
||||||
return (
|
return (
|
||||||
<Card style={tw`flex-row items-center justify-between gap-2 py-3`}>
|
<Card style={tw`flex-row items-center justify-between gap-2 py-3`}>
|
||||||
<View style={tw`flex-row items-center gap-2`}>
|
<View style={tw`flex-row items-center gap-2 px-2`}>
|
||||||
{icon}
|
{icon}
|
||||||
<Text style={twStyle(`mt-0 text-sm text-white`)}>{name}</Text>
|
<Text style={twStyle(`text-sm text-white`)}>{name}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View
|
<View
|
||||||
style={tw`h-8 w-auto flex-row items-center justify-center rounded-full border border-app-lightborder/70 px-2`}
|
style={tw`h-10 w-10 flex-row items-center justify-center rounded-full border border-app-lightborder/70 px-2`}
|
||||||
>
|
>
|
||||||
<Text style={tw`text-xs font-medium text-ink-dull`}>
|
<Text style={tw`text-xs font-medium text-ink-dull`}>
|
||||||
{Math.floor(Math.random() * 200)}
|
{Math.floor(Math.random() * 200)}
|
||||||
|
|
|
@ -109,7 +109,7 @@ const useFormState = () => {
|
||||||
// Switch to the new library
|
// Switch to the new library
|
||||||
currentLibraryStore.id = library.uuid;
|
currentLibraryStore.id = library.uuid;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({ type: 'error', text: 'Failed to create library' });
|
toast.error('Failed to create library');
|
||||||
resetOnboardingStore();
|
resetOnboardingStore();
|
||||||
navigation.navigate('GetStarted');
|
navigation.navigate('GetStarted');
|
||||||
}
|
}
|
78
apps/mobile/src/components/drawer/DrawerContent.tsx
Normal file
78
apps/mobile/src/components/drawer/DrawerContent.tsx
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
import { DrawerContentScrollView } from '@react-navigation/drawer';
|
||||||
|
import { DrawerContentComponentProps } from '@react-navigation/drawer/lib/typescript/src/types';
|
||||||
|
import { AppLogo } from '@sd/assets/images';
|
||||||
|
import { Image } from 'expo-image';
|
||||||
|
import { CheckCircle } from 'phosphor-react-native';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { Platform, Pressable, Text, View } from 'react-native';
|
||||||
|
import { JobManagerContextProvider, useLibraryQuery } from '@sd/client';
|
||||||
|
import Layout from '~/constants/Layout';
|
||||||
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
import { PulseAnimation } from '../animation/lottie';
|
||||||
|
import { ModalRef } from '../layout/Modal';
|
||||||
|
import { JobManagerModal } from '../modal/job/JobManagerModal';
|
||||||
|
import { Button } from '../primitive/Button';
|
||||||
|
import DrawerLibraryManager from './DrawerLibraryManager';
|
||||||
|
import DrawerLocations from './DrawerLocations';
|
||||||
|
import DrawerTags from './DrawerTags';
|
||||||
|
|
||||||
|
const drawerHeight = Platform.select({
|
||||||
|
ios: Layout.window.height * 0.85,
|
||||||
|
android: Layout.window.height * 0.9
|
||||||
|
});
|
||||||
|
|
||||||
|
function JobIcon() {
|
||||||
|
const { data: isActive } = useLibraryQuery(['jobs.isActive']);
|
||||||
|
return isActive ? (
|
||||||
|
<PulseAnimation style={tw`h-[24px] w-[32px]`} speed={1.5} />
|
||||||
|
) : (
|
||||||
|
<CheckCircle color="white" size={24} />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: `navigation` is not typed here...
|
||||||
|
const DrawerContent = ({ navigation, state }: DrawerContentComponentProps) => {
|
||||||
|
// const stackName = getStackNameFromState(state);
|
||||||
|
|
||||||
|
const modalRef = useRef<ModalRef>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DrawerContentScrollView style={tw`flex-1 px-3 py-2`} scrollEnabled={false}>
|
||||||
|
<View style={twStyle('justify-between', { height: drawerHeight })}>
|
||||||
|
<View>
|
||||||
|
<View style={tw`flex flex-row items-center`}>
|
||||||
|
<Image source={AppLogo} style={tw`h-[40px] w-[40px]`} />
|
||||||
|
<Text style={tw`ml-2 text-lg font-bold text-ink`}>Spacedrive</Text>
|
||||||
|
</View>
|
||||||
|
<View style={tw`mt-6`} />
|
||||||
|
{/* Library Manager */}
|
||||||
|
<DrawerLibraryManager />
|
||||||
|
{/* Locations */}
|
||||||
|
<DrawerLocations />
|
||||||
|
{/* Tags */}
|
||||||
|
<DrawerTags />
|
||||||
|
</View>
|
||||||
|
<View style={tw`mt-3 flex w-full flex-row items-center gap-x-4`}>
|
||||||
|
{/* Job Manager */}
|
||||||
|
<JobManagerContextProvider>
|
||||||
|
<Pressable onPress={() => modalRef.current?.present()}>
|
||||||
|
<JobIcon />
|
||||||
|
</Pressable>
|
||||||
|
<JobManagerModal ref={modalRef} />
|
||||||
|
</JobManagerContextProvider>
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
alert('Todo');
|
||||||
|
}}
|
||||||
|
variant="gray"
|
||||||
|
>
|
||||||
|
<Text style={tw`text-xs font-medium text-white`}>Feedback</Text>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</DrawerContentScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerContent;
|
|
@ -1,11 +1,11 @@
|
||||||
|
import { useDrawerStatus } from '@react-navigation/drawer';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { MotiView } from 'moti';
|
import { MotiView } from 'moti';
|
||||||
import { CaretRight, Gear, Lock, Plus } from 'phosphor-react-native';
|
import { CaretRight, Gear, Lock, Plus } from 'phosphor-react-native';
|
||||||
import { useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Alert, Pressable, Text, View } from 'react-native';
|
import { Alert, Pressable, Text, View } from 'react-native';
|
||||||
import { useClientContext } from '@sd/client';
|
import { useClientContext } from '@sd/client';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
|
|
||||||
import { currentLibraryStore } from '~/utils/nav';
|
import { currentLibraryStore } from '~/utils/nav';
|
||||||
|
|
||||||
import { AnimatedHeight } from '../animation/layout';
|
import { AnimatedHeight } from '../animation/layout';
|
||||||
|
@ -13,31 +13,33 @@ import { ModalRef } from '../layout/Modal';
|
||||||
import CreateLibraryModal from '../modal/CreateLibraryModal';
|
import CreateLibraryModal from '../modal/CreateLibraryModal';
|
||||||
import { Divider } from '../primitive/Divider';
|
import { Divider } from '../primitive/Divider';
|
||||||
|
|
||||||
interface Props {
|
const DrawerLibraryManager = () => {
|
||||||
style?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BrowseLibraryManager = ({ style }: Props) => {
|
|
||||||
const [dropdownClosed, setDropdownClosed] = useState(true);
|
const [dropdownClosed, setDropdownClosed] = useState(true);
|
||||||
|
|
||||||
|
// Closes the dropdown when the drawer is closed
|
||||||
|
const isDrawerOpen = useDrawerStatus() === 'open';
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isDrawerOpen) setDropdownClosed(true);
|
||||||
|
}, [isDrawerOpen]);
|
||||||
|
|
||||||
const { library: currentLibrary, libraries } = useClientContext();
|
const { library: currentLibrary, libraries } = useClientContext();
|
||||||
|
|
||||||
const navigation = useNavigation<SettingsStackScreenProps<'Settings'>['navigation']>();
|
const navigation = useNavigation();
|
||||||
|
|
||||||
const modalRef = useRef<ModalRef>(null);
|
const modalRef = useRef<ModalRef>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={twStyle(`w-full`, style)}>
|
<View>
|
||||||
<Pressable onPress={() => setDropdownClosed((v) => !v)}>
|
<Pressable onPress={() => setDropdownClosed((v) => !v)}>
|
||||||
<View
|
<View
|
||||||
style={twStyle(
|
style={twStyle(
|
||||||
'flex h-11 w-full flex-row items-center justify-between border bg-app-input px-3 shadow-sm',
|
'flex h-10 w-full flex-row items-center justify-between border bg-app-input px-3 shadow-sm',
|
||||||
dropdownClosed
|
dropdownClosed
|
||||||
? 'rounded-md border-app-inputborder'
|
? 'rounded-md border-app-inputborder'
|
||||||
: 'rounded-t-md border-app-inputborder border-b-app-inputborder'
|
: 'rounded-t-md border-b-0 border-app-inputborder'
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Text style={tw`text-md font-semibold text-ink`}>
|
<Text style={tw`text-sm font-semibold text-ink`}>
|
||||||
{currentLibrary?.config.name}
|
{currentLibrary?.config.name}
|
||||||
</Text>
|
</Text>
|
||||||
<MotiView
|
<MotiView
|
||||||
|
@ -48,10 +50,13 @@ const BrowseLibraryManager = ({ style }: Props) => {
|
||||||
</MotiView>
|
</MotiView>
|
||||||
</View>
|
</View>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<AnimatedHeight style={tw`absolute top-10 z-10 w-full`} hide={dropdownClosed}>
|
<AnimatedHeight hide={dropdownClosed}>
|
||||||
<View style={tw`w-full rounded-b-md border border-zinc-800 bg-zinc-900 p-2`}>
|
<View
|
||||||
|
style={tw`w-full rounded-b-md border border-app-inputborder bg-app-input p-2`}
|
||||||
|
>
|
||||||
{/* Libraries */}
|
{/* Libraries */}
|
||||||
{libraries.data?.map((library) => {
|
{libraries.data?.map((library) => {
|
||||||
|
// console.log('library', library);
|
||||||
return (
|
return (
|
||||||
<Pressable
|
<Pressable
|
||||||
key={library.uuid}
|
key={library.uuid}
|
||||||
|
@ -80,10 +85,7 @@ const BrowseLibraryManager = ({ style }: Props) => {
|
||||||
{/* Create Library */}
|
{/* Create Library */}
|
||||||
<Pressable
|
<Pressable
|
||||||
style={tw`flex flex-row items-center px-1.5 py-[8px]`}
|
style={tw`flex flex-row items-center px-1.5 py-[8px]`}
|
||||||
onPress={() => {
|
onPress={() => modalRef.current?.present()}
|
||||||
modalRef.current?.present();
|
|
||||||
setDropdownClosed(true);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Plus size={18} weight="bold" color="white" style={tw`mr-2`} />
|
<Plus size={18} weight="bold" color="white" style={tw`mr-2`} />
|
||||||
<Text style={tw`text-sm font-semibold text-white`}>New Library</Text>
|
<Text style={tw`text-sm font-semibold text-white`}>New Library</Text>
|
||||||
|
@ -92,8 +94,13 @@ const BrowseLibraryManager = ({ style }: Props) => {
|
||||||
{/* Manage Library */}
|
{/* Manage Library */}
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.navigate('LibraryGeneralSettings');
|
navigation.navigate('Root', {
|
||||||
setDropdownClosed(true);
|
screen: 'Home',
|
||||||
|
params: {
|
||||||
|
screen: 'SettingsStack',
|
||||||
|
params: { screen: 'LibraryGeneralSettings' }
|
||||||
|
}
|
||||||
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<View style={tw`flex flex-row items-center px-1.5 py-[8px]`}>
|
<View style={tw`flex flex-row items-center px-1.5 py-[8px]`}>
|
||||||
|
@ -114,4 +121,4 @@ const BrowseLibraryManager = ({ style }: Props) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BrowseLibraryManager;
|
export default DrawerLibraryManager;
|
130
apps/mobile/src/components/drawer/DrawerLocations.tsx
Normal file
130
apps/mobile/src/components/drawer/DrawerLocations.tsx
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { Pressable, Text, View } from 'react-native';
|
||||||
|
import {
|
||||||
|
arraysEqual,
|
||||||
|
byteSize,
|
||||||
|
Location,
|
||||||
|
useCache,
|
||||||
|
useLibraryQuery,
|
||||||
|
useNodes,
|
||||||
|
useOnlineLocations
|
||||||
|
} from '@sd/client';
|
||||||
|
import { ModalRef } from '~/components/layout/Modal';
|
||||||
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
import FolderIcon from '../icons/FolderIcon';
|
||||||
|
import CollapsibleView from '../layout/CollapsibleView';
|
||||||
|
import ImportModal from '../modal/ImportModal';
|
||||||
|
import { Button } from '../primitive/Button';
|
||||||
|
|
||||||
|
type DrawerLocationItemProps = {
|
||||||
|
onPress: () => void;
|
||||||
|
location: Location;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrawerLocationItem: React.FC<DrawerLocationItemProps> = ({
|
||||||
|
location,
|
||||||
|
onPress
|
||||||
|
}: DrawerLocationItemProps) => {
|
||||||
|
const onlineLocations = useOnlineLocations();
|
||||||
|
const online = onlineLocations.some((l) => arraysEqual(location.pub_id, l));
|
||||||
|
return (
|
||||||
|
<Pressable onPress={onPress}>
|
||||||
|
<View
|
||||||
|
style={twStyle(
|
||||||
|
'h-auto w-full flex-row items-center justify-between rounded-md border border-app-inputborder/50 bg-app-darkBox p-2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<View style={tw`flex-row items-center gap-1`}>
|
||||||
|
<View style={tw`relative`}>
|
||||||
|
<FolderIcon size={20} />
|
||||||
|
<View
|
||||||
|
style={twStyle(
|
||||||
|
'z-5 absolute bottom-1 right-px h-1.5 w-1.5 rounded-full',
|
||||||
|
online ? 'bg-green-500' : 'bg-red-500'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<Text style={twStyle('text-xs font-medium text-ink')} numberOfLines={1}>
|
||||||
|
{location.name ?? ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View style={tw`rounded-md border border-app-lightborder bg-app-box px-1 py-0.5`}>
|
||||||
|
<Text style={tw`text-[11px] font-medium text-ink-dull`} numberOfLines={1}>
|
||||||
|
{`${byteSize(location.size_in_bytes)}`}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrawerLocations = () => {
|
||||||
|
const navigation = useNavigation<DrawerNavigationHelpers>();
|
||||||
|
|
||||||
|
const modalRef = useRef<ModalRef>(null);
|
||||||
|
|
||||||
|
const result = useLibraryQuery(['locations.list'], { keepPreviousData: true });
|
||||||
|
useNodes(result.data?.nodes);
|
||||||
|
const locations = useCache(result.data?.items);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CollapsibleView
|
||||||
|
title="Locations"
|
||||||
|
titleStyle={tw`text-sm font-semibold text-ink`}
|
||||||
|
containerStyle={tw`mb-3 mt-6`}
|
||||||
|
>
|
||||||
|
<View style={tw`mt-2 flex-col justify-between gap-1`}>
|
||||||
|
{locations?.slice(0, 3).map((location) => (
|
||||||
|
<DrawerLocationItem
|
||||||
|
key={location.id}
|
||||||
|
location={location}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.navigate('BrowseStack', {
|
||||||
|
screen: 'Location',
|
||||||
|
params: { id: location.id },
|
||||||
|
initial: false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
<View style={tw`mt-2 flex-row flex-wrap gap-1`}>
|
||||||
|
{/* Add Location */}
|
||||||
|
<Button
|
||||||
|
style={twStyle(`py-0`, locations?.length > 3 ? 'w-[49%]' : 'w-full')}
|
||||||
|
onPress={() => modalRef.current?.present()}
|
||||||
|
variant="dashed"
|
||||||
|
>
|
||||||
|
<Text style={tw`p-2 text-center text-xs font-medium text-ink-dull`}>
|
||||||
|
+ Location
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
{/* See all locations */}
|
||||||
|
{locations?.length > 3 && (
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate('BrowseStack', {
|
||||||
|
screen: 'Locations',
|
||||||
|
initial: false
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={tw`w-[49%] py-0`}
|
||||||
|
variant="gray"
|
||||||
|
>
|
||||||
|
<Text style={tw`p-2 text-center text-xs font-medium text-ink`}>
|
||||||
|
View all
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</CollapsibleView>
|
||||||
|
<ImportModal ref={modalRef} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerLocations;
|
114
apps/mobile/src/components/drawer/DrawerTags.tsx
Normal file
114
apps/mobile/src/components/drawer/DrawerTags.tsx
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { useRef } from 'react';
|
||||||
|
import { ColorValue, Pressable, Text, View } from 'react-native';
|
||||||
|
import { Tag, useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||||
|
import { ModalRef } from '~/components/layout/Modal';
|
||||||
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
import CollapsibleView from '../layout/CollapsibleView';
|
||||||
|
import CreateTagModal from '../modal/tag/CreateTagModal';
|
||||||
|
import { Button } from '../primitive/Button';
|
||||||
|
|
||||||
|
type DrawerTagItemProps = {
|
||||||
|
tagName: string;
|
||||||
|
tagColor: ColorValue;
|
||||||
|
onPress: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrawerTagItem: React.FC<DrawerTagItemProps> = (props) => {
|
||||||
|
const { tagName, tagColor, onPress } = props;
|
||||||
|
return (
|
||||||
|
<Pressable onPress={onPress} testID="drawer-tag">
|
||||||
|
<View
|
||||||
|
style={twStyle(
|
||||||
|
'h-auto flex-row items-center gap-2 rounded-md border border-app-inputborder/50 bg-app-darkBox p-2'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<View style={twStyle('h-4 w-4 rounded-full', { backgroundColor: tagColor })} />
|
||||||
|
<Text style={twStyle('text-xs font-medium text-ink')} numberOfLines={1}>
|
||||||
|
{tagName}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Pressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const DrawerTags = () => {
|
||||||
|
const tags = useLibraryQuery(['tags.list']);
|
||||||
|
const navigation = useNavigation<DrawerNavigationHelpers>();
|
||||||
|
|
||||||
|
useNodes(tags.data?.nodes);
|
||||||
|
const tagData = useCache(tags.data?.items);
|
||||||
|
|
||||||
|
const modalRef = useRef<ModalRef>(null);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CollapsibleView
|
||||||
|
title="Tags"
|
||||||
|
titleStyle={tw`text-sm font-semibold text-ink`}
|
||||||
|
containerStyle={tw`mb-3 mt-6`}
|
||||||
|
>
|
||||||
|
<View style={tw`mt-2 flex-row justify-between gap-1`}>
|
||||||
|
<TagColumn tags={tagData} dataAmount={[0, 2]} />
|
||||||
|
<TagColumn tags={tagData} dataAmount={[2, 4]} />
|
||||||
|
</View>
|
||||||
|
<View style={tw`mt-2 flex-row flex-wrap gap-1`}>
|
||||||
|
{/* Add Tag */}
|
||||||
|
<Button
|
||||||
|
style={twStyle(`py-0`, tagData?.length > 4 ? 'w-[49%]' : 'w-full')}
|
||||||
|
onPress={() => modalRef.current?.present()}
|
||||||
|
variant="dashed"
|
||||||
|
>
|
||||||
|
<Text style={tw`p-2 text-center text-xs font-medium text-ink-dull`}>+ Tag</Text>
|
||||||
|
</Button>
|
||||||
|
{/* See all tags */}
|
||||||
|
{tagData?.length > 4 && (
|
||||||
|
<Button
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate('BrowseStack', {
|
||||||
|
screen: 'Tags',
|
||||||
|
initial: false
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
style={tw`w-[49%] py-0`}
|
||||||
|
variant="gray"
|
||||||
|
>
|
||||||
|
<Text style={tw`p-2 text-center text-xs font-medium text-ink`}>
|
||||||
|
View all
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
<CreateTagModal ref={modalRef} />
|
||||||
|
</CollapsibleView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TagColumnProps {
|
||||||
|
tags?: Tag[];
|
||||||
|
dataAmount: [start: number, end: number];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TagColumn = ({ tags, dataAmount }: TagColumnProps) => {
|
||||||
|
const navigation = useNavigation<DrawerNavigationHelpers>();
|
||||||
|
return (
|
||||||
|
<View style={tw`w-[49%] flex-col gap-1`}>
|
||||||
|
{tags?.slice(dataAmount[0], dataAmount[1]).map((tag: any) => (
|
||||||
|
<DrawerTagItem
|
||||||
|
key={tag.id}
|
||||||
|
tagName={tag.name!}
|
||||||
|
onPress={() =>
|
||||||
|
navigation.navigate('BrowseStack', {
|
||||||
|
screen: 'Tag',
|
||||||
|
params: { id: tag.id, color: tag.color }
|
||||||
|
})
|
||||||
|
}
|
||||||
|
tagColor={tag.color as ColorValue}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DrawerTags;
|
|
@ -1,20 +1,18 @@
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { FlashList } from '@shopify/flash-list';
|
import { FlashList } from '@shopify/flash-list';
|
||||||
import { UseInfiniteQueryResult } from '@tanstack/react-query';
|
import { UseInfiniteQueryResult } from '@tanstack/react-query';
|
||||||
import { AnimatePresence, MotiView } from 'moti';
|
|
||||||
import { useState } from 'react';
|
|
||||||
import { ActivityIndicator, Pressable } from 'react-native';
|
import { ActivityIndicator, Pressable } from 'react-native';
|
||||||
import { isPath, SearchData, type ExplorerItem } from '@sd/client';
|
import { isPath, SearchData, type ExplorerItem } from '@sd/client';
|
||||||
import Layout from '~/constants/Layout';
|
import Layout from '~/constants/Layout';
|
||||||
import { tw } from '~/lib/tailwind';
|
import { tw } from '~/lib/tailwind';
|
||||||
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
||||||
import { ExplorerLayoutMode, getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
|
import { useExplorerStore } from '~/stores/explorerStore';
|
||||||
import { useActionsModalStore } from '~/stores/modalStore';
|
import { useActionsModalStore } from '~/stores/modalStore';
|
||||||
|
|
||||||
import ScreenContainer from '../layout/ScreenContainer';
|
import ScreenContainer from '../layout/ScreenContainer';
|
||||||
import FileItem from './FileItem';
|
import FileItem from './FileItem';
|
||||||
import FileRow from './FileRow';
|
import FileRow from './FileRow';
|
||||||
import Menu from './Menu';
|
import Menu from './menu/Menu';
|
||||||
|
|
||||||
type ExplorerProps = {
|
type ExplorerProps = {
|
||||||
tabHeight?: boolean;
|
tabHeight?: boolean;
|
||||||
|
@ -27,14 +25,8 @@ type ExplorerProps = {
|
||||||
|
|
||||||
const Explorer = (props: ExplorerProps) => {
|
const Explorer = (props: ExplorerProps) => {
|
||||||
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
|
const navigation = useNavigation<BrowseStackScreenProps<'Location'>['navigation']>();
|
||||||
const explorerStore = useExplorerStore();
|
|
||||||
const [layoutMode, setLayoutMode] = useState<ExplorerLayoutMode>(getExplorerStore().layoutMode);
|
|
||||||
|
|
||||||
function changeLayoutMode(kind: ExplorerLayoutMode) {
|
const store = useExplorerStore();
|
||||||
// We need to keep layoutMode as a state to make sure flash-list re-renders.
|
|
||||||
setLayoutMode(kind);
|
|
||||||
getExplorerStore().layoutMode = kind;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { modalRef, setData } = useActionsModalStore();
|
const { modalRef, setData } = useActionsModalStore();
|
||||||
|
|
||||||
|
@ -52,45 +44,11 @@ const Explorer = (props: ExplorerProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScreenContainer tabHeight={props.tabHeight} scrollview={false} style={'gap-0 py-0'}>
|
<ScreenContainer tabHeight={props.tabHeight} scrollview={false} style={'gap-0 py-0'}>
|
||||||
{/* Header */}
|
<Menu />
|
||||||
{/* Sort By */}
|
|
||||||
{/* <SortByMenu /> */}
|
|
||||||
<AnimatePresence>
|
|
||||||
{explorerStore.toggleMenu && (
|
|
||||||
<MotiView
|
|
||||||
from={{ translateY: -70 }}
|
|
||||||
animate={{ translateY: 0 }}
|
|
||||||
transition={{
|
|
||||||
type: 'timing',
|
|
||||||
duration: 300,
|
|
||||||
repeat: 0,
|
|
||||||
repeatReverse: false
|
|
||||||
}}
|
|
||||||
exit={{ translateY: -70 }}
|
|
||||||
>
|
|
||||||
<Menu
|
|
||||||
changeLayoutMode={(kind: ExplorerLayoutMode) => {
|
|
||||||
changeLayoutMode(kind);
|
|
||||||
}}
|
|
||||||
layoutMode={layoutMode}
|
|
||||||
/>
|
|
||||||
</MotiView>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
{/* Layout (Grid/List) */}
|
|
||||||
{/* {layoutMode === 'grid' ? (
|
|
||||||
<Pressable onPress={() => changeLayoutMode('list')}>
|
|
||||||
<SquaresFour color={tw.color('ink')} size={23} />
|
|
||||||
</Pressable>
|
|
||||||
) : (
|
|
||||||
<Pressable onPress={() => changeLayoutMode('grid')}>
|
|
||||||
<Rows color={tw.color('ink')} size={23} />
|
|
||||||
</Pressable>
|
|
||||||
)} */}
|
|
||||||
{/* Items */}
|
{/* Items */}
|
||||||
<FlashList
|
<FlashList
|
||||||
key={layoutMode}
|
key={store.layoutMode}
|
||||||
numColumns={layoutMode === 'grid' ? getExplorerStore().gridNumColumns : 1}
|
numColumns={store.layoutMode === 'grid' ? store.gridNumColumns : 1}
|
||||||
data={props.items ?? []}
|
data={props.items ?? []}
|
||||||
keyExtractor={(item) =>
|
keyExtractor={(item) =>
|
||||||
item.type === 'NonIndexedPath'
|
item.type === 'NonIndexedPath'
|
||||||
|
@ -101,15 +59,19 @@ const Explorer = (props: ExplorerProps) => {
|
||||||
}
|
}
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<Pressable onPress={() => handlePress(item)}>
|
<Pressable onPress={() => handlePress(item)}>
|
||||||
{layoutMode === 'grid' ? <FileItem data={item} /> : <FileRow data={item} />}
|
{store.layoutMode === 'grid' ? (
|
||||||
|
<FileItem data={item} />
|
||||||
|
) : (
|
||||||
|
<FileRow data={item} />
|
||||||
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
contentContainerStyle={tw`px-2 py-5`}
|
contentContainerStyle={tw`px-2 py-5`}
|
||||||
extraData={layoutMode}
|
extraData={store.layoutMode}
|
||||||
estimatedItemSize={
|
estimatedItemSize={
|
||||||
layoutMode === 'grid'
|
store.layoutMode === 'grid'
|
||||||
? Layout.window.width / getExplorerStore().gridNumColumns
|
? Layout.window.width / store.gridNumColumns
|
||||||
: getExplorerStore().listItemSize
|
: store.listItemSize
|
||||||
}
|
}
|
||||||
onEndReached={() => props.loadMore?.()}
|
onEndReached={() => props.loadMore?.()}
|
||||||
onEndReachedThreshold={0.6}
|
onEndReachedThreshold={0.6}
|
||||||
|
|
|
@ -23,7 +23,7 @@ const FileItem = ({ data }: FileItemProps) => {
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<FileThumb data={data} />
|
<FileThumb data={data} />
|
||||||
<View style={tw`mt-1 px-1.5 py-[1px]`}>
|
<View style={tw`mt-1 px-1.5 py-px`}>
|
||||||
<Text numberOfLines={1} style={tw`text-center text-xs font-medium text-white`}>
|
<Text numberOfLines={1} style={tw`text-center text-xs font-medium text-white`}>
|
||||||
{filePath?.name}
|
{filePath?.name}
|
||||||
{filePath?.extension && `.${filePath.extension}`}
|
{filePath?.extension && `.${filePath.extension}`}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import { DocumentDirectoryPath } from '@dr.pogodin/react-native-fs';
|
import { DocumentDirectoryPath } from '@dr.pogodin/react-native-fs';
|
||||||
import { getIcon } from '@sd/assets/util';
|
import { getIcon } from '@sd/assets/util';
|
||||||
|
import { Image } from 'expo-image';
|
||||||
import { useEffect, useLayoutEffect, useMemo, useState, type PropsWithChildren } from 'react';
|
import { useEffect, useLayoutEffect, useMemo, useState, type PropsWithChildren } from 'react';
|
||||||
import { Image, View } from 'react-native';
|
import { View } from 'react-native';
|
||||||
import {
|
import {
|
||||||
getExplorerItemData,
|
getExplorerItemData,
|
||||||
getItemFilePath,
|
getItemFilePath,
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
|
|
||||||
import { Pressable, View } from 'react-native';
|
|
||||||
import { tw } from '~/lib/tailwind';
|
|
||||||
import { ExplorerLayoutMode } from '~/stores/explorerStore';
|
|
||||||
|
|
||||||
interface MenuProps {
|
|
||||||
layoutMode: ExplorerLayoutMode;
|
|
||||||
changeLayoutMode: (kind: ExplorerLayoutMode) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Menu = ({ layoutMode, changeLayoutMode }: MenuProps) => {
|
|
||||||
return (
|
|
||||||
<View
|
|
||||||
style={tw`w-screen flex-row justify-between border-b border-app-cardborder bg-app-header px-7 py-4`}
|
|
||||||
>
|
|
||||||
<View style={tw`flex-row gap-3`}>
|
|
||||||
<Pressable hitSlop={24} onPress={() => changeLayoutMode('grid')}>
|
|
||||||
<SquaresFour
|
|
||||||
color={tw.color(layoutMode === 'grid' ? 'text-accent' : 'text-ink-dull')}
|
|
||||||
size={23}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable hitSlop={24} onPress={() => changeLayoutMode('list')}>
|
|
||||||
<Rows
|
|
||||||
color={tw.color(layoutMode === 'list' ? 'text-accent' : 'text-ink-dull')}
|
|
||||||
size={23}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
<Pressable hitSlop={24} onPress={() => changeLayoutMode('media')}>
|
|
||||||
<MonitorPlay
|
|
||||||
color={tw.color(layoutMode === 'media' ? 'text-accent' : 'text-ink-dull')}
|
|
||||||
size={23}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
</View>
|
|
||||||
<SlidersHorizontal style={tw`text-ink-dull`} />
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Menu;
|
|
72
apps/mobile/src/components/explorer/menu/Menu.tsx
Normal file
72
apps/mobile/src/components/explorer/menu/Menu.tsx
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
import { AnimatePresence, MotiView } from 'moti';
|
||||||
|
import { MonitorPlay, Rows, SlidersHorizontal, SquaresFour } from 'phosphor-react-native';
|
||||||
|
import { Pressable, View } from 'react-native';
|
||||||
|
import { toast } from '~/components/primitive/Toast';
|
||||||
|
import { tw } from '~/lib/tailwind';
|
||||||
|
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
|
||||||
|
|
||||||
|
import SortByMenu from './SortByMenu';
|
||||||
|
|
||||||
|
const Menu = () => {
|
||||||
|
const store = useExplorerStore();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence>
|
||||||
|
{store.toggleMenu && (
|
||||||
|
<MotiView
|
||||||
|
from={{ translateY: -70 }}
|
||||||
|
animate={{ translateY: 0 }}
|
||||||
|
transition={{
|
||||||
|
type: 'timing',
|
||||||
|
duration: 300,
|
||||||
|
repeat: 0,
|
||||||
|
repeatReverse: false
|
||||||
|
}}
|
||||||
|
exit={{ translateY: -70 }}
|
||||||
|
>
|
||||||
|
<View
|
||||||
|
style={tw`w-screen flex-row items-center justify-between border-b border-app-cardborder bg-app-header px-7 py-4`}
|
||||||
|
>
|
||||||
|
<View style={tw`flex-row gap-3`}>
|
||||||
|
<Pressable onPress={() => (getExplorerStore().layoutMode = 'grid')}>
|
||||||
|
<SquaresFour
|
||||||
|
color={tw.color(
|
||||||
|
store.layoutMode === 'grid'
|
||||||
|
? 'text-accent'
|
||||||
|
: 'text-ink-dull'
|
||||||
|
)}
|
||||||
|
size={23}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable onPress={() => (getExplorerStore().layoutMode = 'list')}>
|
||||||
|
<Rows
|
||||||
|
color={tw.color(
|
||||||
|
store.layoutMode === 'list'
|
||||||
|
? 'text-accent'
|
||||||
|
: 'text-ink-dull'
|
||||||
|
)}
|
||||||
|
size={23}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
<Pressable
|
||||||
|
onPress={() => toast.error('Media view is not available yet...')}
|
||||||
|
// onPress={() => (getExplorerStore().layoutMode = 'media')}
|
||||||
|
>
|
||||||
|
<MonitorPlay
|
||||||
|
color={tw.color(
|
||||||
|
store.layoutMode === 'media'
|
||||||
|
? 'text-accent'
|
||||||
|
: 'text-ink-dull'
|
||||||
|
)}
|
||||||
|
size={23}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
</View>
|
||||||
|
<SortByMenu />
|
||||||
|
</View>
|
||||||
|
</MotiView>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default Menu;
|
|
@ -15,8 +15,8 @@ const sortOptions = {
|
||||||
|
|
||||||
type SortByType = keyof typeof sortOptions;
|
type SortByType = keyof typeof sortOptions;
|
||||||
|
|
||||||
const ArrowUpIcon = () => <ArrowUp weight="bold" size={16} color={tw.color('ink')} />;
|
const ArrowUpIcon = () => <ArrowUp weight="bold" size={16} color={tw.color('ink-dull')} />;
|
||||||
const ArrowDownIcon = () => <ArrowDown weight="bold" size={16} color={tw.color('ink')} />;
|
const ArrowDownIcon = () => <ArrowDown weight="bold" size={16} color={tw.color('ink-dull')} />;
|
||||||
|
|
||||||
const SortByMenu = () => {
|
const SortByMenu = () => {
|
||||||
const [sortBy, setSortBy] = useState<SortByType>('name');
|
const [sortBy, setSortBy] = useState<SortByType>('name');
|
||||||
|
@ -26,7 +26,7 @@ const SortByMenu = () => {
|
||||||
<Menu
|
<Menu
|
||||||
trigger={
|
trigger={
|
||||||
<View style={tw`flex flex-row items-center`}>
|
<View style={tw`flex flex-row items-center`}>
|
||||||
<Text style={tw`mr-0.5 font-medium text-ink`}>{sortOptions[sortBy]}</Text>
|
<Text style={tw`mr-0.5 font-medium text-ink-dull`}>{sortOptions[sortBy]}</Text>
|
||||||
{sortDirection === 'asc' ? <ArrowUpIcon /> : <ArrowDownIcon />}
|
{sortDirection === 'asc' ? <ArrowUpIcon /> : <ArrowDownIcon />}
|
||||||
</View>
|
</View>
|
||||||
}
|
}
|
|
@ -1,22 +1,21 @@
|
||||||
|
import { DrawerNavigationHelpers } from '@react-navigation/drawer/lib/typescript/src/types';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { NativeStackHeaderProps } from '@react-navigation/native-stack';
|
import { NativeStackHeaderProps } from '@react-navigation/native-stack';
|
||||||
import { ArrowLeft, DotsThreeOutline, MagnifyingGlass } from 'phosphor-react-native';
|
import { ArrowLeft, DotsThreeOutline, List, MagnifyingGlass } from 'phosphor-react-native';
|
||||||
import { Platform, Pressable, Text, View } from 'react-native';
|
import { Platform, Pressable, Text, View } from 'react-native';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
|
import { getExplorerStore, useExplorerStore } from '~/stores/explorerStore';
|
||||||
|
|
||||||
import BrowseLibraryManager from '../browse/DrawerLibraryManager';
|
|
||||||
import { Icon } from '../icons/Icon';
|
import { Icon } from '../icons/Icon';
|
||||||
import Search from '../search/Search';
|
import Search from '../search/Search';
|
||||||
|
|
||||||
type HeaderProps = {
|
type HeaderProps = {
|
||||||
title?: string; //title of the page
|
title?: string; //title of the page
|
||||||
showLibrary?: boolean; //show the library manager
|
|
||||||
showSearch?: boolean; //show the search button
|
showSearch?: boolean; //show the search button
|
||||||
searchType?: 'explorer' | 'location' | 'categories'; //Temporary
|
showDrawer?: boolean; //show the drawer button
|
||||||
|
searchType?: 'explorer' | 'location' | 'categories' | 'tags'; //Temporary
|
||||||
navBack?: boolean; //navigate back to the previous screen
|
navBack?: boolean; //navigate back to the previous screen
|
||||||
navBackHome?: boolean; //navigate back to the home screen of the stack
|
|
||||||
headerKind?: 'default' | 'location' | 'tag'; //kind of header
|
headerKind?: 'default' | 'location' | 'tag'; //kind of header
|
||||||
route?: never;
|
route?: never;
|
||||||
routeTitle?: never;
|
routeTitle?: never;
|
||||||
|
@ -33,16 +32,15 @@ type Props =
|
||||||
// Default header with search bar and button to open drawer
|
// Default header with search bar and button to open drawer
|
||||||
export default function Header({
|
export default function Header({
|
||||||
title,
|
title,
|
||||||
showLibrary,
|
|
||||||
searchType,
|
searchType,
|
||||||
navBack,
|
navBack,
|
||||||
route,
|
route,
|
||||||
routeTitle,
|
routeTitle,
|
||||||
navBackHome = false,
|
|
||||||
headerKind = 'default',
|
headerKind = 'default',
|
||||||
showSearch = true
|
showDrawer = false,
|
||||||
|
showSearch = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation<DrawerNavigationHelpers>();
|
||||||
const explorerStore = useExplorerStore();
|
const explorerStore = useExplorerStore();
|
||||||
const routeParams = route?.route.params as any;
|
const routeParams = route?.route.params as any;
|
||||||
const headerHeight = useSafeAreaInsets().top;
|
const headerHeight = useSafeAreaInsets().top;
|
||||||
|
@ -54,7 +52,7 @@ export default function Header({
|
||||||
paddingTop: headerHeight + (isAndroid ? 15 : 0)
|
paddingTop: headerHeight + (isAndroid ? 15 : 0)
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<View style={tw`mx-auto h-auto w-full justify-center px-5 pb-4`}>
|
<View style={tw`mx-auto h-auto w-full justify-center px-5 pb-3`}>
|
||||||
<View style={tw`w-full flex-row items-center justify-between`}>
|
<View style={tw`w-full flex-row items-center justify-between`}>
|
||||||
<View style={tw`flex-row items-center gap-3`}>
|
<View style={tw`flex-row items-center gap-3`}>
|
||||||
{navBack && (
|
{navBack && (
|
||||||
|
@ -69,6 +67,11 @@ export default function Header({
|
||||||
)}
|
)}
|
||||||
<View style={tw`flex-row items-center gap-2`}>
|
<View style={tw`flex-row items-center gap-2`}>
|
||||||
<HeaderIconKind headerKind={headerKind} routeParams={routeParams} />
|
<HeaderIconKind headerKind={headerKind} routeParams={routeParams} />
|
||||||
|
{showDrawer && (
|
||||||
|
<Pressable onPress={() => navigation.openDrawer()}>
|
||||||
|
<List size={24} color={tw.color('text-zinc-300')} />
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
<Text
|
<Text
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
style={tw`max-w-[200px] text-xl font-bold text-white`}
|
style={tw`max-w-[200px] text-xl font-bold text-white`}
|
||||||
|
@ -83,7 +86,7 @@ export default function Header({
|
||||||
<Pressable
|
<Pressable
|
||||||
hitSlop={24}
|
hitSlop={24}
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
navigation.navigate('ExplorerSearch', {
|
navigation.navigate('SearchStack', {
|
||||||
screen: 'Search'
|
screen: 'Search'
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
|
@ -113,8 +116,6 @@ export default function Header({
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{showLibrary && <BrowseLibraryManager style="mt-4" />}
|
|
||||||
{searchType && <HeaderSearchType searchType={searchType} />}
|
{searchType && <HeaderSearchType searchType={searchType} />}
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
|
@ -131,6 +132,8 @@ const HeaderSearchType = ({ searchType }: HeaderSearchTypeProps) => {
|
||||||
return 'Explorer'; //TODO
|
return 'Explorer'; //TODO
|
||||||
case 'location':
|
case 'location':
|
||||||
return <Search placeholder="Location name..." />;
|
return <Search placeholder="Location name..." />;
|
||||||
|
case 'tags':
|
||||||
|
return <Search placeholder="Tag name..." />;
|
||||||
case 'categories':
|
case 'categories':
|
||||||
return <Search placeholder="Category name..." />;
|
return <Search placeholder="Category name..." />;
|
||||||
default:
|
default:
|
||||||
|
@ -150,7 +153,7 @@ const HeaderIconKind = ({ headerKind, routeParams }: HeaderIconKindProps) => {
|
||||||
case 'tag':
|
case 'tag':
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={twStyle('h-[30px] w-[30px] rounded-full', {
|
style={twStyle('h-[24px] w-[24px] rounded-full', {
|
||||||
backgroundColor: routeParams.color
|
backgroundColor: routeParams.color
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Folder, Folder_Light } from '@sd/assets/icons';
|
import { Folder, Folder_Light } from '@sd/assets/icons';
|
||||||
import { Image } from 'react-native';
|
import { Image } from 'expo-image';
|
||||||
|
|
||||||
type FolderProps = {
|
type FolderProps = {
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { getIcon, iconNames } from '@sd/assets/util';
|
import { getIcon, iconNames } from '@sd/assets/util';
|
||||||
import { Image, ImageProps } from 'react-native';
|
import { Image, ImageProps } from 'expo-image';
|
||||||
import { ClassInput } from 'twrnc';
|
import { ClassInput } from 'twrnc';
|
||||||
import { isDarkTheme } from '@sd/client';
|
import { isDarkTheme } from '@sd/client';
|
||||||
import { twStyle } from '~/lib/tailwind';
|
import { twStyle } from '~/lib/tailwind';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { Image } from 'expo-image';
|
||||||
import { Icon } from 'phosphor-react-native';
|
import { Icon } from 'phosphor-react-native';
|
||||||
import { Fragment } from 'react';
|
import { Fragment } from 'react';
|
||||||
import { Image, Text, View, ViewStyle } from 'react-native';
|
import { Text, View, ViewStyle } from 'react-native';
|
||||||
import { TextItems } from '@sd/client';
|
import { TextItems } from '@sd/client';
|
||||||
import { styled, tw, twStyle } from '~/lib/tailwind';
|
import { styled, tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
|
|
@ -150,19 +150,10 @@ const toastErrorSuccess = (
|
||||||
) => {
|
) => {
|
||||||
return {
|
return {
|
||||||
onError: () => {
|
onError: () => {
|
||||||
errorMessage &&
|
errorMessage && toast.error(errorMessage);
|
||||||
toast({
|
|
||||||
type: 'error',
|
|
||||||
text: errorMessage
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
successMessage &&
|
successMessage && toast.success(successMessage), successCallBack?.();
|
||||||
toast({
|
|
||||||
type: 'success',
|
|
||||||
text: successMessage
|
|
||||||
}),
|
|
||||||
successCallBack?.();
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { useRoute } from '@react-navigation/native';
|
|
||||||
import { DimensionValue, Platform } from 'react-native';
|
import { DimensionValue, Platform } from 'react-native';
|
||||||
import LinearGradient from 'react-native-linear-gradient';
|
import LinearGradient from 'react-native-linear-gradient';
|
||||||
import { ClassInput } from 'twrnc';
|
import { ClassInput } from 'twrnc';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
import { useExplorerStore } from '~/stores/explorerStore';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode; // children of fade
|
children: React.ReactNode; // children of fade
|
||||||
|
@ -13,7 +11,6 @@ interface Props {
|
||||||
orientation?: 'horizontal' | 'vertical'; // orientation of fade
|
orientation?: 'horizontal' | 'vertical'; // orientation of fade
|
||||||
fadeSides?: 'left-right' | 'top-bottom'; // which sides to fade
|
fadeSides?: 'left-right' | 'top-bottom'; // which sides to fade
|
||||||
screenFade?: boolean; // if true, the fade will consider the bottom tab bar height
|
screenFade?: boolean; // if true, the fade will consider the bottom tab bar height
|
||||||
noConditions?: boolean; // if true, the fade will be rendered as is
|
|
||||||
bottomFadeStyle?: ClassInput; // tailwind style for bottom fade
|
bottomFadeStyle?: ClassInput; // tailwind style for bottom fade
|
||||||
topFadeStyle?: ClassInput; // tailwind style for top fade
|
topFadeStyle?: ClassInput; // tailwind style for top fade
|
||||||
}
|
}
|
||||||
|
@ -25,20 +22,15 @@ const Fade = ({
|
||||||
height,
|
height,
|
||||||
bottomFadeStyle,
|
bottomFadeStyle,
|
||||||
topFadeStyle,
|
topFadeStyle,
|
||||||
noConditions = false,
|
|
||||||
screenFade = false,
|
screenFade = false,
|
||||||
fadeSides = 'left-right',
|
fadeSides = 'left-right',
|
||||||
orientation = 'horizontal'
|
orientation = 'horizontal'
|
||||||
}: Props) => {
|
}: Props) => {
|
||||||
const route = useRoute();
|
|
||||||
const { toggleMenu } = useExplorerStore();
|
|
||||||
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
|
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
|
||||||
const gradientStartEndMap = {
|
const gradientStartEndMap = {
|
||||||
'left-right': { start: { x: 0, y: 0 }, end: { x: 1, y: 0 } },
|
'left-right': { start: { x: 0, y: 0 }, end: { x: 1, y: 0 } },
|
||||||
'top-bottom': { start: { x: 0, y: 1 }, end: { x: 0, y: 0 } }
|
'top-bottom': { start: { x: 0, y: 1 }, end: { x: 0, y: 0 } }
|
||||||
};
|
};
|
||||||
const menuHeight = 57; // height of the explorer menu
|
|
||||||
const routesWithMenu = ['Location', 'Search', 'Tag']; // routes that are associated with the explorer
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<LinearGradient
|
<LinearGradient
|
||||||
|
@ -46,10 +38,7 @@ const Fade = ({
|
||||||
width: orientation === 'vertical' ? height : width,
|
width: orientation === 'vertical' ? height : width,
|
||||||
height: orientation === 'vertical' ? width : height,
|
height: orientation === 'vertical' ? width : height,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top:
|
top: 0,
|
||||||
!noConditions && toggleMenu && routesWithMenu.includes(route.name)
|
|
||||||
? menuHeight
|
|
||||||
: 0,
|
|
||||||
alignSelf: 'center',
|
alignSelf: 'center',
|
||||||
left: fadeSides === 'left-right' ? 0 : undefined,
|
left: fadeSides === 'left-right' ? 0 : undefined,
|
||||||
transform: fadeSides === 'left-right' ? undefined : [{ rotate: '180deg' }],
|
transform: fadeSides === 'left-right' ? undefined : [{ rotate: '180deg' }],
|
||||||
|
|
|
@ -3,8 +3,6 @@ import { Platform, ScrollView, View } from 'react-native';
|
||||||
import { ClassInput } from 'twrnc/dist/esm/types';
|
import { ClassInput } from 'twrnc/dist/esm/types';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
import Fade from './Fade';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
/** If true, the container will be a ScrollView */
|
/** If true, the container will be a ScrollView */
|
||||||
|
@ -13,16 +11,11 @@ interface Props {
|
||||||
/** If true, the bottom tab bar height will be added to the bottom of the container */
|
/** If true, the bottom tab bar height will be added to the bottom of the container */
|
||||||
tabHeight?: boolean;
|
tabHeight?: boolean;
|
||||||
scrollToBottomOnChange?: boolean;
|
scrollToBottomOnChange?: boolean;
|
||||||
/** Styling of both side fades */
|
|
||||||
topFadeStyle?: string;
|
|
||||||
bottomFadeStyle?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ScreenContainer = ({
|
const ScreenContainer = ({
|
||||||
children,
|
children,
|
||||||
style,
|
style,
|
||||||
topFadeStyle,
|
|
||||||
bottomFadeStyle,
|
|
||||||
scrollview = true,
|
scrollview = true,
|
||||||
tabHeight = true,
|
tabHeight = true,
|
||||||
scrollToBottomOnChange = false
|
scrollToBottomOnChange = false
|
||||||
|
@ -31,16 +24,6 @@ const ScreenContainer = ({
|
||||||
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
|
const bottomTabBarHeight = Platform.OS === 'ios' ? 80 : 60;
|
||||||
return scrollview ? (
|
return scrollview ? (
|
||||||
<View style={tw`relative flex-1`}>
|
<View style={tw`relative flex-1`}>
|
||||||
<Fade
|
|
||||||
topFadeStyle={topFadeStyle}
|
|
||||||
bottomFadeStyle={bottomFadeStyle}
|
|
||||||
screenFade
|
|
||||||
fadeSides="top-bottom"
|
|
||||||
orientation="vertical"
|
|
||||||
color="black"
|
|
||||||
width={30}
|
|
||||||
height="100%"
|
|
||||||
>
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onContentSizeChange={() => {
|
onContentSizeChange={() => {
|
||||||
|
@ -55,20 +38,9 @@ const ScreenContainer = ({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</Fade>
|
|
||||||
</View>
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<View style={tw`relative flex-1`}>
|
<View style={tw`relative flex-1`}>
|
||||||
<Fade
|
|
||||||
topFadeStyle={topFadeStyle}
|
|
||||||
bottomFadeStyle={bottomFadeStyle}
|
|
||||||
screenFade
|
|
||||||
fadeSides="top-bottom"
|
|
||||||
orientation="vertical"
|
|
||||||
color="black"
|
|
||||||
width={30}
|
|
||||||
height="100%"
|
|
||||||
>
|
|
||||||
<View
|
<View
|
||||||
style={twStyle(
|
style={twStyle(
|
||||||
'flex-1 justify-between gap-10 bg-black py-6',
|
'flex-1 justify-between gap-10 bg-black py-6',
|
||||||
|
@ -78,7 +50,6 @@ const ScreenContainer = ({
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
</View>
|
</View>
|
||||||
</Fade>
|
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,7 +28,7 @@ const GridLocation: React.FC<GridLocationProps> = ({ location, modalRef }: GridL
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
<Pressable hitSlop={24} onPress={() => modalRef.current?.present()}>
|
<Pressable onPress={() => modalRef.current?.present()}>
|
||||||
<DotsThreeOutlineVertical
|
<DotsThreeOutlineVertical
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={20}
|
size={20}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
import { DotsThreeOutlineVertical } from 'phosphor-react-native';
|
import { DotsThreeOutlineVertical } from 'phosphor-react-native';
|
||||||
|
import { useRef } from 'react';
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import { Swipeable } from 'react-native-gesture-handler';
|
import { Swipeable } from 'react-native-gesture-handler';
|
||||||
import { arraysEqual, byteSize, Location, useOnlineLocations } from '@sd/client';
|
import { arraysEqual, byteSize, Location, useOnlineLocations } from '@sd/client';
|
||||||
|
@ -8,20 +9,22 @@ import { SettingsStackScreenProps } from '~/navigation/tabs/SettingsStack';
|
||||||
|
|
||||||
import FolderIcon from '../icons/FolderIcon';
|
import FolderIcon from '../icons/FolderIcon';
|
||||||
import Card from '../layout/Card';
|
import Card from '../layout/Card';
|
||||||
import { ModalRef } from '../layout/Modal';
|
|
||||||
import RightActions from './RightActions';
|
import RightActions from './RightActions';
|
||||||
|
|
||||||
interface ListLocationProps {
|
interface ListLocationProps {
|
||||||
location: Location;
|
location: Location;
|
||||||
modalRef: React.RefObject<ModalRef>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListLocation = ({ location, modalRef }: ListLocationProps) => {
|
const ListLocation = ({ location }: ListLocationProps) => {
|
||||||
|
const swipeRef = useRef<Swipeable>(null);
|
||||||
|
|
||||||
const navigation = useNavigation<SettingsStackScreenProps<'LocationSettings'>['navigation']>();
|
const navigation = useNavigation<SettingsStackScreenProps<'LocationSettings'>['navigation']>();
|
||||||
const onlineLocations = useOnlineLocations();
|
const onlineLocations = useOnlineLocations();
|
||||||
const online = onlineLocations.some((l) => arraysEqual(location.pub_id, l));
|
const online = onlineLocations.some((l) => arraysEqual(location.pub_id, l));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Swipeable
|
<Swipeable
|
||||||
|
ref={swipeRef}
|
||||||
containerStyle={tw`rounded-md border border-app-cardborder bg-app-card`}
|
containerStyle={tw`rounded-md border border-app-cardborder bg-app-card`}
|
||||||
enableTrackpadTwoFingerGesture
|
enableTrackpadTwoFingerGesture
|
||||||
renderRightActions={(progress, _, swipeable) => (
|
renderRightActions={(progress, _, swipeable) => (
|
||||||
|
@ -36,7 +39,7 @@ const ListLocation = ({ location, modalRef }: ListLocationProps) => {
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<Card style={tw`h-auto flex-row justify-between gap-3 border-0 p-3`}>
|
<Card style={tw`h-auto flex-row justify-between gap-3 border-0 p-3`}>
|
||||||
<View style={tw`w-[50%] flex-row items-center gap-2`}>
|
<View style={tw`w-1/2 flex-row items-center gap-2`}>
|
||||||
<View style={tw`relative`}>
|
<View style={tw`relative`}>
|
||||||
<FolderIcon size={38} />
|
<FolderIcon size={38} />
|
||||||
<View
|
<View
|
||||||
|
@ -63,13 +66,13 @@ const ListLocation = ({ location, modalRef }: ListLocationProps) => {
|
||||||
style={tw`rounded-md border border-app-lightborder bg-app-highlight p-1.5`}
|
style={tw`rounded-md border border-app-lightborder bg-app-highlight p-1.5`}
|
||||||
>
|
>
|
||||||
<Text
|
<Text
|
||||||
style={tw`text-left text-xs font-bold text-ink-dull`}
|
style={tw`text-left text-xs font-medium text-ink-dull`}
|
||||||
numberOfLines={1}
|
numberOfLines={1}
|
||||||
>
|
>
|
||||||
{`${byteSize(location.size_in_bytes)}`}
|
{`${byteSize(location.size_in_bytes)}`}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Pressable hitSlop={24} onPress={() => modalRef.current?.present()}>
|
<Pressable hitSlop={24} onPress={() => swipeRef.current?.openRight()}>
|
||||||
<DotsThreeOutlineVertical
|
<DotsThreeOutlineVertical
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={20}
|
size={20}
|
||||||
|
|
|
@ -29,19 +29,21 @@ export const LocationItem = ({
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
>
|
>
|
||||||
{viewStyle === 'grid' ? (
|
{viewStyle === 'grid' ? (
|
||||||
<GridLocation location={location} modalRef={modalRef} />
|
<>
|
||||||
|
<GridLocation location={location} modalRef={modalRef} />
|
||||||
|
<LocationModal
|
||||||
|
editLocation={() => {
|
||||||
|
editLocation();
|
||||||
|
modalRef.current?.close();
|
||||||
|
}}
|
||||||
|
locationId={location.id}
|
||||||
|
ref={modalRef}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ListLocation location={location} modalRef={modalRef} />
|
<ListLocation location={location} />
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<LocationModal
|
|
||||||
editLocation={() => {
|
|
||||||
editLocation();
|
|
||||||
modalRef.current?.close();
|
|
||||||
}}
|
|
||||||
locationId={location.id}
|
|
||||||
ref={modalRef}
|
|
||||||
/>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -102,7 +102,10 @@ export const ActionsModal = () => {
|
||||||
<View style={tw`flex-1 px-4`}>
|
<View style={tw`flex-1 px-4`}>
|
||||||
<View style={tw`flex flex-row items-center`}>
|
<View style={tw`flex flex-row items-center`}>
|
||||||
{/* Thumbnail/Icon */}
|
{/* Thumbnail/Icon */}
|
||||||
<Pressable onPress={() => fileInfoRef.current?.present()}>
|
<Pressable
|
||||||
|
onPress={handleOpen}
|
||||||
|
onLongPress={() => fileInfoRef.current?.present()}
|
||||||
|
>
|
||||||
<FileThumb data={data} size={1} />
|
<FileThumb data={data} size={1} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<View style={tw`ml-2 flex-1`}>
|
<View style={tw`ml-2 flex-1`}>
|
||||||
|
|
|
@ -28,9 +28,6 @@ const CreateTagModal = forwardRef<ModalRef, unknown>((_, ref) => {
|
||||||
const submitPlausibleEvent = usePlausibleEvent();
|
const submitPlausibleEvent = usePlausibleEvent();
|
||||||
|
|
||||||
const { mutate: createTag } = useLibraryMutation('tags.create', {
|
const { mutate: createTag } = useLibraryMutation('tags.create', {
|
||||||
onMutate: () => {
|
|
||||||
console.log('Creating tag');
|
|
||||||
},
|
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Reset form
|
// Reset form
|
||||||
setTagName('');
|
setTagName('');
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { Pressable, Text, View } from 'react-native';
|
||||||
import { FlatList } from 'react-native-gesture-handler';
|
import { FlatList } from 'react-native-gesture-handler';
|
||||||
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
|
import { useCache, useLibraryQuery, useNodes } from '@sd/client';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
import { BrowseStackScreenProps } from '~/navigation/tabs/BrowseStack';
|
import { OverviewStackScreenProps } from '~/navigation/tabs/OverviewStack';
|
||||||
|
|
||||||
import Fade from '../layout/Fade';
|
import Fade from '../layout/Fade';
|
||||||
import { ModalRef } from '../layout/Modal';
|
import { ModalRef } from '../layout/Modal';
|
||||||
|
@ -15,7 +15,7 @@ import OverviewSection from './OverviewSection';
|
||||||
import StatCard from './StatCard';
|
import StatCard from './StatCard';
|
||||||
|
|
||||||
const Locations = () => {
|
const Locations = () => {
|
||||||
const navigation = useNavigation<BrowseStackScreenProps<'Browse'>['navigation']>();
|
const navigation = useNavigation<OverviewStackScreenProps<'Overview'>['navigation']>();
|
||||||
const modalRef = useRef<ModalRef>(null);
|
const modalRef = useRef<ModalRef>(null);
|
||||||
|
|
||||||
const locationsQuery = useLibraryQuery(['locations.list']);
|
const locationsQuery = useLibraryQuery(['locations.list']);
|
||||||
|
@ -26,7 +26,6 @@ const Locations = () => {
|
||||||
<>
|
<>
|
||||||
<OverviewSection title="Locations" count={locations?.length}>
|
<OverviewSection title="Locations" count={locations?.length}>
|
||||||
<View style={tw`flex-row items-center`}>
|
<View style={tw`flex-row items-center`}>
|
||||||
<Fade height={'100%'} width={30} color="black">
|
|
||||||
<FlatList
|
<FlatList
|
||||||
horizontal
|
horizontal
|
||||||
data={locations}
|
data={locations}
|
||||||
|
@ -60,7 +59,8 @@ const Locations = () => {
|
||||||
renderItem={({ item }) => (
|
renderItem={({ item }) => (
|
||||||
<Pressable
|
<Pressable
|
||||||
onPress={() =>
|
onPress={() =>
|
||||||
navigation.navigate('BrowseStack', {
|
navigation.jumpTo('BrowseStack', {
|
||||||
|
initial: false,
|
||||||
screen: 'Location',
|
screen: 'Location',
|
||||||
params: { id: item.id }
|
params: { id: item.id }
|
||||||
})
|
})
|
||||||
|
@ -76,7 +76,6 @@ const Locations = () => {
|
||||||
</Pressable>
|
</Pressable>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Fade>
|
|
||||||
</View>
|
</View>
|
||||||
</OverviewSection>
|
</OverviewSection>
|
||||||
<ImportModal ref={modalRef} />
|
<ImportModal ref={modalRef} />
|
||||||
|
|
|
@ -7,6 +7,7 @@ import { ClassInput } from 'twrnc/dist/esm/types';
|
||||||
import { byteSize, Statistics, StatisticsResponse, useLibraryContext } from '@sd/client';
|
import { byteSize, Statistics, StatisticsResponse, useLibraryContext } from '@sd/client';
|
||||||
import useCounter from '~/hooks/useCounter';
|
import useCounter from '~/hooks/useCounter';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
import Card from '../layout/Card';
|
import Card from '../layout/Card';
|
||||||
|
|
||||||
const StatItemNames: Partial<Record<keyof Statistics, string>> = {
|
const StatItemNames: Partial<Record<keyof Statistics, string>> = {
|
||||||
|
@ -31,13 +32,9 @@ const StatItem = ({ title, bytes, isLoading, style }: StatItemProps) => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
style={twStyle(
|
style={twStyle('flex flex-col items-center justify-center p-2', style, {
|
||||||
'flex flex-col items-center justify-center p-2',
|
hidden: isLoading
|
||||||
style,
|
})}
|
||||||
{
|
|
||||||
hidden: isLoading
|
|
||||||
}
|
|
||||||
)}
|
|
||||||
>
|
>
|
||||||
<Text style={tw`text-sm font-bold text-zinc-400`}>{title}</Text>
|
<Text style={tw`text-sm font-bold text-zinc-400`}>{title}</Text>
|
||||||
<View style={tw`mt-1 flex-row items-baseline`}>
|
<View style={tw`mt-1 flex-row items-baseline`}>
|
||||||
|
|
|
@ -84,7 +84,7 @@ const StatCard = ({ icon, name, connectionType, ...stats }: StatCardProps) => {
|
||||||
style={tw`flex h-10 flex-row items-center gap-1.5 border-t border-app-cardborder px-2`}
|
style={tw`flex h-10 flex-row items-center gap-1.5 border-t border-app-cardborder px-2`}
|
||||||
>
|
>
|
||||||
<View
|
<View
|
||||||
style={tw`rounded border border-app-lightborder bg-app-highlight px-1.5 py-[1px]`}
|
style={tw`rounded border border-app-lightborder bg-app-highlight px-1.5 py-px`}
|
||||||
>
|
>
|
||||||
<Text style={tw`text-xs font-medium uppercase text-ink-dull`}>
|
<Text style={tw`text-xs font-medium uppercase text-ink-dull`}>
|
||||||
{connectionType || 'Local'}
|
{connectionType || 'Local'}
|
||||||
|
|
28
apps/mobile/src/components/primitive/DottedDivider.tsx
Normal file
28
apps/mobile/src/components/primitive/DottedDivider.tsx
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import { View } from 'react-native';
|
||||||
|
import { ClassInput } from 'twrnc';
|
||||||
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
//border-style is not supported - so this is a way to do it
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
color?: string;
|
||||||
|
dotCount?: number;
|
||||||
|
style?: ClassInput;
|
||||||
|
}
|
||||||
|
|
||||||
|
const DottedDivider = ({ dotCount = 100, color = 'bg-app-lightborder', style }: Props) => {
|
||||||
|
return (
|
||||||
|
<View style={tw`flex-1 flex-row items-center gap-0.5 overflow-hidden`}>
|
||||||
|
{Array.from({ length: dotCount }).map((_, index) => (
|
||||||
|
<View
|
||||||
|
key={index}
|
||||||
|
style={twStyle(`h-0.5 w-0.5 rounded-full`, style, {
|
||||||
|
backgroundColor: tw.color(color)
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DottedDivider;
|
|
@ -12,7 +12,7 @@ export const InfoPill = (props: Props) => {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={twStyle(
|
style={twStyle(
|
||||||
'rounded-md border border-transparent bg-app-highlight px-[6px] py-[1px] shadow shadow-app-shade/5',
|
'rounded-md border border-transparent bg-app-highlight px-[6px] py-px shadow shadow-app-shade/5',
|
||||||
props.containerStyle
|
props.containerStyle
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
@ -27,7 +27,7 @@ export function PlaceholderPill(props: Props) {
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={twStyle(
|
style={twStyle(
|
||||||
'rounded-md border border-dashed border-app-highlight bg-transparent px-[6px] py-[1px] shadow shadow-app-shade/10',
|
'rounded-md border border-dashed border-app-highlight bg-transparent px-[6px] py-px shadow shadow-app-shade/10',
|
||||||
props.containerStyle
|
props.containerStyle
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -16,7 +16,7 @@ type SwitchContainerProps = { title: string; description?: string } & SwitchProp
|
||||||
export const SwitchContainer: FC<SwitchContainerProps> = ({ title, description, ...props }) => {
|
export const SwitchContainer: FC<SwitchContainerProps> = ({ title, description, ...props }) => {
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex flex-row items-center justify-between pb-6`}>
|
<View style={tw`flex flex-row items-center justify-between pb-6`}>
|
||||||
<View style={tw`w-[80%]`}>
|
<View style={tw`w-4/5`}>
|
||||||
<Text style={tw`text-sm font-medium text-ink`}>{title}</Text>
|
<Text style={tw`text-sm font-medium text-ink`}>{title}</Text>
|
||||||
{description && <Text style={tw`mt-2 text-sm text-ink-dull`}>{description}</Text>}
|
{description && <Text style={tw`mt-2 text-sm text-ink-dull`}>{description}</Text>}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -3,25 +3,25 @@ import { Text, View } from 'react-native';
|
||||||
import Toast, { ToastConfig } from 'react-native-toast-message';
|
import Toast, { ToastConfig } from 'react-native-toast-message';
|
||||||
import { tw } from '~/lib/tailwind';
|
import { tw } from '~/lib/tailwind';
|
||||||
|
|
||||||
// TODO:
|
const baseStyles = 'w-[340px] flex-row overflow-hidden rounded-md border p-3 shadow-lg';
|
||||||
// - Expand toast on press to show full message if it's too long
|
|
||||||
// - Add a onPress option
|
|
||||||
// - Add leading icon & trailing icon
|
|
||||||
|
|
||||||
const toastConfig: ToastConfig = {
|
const toastConfig: ToastConfig = {
|
||||||
success: ({ text1, ...rest }) => (
|
success: ({ text1, ...rest }) => (
|
||||||
<View
|
<View style={tw.style(baseStyles, 'border-app-line bg-app-darkBox/90 ')}>
|
||||||
style={tw`w-[340px] flex-row overflow-hidden rounded-md border border-app-line bg-app-darkBox/90 p-3 shadow-lg`}
|
|
||||||
>
|
|
||||||
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
|
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
|
||||||
{text1}
|
{text1}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
),
|
),
|
||||||
error: ({ text1, ...rest }) => (
|
error: ({ text1, ...rest }) => (
|
||||||
<View
|
<View style={tw.style(baseStyles, 'border-red-500 bg-red-500/90')}>
|
||||||
style={tw`border-app-red bg-app-red/90 w-[340px] flex-row overflow-hidden rounded-md border p-3 shadow-lg`}
|
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
|
||||||
>
|
{text1}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
),
|
||||||
|
info: ({ text1, ...rest }) => (
|
||||||
|
<View style={tw.style(baseStyles, 'border-app-line bg-app-darkBox/90')}>
|
||||||
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
|
<Text style={tw`text-sm font-medium text-ink`} numberOfLines={3}>
|
||||||
{text1}
|
{text1}
|
||||||
</Text>
|
</Text>
|
||||||
|
@ -29,8 +29,26 @@ const toastConfig: ToastConfig = {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
|
|
||||||
function toast({ text, type }: { type: 'success' | 'error' | 'info'; text: string }) {
|
function showToast({ text, type }: { type: 'success' | 'error' | 'info'; text: string }) {
|
||||||
Toast.show({ type, text1: text, visibilityTime: 3000, topOffset: 60 });
|
const visibilityTime = 3000;
|
||||||
|
const topOffset = 60;
|
||||||
|
Toast.show({ type, text1: text, visibilityTime, topOffset });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toast: {
|
||||||
|
success: (text: string) => void;
|
||||||
|
error: (text: string) => void;
|
||||||
|
info: (text: string) => void;
|
||||||
|
} = {
|
||||||
|
success: function (text: string): void {
|
||||||
|
showToast({ text, type: 'success' });
|
||||||
|
},
|
||||||
|
error: function (text: string): void {
|
||||||
|
showToast({ text, type: 'error' });
|
||||||
|
},
|
||||||
|
info: function (text: string): void {
|
||||||
|
showToast({ text, type: 'info' });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export { Toast, toast, toastConfig };
|
export { Toast, toast, toastConfig };
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default function Search({ placeholder }: Props) {
|
||||||
}, [searchStore]);
|
}, [searchStore]);
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
style={tw`mt-4 flex h-11 w-full flex-row items-center justify-between rounded-md border border-app-inputborder bg-app-input px-3 shadow-sm`}
|
style={tw`mt-3 h-10 w-full flex-row items-center justify-between rounded-md border border-app-inputborder bg-app-input px-3 shadow-sm`}
|
||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
onChangeText={(text) => searchStore.setSearch(text)}
|
onChangeText={(text) => searchStore.setSearch(text)}
|
||||||
|
|
|
@ -47,7 +47,7 @@ const FiltersBar = () => {
|
||||||
<Plus weight="bold" size={20} color={tw.color('text-ink-dull')} />
|
<Plus weight="bold" size={20} color={tw.color('text-ink-dull')} />
|
||||||
</Button>
|
</Button>
|
||||||
<View style={tw`relative flex-1`}>
|
<View style={tw`relative flex-1`}>
|
||||||
<Fade noConditions height={'100%'} width={30} color="app-header">
|
<Fade height={'100%'} width={30} color="app-header">
|
||||||
<FlatList
|
<FlatList
|
||||||
ref={flatListRef}
|
ref={flatListRef}
|
||||||
showsHorizontalScrollIndicator={false}
|
showsHorizontalScrollIndicator={false}
|
||||||
|
|
|
@ -19,6 +19,7 @@ import Extension from './Extension';
|
||||||
import Kind from './Kind';
|
import Kind from './Kind';
|
||||||
import Locations from './Locations';
|
import Locations from './Locations';
|
||||||
import Name from './Name';
|
import Name from './Name';
|
||||||
|
import SavedSearches from './SavedSearches';
|
||||||
import Tags from './Tags';
|
import Tags from './Tags';
|
||||||
|
|
||||||
const options = [
|
const options = [
|
||||||
|
@ -84,6 +85,7 @@ const FiltersList = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={tw`gap-10`}>
|
<View style={tw`gap-10`}>
|
||||||
|
<SavedSearches />
|
||||||
<View>
|
<View>
|
||||||
<SectionTitle
|
<SectionTitle
|
||||||
style={tw`px-6 pb-3`}
|
style={tw`px-6 pb-3`}
|
||||||
|
|
|
@ -38,9 +38,7 @@ const Locations = () => {
|
||||||
data={locations}
|
data={locations}
|
||||||
renderItem={({ item }) => <LocationFilter data={item} />}
|
renderItem={({ item }) => <LocationFilter data={item} />}
|
||||||
numColumns={
|
numColumns={
|
||||||
(locations && locations.length < 3
|
locations ? Math.max(Math.ceil(locations.length / 2), 2) : 1
|
||||||
? 2
|
|
||||||
: Math.ceil(locations.length / 2)) ?? 1
|
|
||||||
}
|
}
|
||||||
contentContainerStyle={tw`w-full`}
|
contentContainerStyle={tw`w-full`}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
|
|
59
apps/mobile/src/components/search/filters/SavedSearches.tsx
Normal file
59
apps/mobile/src/components/search/filters/SavedSearches.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import { MotiView } from 'moti';
|
||||||
|
import { MotiPressable } from 'moti/interactions';
|
||||||
|
import { FlatList, Text, View } from 'react-native';
|
||||||
|
import { Icon } from '~/components/icons/Icon';
|
||||||
|
import Card from '~/components/layout/Card';
|
||||||
|
import Fade from '~/components/layout/Fade';
|
||||||
|
import SectionTitle from '~/components/layout/SectionTitle';
|
||||||
|
import VirtualizedListWrapper from '~/components/layout/VirtualizedListWrapper';
|
||||||
|
import DottedDivider from '~/components/primitive/DottedDivider';
|
||||||
|
import { tw } from '~/lib/tailwind';
|
||||||
|
|
||||||
|
const SavedSearches = () => {
|
||||||
|
return (
|
||||||
|
<Fade color="black" width={30} height="100%">
|
||||||
|
<MotiView
|
||||||
|
from={{ opacity: 0, translateY: 20 }}
|
||||||
|
animate={{ opacity: 1, translateY: 0 }}
|
||||||
|
transition={{ type: 'timing', duration: 300 }}
|
||||||
|
>
|
||||||
|
<SectionTitle
|
||||||
|
style={tw`px-6 pb-3`}
|
||||||
|
title="Saved searches"
|
||||||
|
sub="Tap a saved search for searching quickly"
|
||||||
|
/>
|
||||||
|
<VirtualizedListWrapper contentContainerStyle={tw`px-6`} horizontal>
|
||||||
|
<FlatList
|
||||||
|
data={Array.from({ length: 6 })}
|
||||||
|
renderItem={() => <SavedSearch />}
|
||||||
|
keyExtractor={(_, index) => index.toString()}
|
||||||
|
numColumns={Math.ceil(6 / 2)}
|
||||||
|
scrollEnabled={false}
|
||||||
|
contentContainerStyle={tw`w-full`}
|
||||||
|
showsHorizontalScrollIndicator={false}
|
||||||
|
style={tw`flex-row`}
|
||||||
|
ItemSeparatorComponent={() => <View style={tw`h-2 w-2`} />}
|
||||||
|
/>
|
||||||
|
</VirtualizedListWrapper>
|
||||||
|
<DottedDivider style={'mt-6'} />
|
||||||
|
</MotiView>
|
||||||
|
</Fade>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SavedSearch = () => {
|
||||||
|
return (
|
||||||
|
<MotiPressable
|
||||||
|
from={{ opacity: 0, translateY: 20 }}
|
||||||
|
animate={{ opacity: 1, translateY: 0 }}
|
||||||
|
transition={{ type: 'timing', duration: 300 }}
|
||||||
|
>
|
||||||
|
<Card style={tw`mr-2 w-auto flex-row gap-2 p-2.5`}>
|
||||||
|
<Icon name="Folder" size={20} />
|
||||||
|
<Text style={tw`text-sm font-medium text-ink`}>Saved search</Text>
|
||||||
|
</Card>
|
||||||
|
</MotiPressable>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SavedSearches;
|
|
@ -38,11 +38,7 @@ const Tags = () => {
|
||||||
data={tagsData}
|
data={tagsData}
|
||||||
renderItem={({ item }) => <TagFilter tag={item} />}
|
renderItem={({ item }) => <TagFilter tag={item} />}
|
||||||
extraData={searchStore.filters.tags}
|
extraData={searchStore.filters.tags}
|
||||||
numColumns={
|
numColumns={tagsData ? Math.max(Math.ceil(tagsData.length / 2), 2) : 1}
|
||||||
tagsData && tagsData.length < 3
|
|
||||||
? 2
|
|
||||||
: Math.ceil(tagsData.length / 2) ?? 1
|
|
||||||
}
|
|
||||||
key={tagsData ? 'tagsSearch' : '_'}
|
key={tagsData ? 'tagsSearch' : '_'}
|
||||||
contentContainerStyle={tw`w-full`}
|
contentContainerStyle={tw`w-full`}
|
||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
|
|
|
@ -24,7 +24,7 @@ const SettingsToggle = ({ title, description, onEnabledChange, control, name }:
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={tw`flex-row items-center justify-between`}>
|
<View style={tw`flex-row items-center justify-between`}>
|
||||||
<View style={tw`w-[75%]`}>
|
<View style={tw`w-3/4`}>
|
||||||
<Text style={tw`text-sm font-medium text-ink`}>{title}</Text>
|
<Text style={tw`text-sm font-medium text-ink`}>{title}</Text>
|
||||||
{description && <Text style={tw`mt-1 text-xs text-ink-dull`}>{description}</Text>}
|
{description && <Text style={tw`mt-1 text-xs text-ink-dull`}>{description}</Text>}
|
||||||
</View>
|
</View>
|
||||||
|
|
|
@ -20,7 +20,7 @@ const GridTag = ({ tag, modalRef }: GridTagProps) => {
|
||||||
backgroundColor: tag.color!
|
backgroundColor: tag.color!
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Pressable hitSlop={24} onPress={() => modalRef.current?.present()}>
|
<Pressable onPress={() => modalRef.current?.present()}>
|
||||||
<DotsThreeOutlineVertical
|
<DotsThreeOutlineVertical
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={20}
|
size={20}
|
||||||
|
|
|
@ -1,28 +1,28 @@
|
||||||
import { DotsThreeOutlineVertical } from 'phosphor-react-native';
|
import { DotsThreeOutlineVertical } from 'phosphor-react-native';
|
||||||
|
import { useRef } from 'react';
|
||||||
import { Pressable, Text, View } from 'react-native';
|
import { Pressable, Text, View } from 'react-native';
|
||||||
import { Swipeable } from 'react-native-gesture-handler';
|
import { Swipeable } from 'react-native-gesture-handler';
|
||||||
import { ClassInput } from 'twrnc';
|
import { ClassInput } from 'twrnc';
|
||||||
import { Tag } from '@sd/client';
|
import { Tag } from '@sd/client';
|
||||||
import { tw, twStyle } from '~/lib/tailwind';
|
import { tw, twStyle } from '~/lib/tailwind';
|
||||||
|
|
||||||
import { ModalRef } from '../layout/Modal';
|
|
||||||
import RightActions from './RightActions';
|
import RightActions from './RightActions';
|
||||||
|
|
||||||
interface ListTagProps {
|
interface ListTagProps {
|
||||||
tag: Tag;
|
tag: Tag;
|
||||||
tagStyle?: ClassInput;
|
tagStyle?: ClassInput;
|
||||||
modalRef: React.RefObject<ModalRef>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ListTag = ({ tag, tagStyle, modalRef }: ListTagProps) => {
|
const ListTag = ({ tag, tagStyle }: ListTagProps) => {
|
||||||
|
const swipeRef = useRef<Swipeable>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Swipeable
|
<Swipeable
|
||||||
|
ref={swipeRef}
|
||||||
containerStyle={tw`rounded-md border border-app-cardborder bg-app-card p-3`}
|
containerStyle={tw`rounded-md border border-app-cardborder bg-app-card p-3`}
|
||||||
enableTrackpadTwoFingerGesture
|
enableTrackpadTwoFingerGesture
|
||||||
renderRightActions={(progress, _, swipeable) => (
|
renderRightActions={(progress, _, swipeable) => (
|
||||||
<>
|
<RightActions progress={progress} swipeable={swipeable} tag={tag} />
|
||||||
<RightActions progress={progress} swipeable={swipeable} tag={tag} />
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<View style={twStyle('h-auto flex-row items-center justify-between', tagStyle)}>
|
<View style={twStyle('h-auto flex-row items-center justify-between', tagStyle)}>
|
||||||
|
@ -39,7 +39,7 @@ const ListTag = ({ tag, tagStyle, modalRef }: ListTagProps) => {
|
||||||
{tag.name}
|
{tag.name}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
<Pressable hitSlop={24} onPress={() => modalRef.current?.present()}>
|
<Pressable onPress={() => swipeRef.current?.openRight()}>
|
||||||
<DotsThreeOutlineVertical
|
<DotsThreeOutlineVertical
|
||||||
weight="fill"
|
weight="fill"
|
||||||
size={20}
|
size={20}
|
||||||
|
|
|
@ -24,12 +24,14 @@ export const TagItem = ({ tag, onPress, viewStyle = 'grid' }: TagItemProps) => {
|
||||||
testID="browse-tag"
|
testID="browse-tag"
|
||||||
>
|
>
|
||||||
{viewStyle === 'grid' ? (
|
{viewStyle === 'grid' ? (
|
||||||
<GridTag tag={tag} modalRef={modalRef} />
|
<>
|
||||||
|
<GridTag tag={tag} modalRef={modalRef} />
|
||||||
|
<TagModal ref={modalRef} tag={tag} />
|
||||||
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ListTag tag={tag} modalRef={modalRef} />
|
<ListTag tag={tag} />
|
||||||
)}
|
)}
|
||||||
</Pressable>
|
</Pressable>
|
||||||
<TagModal ref={modalRef} tag={tag} />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -70,7 +70,7 @@ module.exports = {
|
||||||
iconborder: `hsla(${DARK_HUE}, 10%, 100%, ${ALPHA})`,
|
iconborder: `hsla(${DARK_HUE}, 10%, 100%, ${ALPHA})`,
|
||||||
// background (dark)
|
// background (dark)
|
||||||
box: `hsla(${DARK_HUE}, 15%, 18%, ${ALPHA})`,
|
box: `hsla(${DARK_HUE}, 15%, 18%, ${ALPHA})`,
|
||||||
darkBox: `hsla(${DARK_HUE}, 15%, 7%, ${ALPHA})`,
|
darkBox: `hsla(${DARK_HUE}, 10%, 7%, ${ALPHA})`,
|
||||||
// foreground (light)
|
// foreground (light)
|
||||||
overlay: `hsla(${DARK_HUE}, 15%, 17%, ${ALPHA})`,
|
overlay: `hsla(${DARK_HUE}, 15%, 17%, ${ALPHA})`,
|
||||||
// border
|
// border
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue