diff --git a/.eslintrc.js b/.eslintrc.js index b90e3f317..30b671959 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,5 @@ // TODO: Update this config... https://typescript-eslint.io/blog/announcing-typescript-eslint-v6/ +// TODO: Please I already forgot :sob: module.exports = { env: { browser: true, diff --git a/.gitattributes b/.gitattributes index f863a9cde..ffcff90b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -6,4 +6,9 @@ *.sql linguist-detectable=false # I don't want docs website to be included in language stats -docs/** linguist-vendored \ No newline at end of file +docs/** linguist-vendored + +# Ignore all snapshots and test files +**/__snapshots__/** linguist-vendored +**/*.test.ts linguist-vendored +**/*.test.tsx linguist-vendored \ No newline at end of file diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 04e6f2614..b303e108c 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -6,65 +6,36 @@ To ensure nobody's time and effort is wasted, please be sure to follow the guide 1. Check to see if an issue already exists relevant to your feature/topic 2. Create an issue (if an issue does not already exist) and express interest in working it (see the [issues](#issues) section below) -3. Create a new feature branch **off of the `develop` branch** - _not_ `main`. All PRs should be made against `develop`. +3. Create a new feature branch **off of the `experimental` or `develop` branches** - _not_ `main`. All PRs should be made against either `experimental` or `develop`. 4. Add appropiate documentation, tests, etc, if necessary. 5. Ensure you have your code formatters properly configured (both Prettier and Rustfmt). 6. Once you've completed your changes, create your PR! -7. Follow the PR naming format to help ensure the changelog generator properly picks up your additions: +7. Follow the PR naming format outlined at [gitmoji.dev](https://gitmoji.dev/specification), used for more uniform generation of release notes - > :information_source: Honestly, don't stress about this part right now. I don't even have a changelog generator!! This kind of structure will only matter once releases are more regular and providing changelogs are more important. For now, just make sure your PR name and body is descriptive and concise :heart: - - ``` - : - ``` - - Where `type` is one of the following: - - - `feat`: A new feature - - `fix`: A bug fix - - `docs`: Documentation only changes - - `refactor`: A code change that neither fixes a bug nor adds a feature - - `perf`: A code change that improves performance - - `test`: Adding missing tests or correcting existing tests - - `ci`: Changes to our CI configuration files and scripts - - `chore`: Other changes that don't modify `src` or `test` files, such as updating `package.json` or `README.md` - - `revert`: Reverts a previous commit - - `WIP`: Work in progress - - The `description` should contain a _succinct_ description of the change: - - - use the imperative, present tense: "change" not "changed" nor "changes" - - don't capitalize the first letter - - no dot (.) at the end - - Examples: - - ``` - feat: add support for Reading Lists - fix: remove broken link - docs: update CONTRIBUTING.md to include PR naming format - ``` + > :information_source: Don't stress too much about this part. Just make sure your PR name and body is descriptive and concise, :heart: 8. Stick around and make sure your PR passes all checks and gets merged! ## Issues -I use GitHub issues to track bugs, feature requests, and other tasks. No rigid structure is enforced, but please try and follow these guidelines: +I use GitHub issues to track bugs, feature requests, and other tasks. No rigid structure is enforced, but please try to fill out the templates fully as best you can. Generally, it is useful to include the following information: + +- Docker tag (or commit hash displayed in settings) +- Log output (server logs, browser console, etc) +- Access method (browser on host machine, mobile on network, etc) +- Network logs (network tab) and details (reverse proxy, VPN, etc) + +If you're not sure if an issue is relevant or appropriate, e.g. if you have more of a question to ask, feel free to pop in the [Discord](https://discord.gg/63Ybb7J3as) and ask! -- Please try and be as descriptive as possible when opening an issue. -- There are a few templates available to help guide you, but if you're not sure which one to use just use the "Blank Issue" template. - - If you're opening an issue to request a feature, please try and explain why you think it would be a good addition to the project. If applicable, include example use cases. - - If you're opening an issue to report a bug, please try and include a minimal reproduction of the bug (video, code, logs, etc). - - If you're not sure if an issue is relevant or appropriate, e.g. if you have more of a question to ask, feel free to pop in the [Discord](https://discord.gg/63Ybb7J3as) and ask! -- **Please don't ghost an issue you've been assigned** - if you're no longer interested in working on it, that is totally okay! Just leave a comment on the issue so that I know you're no longer interested and I can reassign it to someone else. I will never be offended if you no longer want to work on an issue - I'm just trying to make sure that nobody's time and effort is wasted. +## Pull Requests -## A note on merging +> :information_source: There are two development branches: `experimental` and `develop`. These correspond to the `experimental` and `nightly` tags on Docker Hub, respectively. In general, `experimental` is for large or breaking changes, while `develop` is for smaller, more incremental changes. PRs will be merged once the following criteria are met: - All CI checks pass - At least one _maintainer_ has reviewed your PR -All PRs to `develop` will be squashed. All PRs to `main` will be merge commits. This is to ensure that the commit history is clean and easy to follow, and to ensure that the changelog generator works properly. +All PRs to `experimental` will be squashed. All PRs to `develop` from `experimental` and to `main` will be merge commits. This is to ensure that the commit history is clean and easy to follow, and to ensure that the changelog generator works properly. Thanks for considering contributing to Stump! :heart: diff --git a/.github/actions/build-desktop/action.yml b/.github/actions/build-desktop/action.yml index 615d3cb8e..da8af1f40 100644 --- a/.github/actions/build-desktop/action.yml +++ b/.github/actions/build-desktop/action.yml @@ -21,10 +21,7 @@ runs: # fi - name: Setup rust - uses: ./.github/actions/setup-cargo - - - name: Generate Prisma client - uses: ./.github/actions/setup-prisma + uses: ./.github/actions/setup-rust - name: Copy bundled web app uses: actions/download-artifact@v3 diff --git a/.github/actions/build-docker/action.yml b/.github/actions/build-docker/action.yml index c78951180..d0f82a78b 100644 --- a/.github/actions/build-docker/action.yml +++ b/.github/actions/build-docker/action.yml @@ -37,10 +37,7 @@ runs: shell: bash - name: Setup rust - uses: ./.github/actions/setup-cargo - - - name: Generate Prisma client - uses: ./.github/actions/setup-prisma + uses: ./.github/actions/setup-rust # We only need QEMU when an arm* platform is targeted - name: Check QEMU requirement @@ -74,6 +71,7 @@ runs: context: . build-args: | "GIT_REV=${{ env.GIT_REV }}" + "TAGS=${{ env.TAGS }}" file: docker/Dockerfile platforms: ${{ inputs.platforms }} load: ${{ inputs.load }} @@ -81,7 +79,7 @@ runs: tags: ${{ env.TAGS }} - name: Discord notification - if: ${{ success() && inputs.push == 'true' }} + if: ${{ success() && inputs.push == 'true' && inputs.discord-webhook != '' }} env: DISCORD_WEBHOOK: ${{ inputs.discord-webhook }} uses: 'Ilshidur/action-discord@0.3.2' diff --git a/.github/actions/build-server/action.yml b/.github/actions/build-server/action.yml index f8a96739a..010103e57 100644 --- a/.github/actions/build-server/action.yml +++ b/.github/actions/build-server/action.yml @@ -12,19 +12,8 @@ runs: - name: Checkout project uses: actions/checkout@v3 - # - name: Configure environment - # run: | - # if [[ ${{ inputs.platform }} == 'linux' || ${{ inputs.platform }} == 'windows' ]]; then - # echo "RUN_SETUP=false" >> $GITHUB_ENV - # else - # echo "RUN_SETUP=true" >> $GITHUB_ENV - # fi - - name: Setup rust - uses: ./.github/actions/setup-cargo - - - name: Generate Prisma client - uses: ./.github/actions/setup-prisma + uses: ./.github/actions/setup-rust - name: Copy bundled web app uses: actions/download-artifact@v3 diff --git a/.github/actions/build-web/action.yml b/.github/actions/build-web/action.yml index 9a39f4517..3c15afcb4 100644 --- a/.github/actions/build-web/action.yml +++ b/.github/actions/build-web/action.yml @@ -7,17 +7,31 @@ runs: - name: Checkout project uses: actions/checkout@v3 - - name: Setup pnpm - uses: ./.github/actions/setup-pnpm + - uses: actions/setup-node@v4 + with: + node-version: '20.0.0' + + - name: Install yarn + shell: bash + run: npm install -g yarn + + - uses: actions/setup-node@v4 + with: + node-version: '20.0.0' + cache: 'yarn' + + - name: Install yarn + shell: bash + run: npm install -g yarn - name: Install dependencies shell: bash - run: pnpm install + run: yarn install working-directory: apps/web - name: Build app shell: bash - run: pnpm run build + run: yarn build working-directory: apps/web - name: Upload bundle diff --git a/.github/actions/setup-cargo/action.yml b/.github/actions/setup-cargo/action.yml deleted file mode 100644 index 999b65639..000000000 --- a/.github/actions/setup-cargo/action.yml +++ /dev/null @@ -1,45 +0,0 @@ -name: 'Setup system dependencies' -description: 'Install system dependencies and setup cache' - -runs: - using: 'composite' - steps: - - name: Configure environment - run: | - if [[ ${{ runner.name }} == 'oromei-ubuntu' || ${{ runner.os }} == 'Windows' ]]; then - echo "RUN_SETUP=false" >> $GITHUB_ENV - else - echo "RUN_SETUP=true" >> $GITHUB_ENV - fi - shell: bash - - - name: System setup - if: ${{ env.RUN_SETUP == 'true' }} - shell: bash - run: CHECK_NODE=0 CHECK_CARGO=0 DEV_SETUP=0 ./scripts/system-setup.sh - - - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: 1.72.1 - profile: minimal - override: true - components: rustfmt, clippy - - - name: Cache cargo registry - uses: actions/cache@v3 - with: - path: ~/.cargo/registry - key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo index - uses: actions/cache@v3 - with: - path: ~/.cargo/git - key: ${{ runner.os }}-cargo-index-${{ hashFiles('**/Cargo.lock') }} - - - name: Cache cargo build - uses: actions/cache@v3 - with: - path: target - key: ${{ runner.os }}-cargo-build-target-${{ hashFiles('**/Cargo.lock') }} diff --git a/.github/actions/setup-pnpm/action.yaml b/.github/actions/setup-pnpm/action.yaml deleted file mode 100644 index a21fae754..000000000 --- a/.github/actions/setup-pnpm/action.yaml +++ /dev/null @@ -1,30 +0,0 @@ -name: PNPM Setup -description: Setup PNPM and cache PNPM dependencies -runs: - using: 'composite' - steps: - - uses: pnpm/action-setup@v2 - name: Install pnpm - id: pnpm-install - with: - version: 7 - run_install: false - - - name: Get pnpm store directory - id: pnpm-cache-store - shell: bash - run: | - echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT - - - uses: actions/cache@v3 - name: Setup pnpm cache - id: pnpm-cache - with: - path: ${{ steps.pnpm-cache-store.outputs.STORE_PATH }} - key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} - restore-keys: | - ${{ runner.os }}-pnpm-store- - - - name: Install dependencies - shell: bash - run: pnpm install --no-frozen-lockfile diff --git a/.github/actions/setup-prisma/action.yaml b/.github/actions/setup-prisma/action.yaml deleted file mode 100644 index 1f9d7ded9..000000000 --- a/.github/actions/setup-prisma/action.yaml +++ /dev/null @@ -1,17 +0,0 @@ -name: Prisma Setup -description: Generate/cache Prisma client -runs: - using: 'composite' - steps: - - name: Cache Prisma client - id: cache-prisma - uses: actions/cache@v3 - with: - path: core/src/prisma.rs - key: ${{ runner.os }}-prisma-${{ hashFiles('**/schema.prisma') }} - - - name: Generate Prisma client - # working-directory: core - if: steps.cache-prisma.outputs.cache-hit != 'true' - shell: bash - run: cargo prisma generate --schema=./core/prisma/schema.prisma diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 000000000..ad431e256 --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -0,0 +1,44 @@ +name: 'Setup system dependencies' +description: 'Install system dependencies and setup cache' + +runs: + using: 'composite' + steps: + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: 1.77.2 + profile: minimal + override: true + components: rustfmt, clippy + + - name: Configure environment + if: ${{ runner.name != 'oromei-ubuntu' && runner.os != 'Windows' }} + shell: bash + run: CHECK_NODE=0 CHECK_CARGO=0 DEV_SETUP=0 ./scripts/system-setup.sh + + - name: Cache Rust Dependencies + uses: Swatinem/rust-cache@v2 + with: + shared-key: stump-rust-cache + + - name: Restore cached Prisma client + id: cache-prisma-restore + uses: actions/cache/restore@v3 + with: + path: core/src/prisma.rs + key: ${{ runner.os }}-prisma-${{ hashFiles('**/schema.prisma') }} + + - name: Generate Prisma client + if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }} + shell: bash + run: cargo prisma generate --schema=./core/prisma/schema.prisma + + - name: Save Prisma client + id: cache-prisma-save + if: ${{ steps.cache-prisma-restore.outputs.cache-hit != 'true' }} + uses: actions/cache/save@v3 + with: + path: core/src/prisma.rs + key: ${{ runner.os }}-prisma-${{ hashFiles('**/schema.prisma') }} + restore-keys: ${{ runner.os }}-prisma-${{ hashFiles('**/schema.prisma') }} diff --git a/.github/workflows/build_nix.yml b/.github/workflows/build_nix.yml index af420f537..15a8f967b 100644 --- a/.github/workflows/build_nix.yml +++ b/.github/workflows/build_nix.yml @@ -15,5 +15,3 @@ jobs: - uses: cachix/install-nix-action@v17 - name: test run: nix develop --command "pkg-config" "--libs" "--cflags" "gdk-3.0" "gdk-3.0 >= 3.22" - # - name: Building package - # run: nix develop --command pnpm core run setup && cargo check diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 32f5efd4c..6a846f70f 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -10,16 +10,13 @@ jobs: check-rust: if: "!contains(github.event.pull_request.head.ref, 'release/v')" name: Rust checks - runs-on: [self-hosted] + runs-on: [ubuntu-22.04] steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup cargo - uses: ./.github/actions/setup-cargo - - - name: Setup prisma - uses: ./.github/actions/setup-prisma + - name: Setup rust + uses: ./.github/actions/setup-rust - name: Run cargo checks run: | @@ -33,13 +30,26 @@ jobs: check-typescript: if: "!contains(github.event.pull_request.head.ref, 'release/v')" name: TypeScript checks - runs-on: [self-hosted] + runs-on: [ubuntu-22.04] steps: - name: Checkout repository uses: actions/checkout@v3 - - name: Setup PNPM and TypeScript - uses: ./.github/actions/setup-pnpm + - uses: actions/setup-node@v4 + with: + node-version: '20.0.0' + + - name: Install yarn + shell: bash + run: npm install -g yarn + + - uses: actions/setup-node@v4 + with: + node-version: '20.0.0' + cache: 'yarn' + + - name: Install dependencies + run: yarn install - name: Run TypeScript lints - run: pnpm lint + run: yarn lint diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml new file mode 100644 index 000000000..14ae65ec0 --- /dev/null +++ b/.github/workflows/experimental.yml @@ -0,0 +1,37 @@ +name: 'Stump Experimental CI' + +on: + pull_request: + branches: + - experimental + push: + branches: + - experimental + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }} + +jobs: + nightly-docker-build: + name: Build docker image + runs-on: [ubuntu-22.04] + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup and build docker image + uses: ./.github/actions/build-docker + with: + username: ${{ env.DOCKER_USERNAME }} + password: ${{ env.DOCKER_PASSWORD }} + tags: 'experimental' + load: ${{ github.event_name == 'pull_request' }} + push: ${{ github.event_name == 'push' }} + platforms: 'linux/amd64' + # platforms: 'linux/arm64/v8,linux/arm/v7,linux/amd64' + discord-webhook: ${{ secrets.DISCORD_WEBHOOK }} diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index aae932f27..529aa7359 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -18,8 +18,9 @@ env: jobs: nightly-docker-build: + if: "!contains(github.event.pull_request.head.ref, 'release/v')" name: Build docker image - runs-on: [self-hosted] + runs-on: [ubuntu-22.04] steps: - name: Checkout repository uses: actions/checkout@v3 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index effff998d..053be2d73 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: parse-semver: if: contains(github.event.pull_request.head.ref, 'release/v') name: Parse semver version - runs-on: [self-hosted] + runs-on: [self-hosted, ubuntu-22.04] outputs: version: ${{ steps.tag.outputs.version }} tags: ${{ steps.docker-tags.outputs.tags }} @@ -42,7 +42,7 @@ jobs: push-or-load: if: contains(github.event.pull_request.head.ref, 'release/v') name: Configure docker load/push - runs-on: [self-hosted] + runs-on: [self-hosted, ubuntu-22.04] outputs: load: ${{ steps.configure.outputs.load }} push: ${{ steps.configure.outputs.push }} @@ -70,7 +70,7 @@ jobs: build-stable-docker: if: contains(github.event.pull_request.head.ref, 'release/v') name: Build docker image - runs-on: [self-hosted] + runs-on: [ubuntu-22.04] needs: [parse-semver, push-or-load] steps: - name: Checkout repository diff --git a/.gitignore b/.gitignore index 642aed0f8..a9e13c577 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ -# OS .DS_Store -# Logs logs/ +!packages/browser/**/logs/ *.log -# Cache .eslintcache .idea .npm @@ -14,7 +12,6 @@ logs/ !.vscode/extensions.json !.vscode/*.todo -# Directories build/ coverage/ cjs/ @@ -30,17 +27,14 @@ node_modules/ target/ .next .netlify -.pnpm-store .vercel -# Custom *.min.js *.map *.tsbuildinfo .env docker-compose.yaml -# rust core/integration-tests/.* core/integration-tests/*libraries* static diff --git a/.npmrc b/.npmrc index fa4e09523..9c9c396c0 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,2 @@ -strict-peer-dependencies=false \ No newline at end of file +strict-peer-dependencies=false +node-linker=hoisted \ No newline at end of file diff --git a/.prettierignore b/.prettierignore index a144a282b..3e7b5e914 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,15 +1,13 @@ -# Files generated.ts *-lock.* -# Directories node_modules target dist build .next +.expo -# locale files -interface/src/i18n/locales/*.json +packages/i18n/src/locales/*.json -CHANGELOG.md \ No newline at end of file +CHANGELOG.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 4a011ef99..396c09d41 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,7 +8,10 @@ "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" }, - "tailwindCSS.experimental.classRegex": [["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"]], + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], "tailwindCSS.classAttributes": ["class", "className", ".*CLASSES", ".*VARIANTS"], "typescript.tsdk": "node_modules/typescript/lib" } diff --git a/Cargo.lock b/Cargo.lock index 9069dfb7d..c1bed6962 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,11 +117,17 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "anstream" -version = "0.6.4" +version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" dependencies = [ "anstyle", "anstyle-parse", @@ -177,6 +183,19 @@ version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" +[[package]] +name = "async-channel" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d4d23bcc79e27423727b36823d86233aad06dfea531837b038394d11e9928" +dependencies = [ + "concurrent-queue", + "event-listener", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + [[package]] name = "async-stream" version = "0.3.5" @@ -196,18 +215,18 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] name = "async-trait" -version = "0.1.74" +version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -250,7 +269,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" dependencies = [ "flate2", - "http", + "http 0.2.9", "log", "native-tls", "serde", @@ -289,9 +308,9 @@ dependencies = [ "bytes", "futures-util", "headers", - "http", - "http-body", - "hyper", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "itoa 1.0.5", "matchit", "memchr", @@ -322,8 +341,8 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "mime", "rustversion", "tower-layer", @@ -339,7 +358,7 @@ dependencies = [ "axum", "bytes", "futures-util", - "http", + "http 0.2.9", "mime", "pin-project-lite", "serde", @@ -360,7 +379,7 @@ dependencies = [ "heck 0.4.0", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -412,6 +431,12 @@ version = "0.21.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35636a1494ede3b646cc98f74f8e62c773a38a659ebc777a2cf26b9b74171df9" +[[package]] +name = "base64" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" + [[package]] name = "base64ct" version = "1.6.0" @@ -420,11 +445,11 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bcrypt" -version = "0.15.0" +version = "0.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d1c9c15093eb224f0baa400f38fcd713fc1391a6f1c389d886beef146d60a3" +checksum = "e65938ed058ef47d92cf8b346cc76ef48984572ade631927e9937b5ffc7662c7" dependencies = [ - "base64 0.21.5", + "base64 0.22.0", "blowfish", "getrandom 0.2.11", "subtle", @@ -462,7 +487,7 @@ dependencies = [ "regex", "rustc-hash", "shlex", - "syn 2.0.31", + "syn 2.0.58", "which", ] @@ -652,6 +677,12 @@ dependencies = [ "toml 0.5.10", ] +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cbc" version = "0.1.2" @@ -704,7 +735,7 @@ checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" dependencies = [ "byteorder", "fnv", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -733,9 +764,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.31" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" +checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" dependencies = [ "android-tzdata", "iana-time-zone", @@ -743,7 +774,34 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets 0.48.5", + "windows-targets 0.52.0", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", ] [[package]] @@ -769,9 +827,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" dependencies = [ "clap_builder", "clap_derive", @@ -779,33 +837,33 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" dependencies = [ "anstream", "anstyle", "clap_lex", - "strsim", + "strsim 0.11.1", ] [[package]] name = "clap_derive" -version = "4.4.7" +version = "4.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" +checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" dependencies = [ - "heck 0.4.0", + "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] name = "clap_lex" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" [[package]] name = "cli" @@ -913,6 +971,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "concurrent-queue" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "connection-string" version = "0.1.14" @@ -1062,6 +1129,44 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "futures", + "is-terminal", + "itertools 0.10.5", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "tokio", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools 0.10.5", +] + [[package]] name = "crossbeam-channel" version = "0.5.6" @@ -1292,7 +1397,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn 1.0.107", ] @@ -1540,7 +1645,7 @@ dependencies = [ "schema-ast", "serde", "serde_json", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -1679,9 +1784,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" -version = "0.4.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4adbf0983fe06bd3a5c19c8477a637c2389feb0994eca7a59e3b961054aa7c0a" +checksum = "2b73807008a3c7f171cc40312f37d95ef0396e048b5848d775f54b1a4dd4a0d3" dependencies = [ "serde", ] @@ -1696,6 +1801,27 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "event-listener" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "332f51cb23d20b0de8458b86580878211da09bcd4503cb579c225b3d124cabb3" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "exr" version = "1.5.2" @@ -1789,7 +1915,7 @@ checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "windows-sys 0.42.0", ] @@ -1825,7 +1951,7 @@ dependencies = [ "futures-sink", "nanorand", "pin-project", - "spin 0.9.8", + "spin", ] [[package]] @@ -1858,15 +1984,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "fsevent-sys" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" -dependencies = [ - "libc", -] - [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1885,9 +2002,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" dependencies = [ "futures-channel", "futures-core", @@ -1900,9 +2017,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", @@ -1910,15 +2027,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-executor" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" dependencies = [ "futures-core", "futures-task", @@ -1927,32 +2044,32 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-macro" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] name = "futures-sink" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-timer" @@ -1962,9 +2079,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.29" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-channel", "futures-core", @@ -2221,7 +2338,7 @@ dependencies = [ "aho-corasick 1.1.2", "bstr", "log", - "regex-automata 0.4.3", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -2312,8 +2429,8 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", - "indexmap 2.0.2", + "http 0.2.9", + "indexmap 2.2.6", "slab", "tokio", "tokio-util", @@ -2380,7 +2497,7 @@ dependencies = [ "base64 0.21.5", "bytes", "headers-core", - "http", + "http 0.2.9", "httpdate", "mime", "sha1", @@ -2392,7 +2509,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" dependencies = [ - "http", + "http 0.2.9", ] [[package]] @@ -2410,6 +2527,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.1.19" @@ -2485,6 +2608,17 @@ dependencies = [ "itoa 1.0.5", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.5", +] + [[package]] name = "http-body" version = "0.4.5" @@ -2492,7 +2626,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", - "http", + "http 0.2.9", + "pin-project-lite", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", "pin-project-lite", ] @@ -2531,8 +2688,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "httparse", "httpdate", "itoa 1.0.5", @@ -2544,18 +2701,40 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa 1.0.5", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" -version = "0.24.1" +version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d78e1e73ec14cf7375674f74d7dde185c8206fd9dea6fb6295e8a98098aaa97" +checksum = "a0bea761b46ae2b24eb4aef630d8d1c398157b6fc29e6350ecf090a0b70c952c" dependencies = [ "futures-util", - "http", - "hyper", + "http 1.1.0", + "hyper 1.2.0", + "hyper-util", "rustls", + "rustls-pki-types", "tokio", "tokio-rustls", + "tower-service", ] [[package]] @@ -2565,12 +2744,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.27", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.2.0", + "pin-project-lite", + "socket2 0.5.5", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.53" @@ -2690,9 +2889,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.2" +version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown 0.14.1", @@ -2701,9 +2900,9 @@ dependencies = [ [[package]] name = "indicatif" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" dependencies = [ "console", "instant", @@ -2781,7 +2980,7 @@ version = "0.0.1" dependencies = [ "async-trait", "dotenv", - "reqwest", + "reqwest 0.12.3", "serde_json", "thiserror", "tokio", @@ -2848,9 +3047,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.12.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] @@ -2974,6 +3173,21 @@ dependencies = [ "serde_json", ] +[[package]] +name = "jsonwebtoken" +version = "9.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ae10193d25051e74945f1ea2d0b42e03cc3b890f7e4cc5faa44997d808193f" +dependencies = [ + "base64 0.21.5", + "js-sys", + "pem", + "ring", + "serde", + "serde_json", + "simple_asn1", +] + [[package]] name = "keccak" version = "0.1.4" @@ -3122,6 +3336,17 @@ dependencies = [ "safemem", ] +[[package]] +name = "linemux" +version = "0.3.0" +source = "git+https://github.com/jmagnuson/linemux.git?rev=acaafc602afac5d7a9cd3e087dafc937cac1e364#acaafc602afac5d7a9cd3e087dafc937cac1e364" +dependencies = [ + "futures-util", + "notify", + "pin-project-lite", + "tokio", +] + [[package]] name = "link-cplusplus" version = "1.0.8" @@ -3145,9 +3370,9 @@ checksum = "f9275e0933cf8bb20f008924c0cb07a0692fe54d8064996520bf998de9eb79aa" [[package]] name = "local-ip-address" -version = "0.5.6" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66357e687a569abca487dc399a9c9ac19beb3f13991ed49f00c144e02cbd42ab" +checksum = "136ef34e18462b17bf39a7826f8f3bbc223341f8e83822beb8b77db9a3d49696" dependencies = [ "libc", "neli", @@ -3349,7 +3574,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "953cbbb6f9ba4b9304f4df79b98cdc9d14071ed93065a9fca11c00c5d9181b66" dependencies = [ - "hyper", + "hyper 0.14.27", "indexmap 1.9.2", "ipnet", "metrics 0.19.0", @@ -3518,12 +3743,12 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http", + "http 0.2.9", "httparse", "log", "memchr", "mime", - "spin 0.9.8", + "spin", "version_check", ] @@ -3640,20 +3865,19 @@ dependencies = [ [[package]] name = "notify" -version = "5.1.0" +version = "6.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58ea850aa68a06e48fdb069c0ec44d0d64c8dbffa49bf3b6f7f0a901fdea1ba9" +checksum = "5738a2795d57ea20abec2d6d76c6081186709c0024187cd5977265eda6598b51" dependencies = [ "bitflags 1.3.2", "crossbeam-channel", "filetime", - "fsevent-sys", "inotify", "kqueue", "libc", "mio", "walkdir", - "windows-sys 0.42.0", + "windows-sys 0.45.0", ] [[package]] @@ -3711,6 +3935,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-integer" version = "0.1.45" @@ -3844,6 +4074,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" + [[package]] name = "opaque-debug" version = "0.3.0" @@ -4007,6 +4243,12 @@ dependencies = [ "system-deps 6.0.3", ] +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.11.2" @@ -4037,7 +4279,7 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", "winapi", ] @@ -4050,7 +4292,7 @@ checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" dependencies = [ "cfg-if", "libc", - "redox_syscall 0.2.16", + "redox_syscall", "smallvec", "windows-sys 0.42.0", ] @@ -4114,7 +4356,7 @@ dependencies = [ "deflate", "fax", "globalcache", - "indexmap 2.0.2", + "indexmap 2.2.6", "istring", "itertools 0.10.5", "jpeg-decoder", @@ -4172,6 +4414,16 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +[[package]] +name = "pem" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8fcc794035347fb64beda2d3b462595dd2753e3f268d89c5aae77e8cf2c310" +dependencies = [ + "base64 0.21.5", + "serde", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -4388,6 +4640,34 @@ dependencies = [ "xml-rs", ] +[[package]] +name = "plotters" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" + +[[package]] +name = "plotters-svg" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +dependencies = [ + "plotters-backend", +] + [[package]] name = "png" version = "0.17.7" @@ -4431,7 +4711,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -4486,7 +4766,7 @@ dependencies = [ "tokio", "tracing", "user-facing-errors", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -4496,12 +4776,12 @@ source = "git+https://github.com/Brendonovich/prisma-client-rust.git?tag=0.6.11# dependencies = [ "directories", "flate2", - "http", + "http 0.2.9", "prisma-client-rust-sdk", "proc-macro2", "quote", "regex", - "reqwest", + "reqwest 0.11.22", "serde", "serde_json", "serde_path_to_error", @@ -4569,7 +4849,7 @@ dependencies = [ "regex", "serde", "serde_json", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -4615,9 +4895,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" dependencies = [ "unicode-ident", ] @@ -4688,7 +4968,7 @@ dependencies = [ "tracing", "tracing-core", "url", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -4724,7 +5004,7 @@ dependencies = [ "serde_json", "thiserror", "user-facing-errors", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -4768,7 +5048,7 @@ dependencies = [ "tracing-subscriber", "url", "user-facing-errors", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -4799,9 +5079,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -4945,9 +5225,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -4955,9 +5235,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -4981,15 +5261,6 @@ dependencies = [ "bitflags 1.3.2", ] -[[package]] -name = "redox_syscall" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" -dependencies = [ - "bitflags 1.3.2", -] - [[package]] name = "redox_users" version = "0.4.3" @@ -4997,19 +5268,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ "getrandom 0.2.11", - "redox_syscall 0.2.16", + "redox_syscall", "thiserror", ] [[package]] name = "regex" -version = "1.10.2" +version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343" +checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick 1.1.2", "memchr", - "regex-automata 0.4.3", + "regex-automata 0.4.6", "regex-syntax 0.8.2", ] @@ -5024,9 +5295,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f" +checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick 1.1.2", "memchr", @@ -5088,10 +5359,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", - "hyper-rustls", + "http 0.2.9", + "http-body 0.4.5", + "hyper 0.14.27", "hyper-tls", "ipnet", "js-sys", @@ -5101,14 +5371,51 @@ dependencies = [ "once_cell", "percent-encoding", "pin-project-lite", - "rustls", - "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "system-configuration", "tokio", "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e6cc1e89e689536eb5aeede61520e874df5a4707df811cd5da4aa5fbb2aae19" +dependencies = [ + "base64 0.22.0", + "bytes", + "futures-core", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.2.0", + "hyper-rustls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pemfile", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", "tokio-rustls", "tower-service", "url", @@ -5116,7 +5423,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "webpki-roots", - "winreg", + "winreg 0.52.0", ] [[package]] @@ -5145,31 +5452,17 @@ dependencies = [ [[package]] name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - -[[package]] -name = "ring" -version = "0.17.7" +version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "688c63d65483050968b2a8937f7995f443e27041a0f7700aa59b0822aedebb74" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", + "cfg-if", "getrandom 0.2.11", "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.48.0", + "spin", + "untrusted", + "windows-sys 0.52.0", ] [[package]] @@ -5215,7 +5508,7 @@ dependencies = [ "quote", "rust-embed-utils", "shellexpand", - "syn 2.0.31", + "syn 2.0.58", "walkdir", ] @@ -5261,9 +5554,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.27" +version = "0.38.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfeae074e687625746172d639330f1de242a178bf3189b51e35a7a21573513ac" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" dependencies = [ "bitflags 2.4.0", "errno", @@ -5274,33 +5567,43 @@ dependencies = [ [[package]] name = "rustls" -version = "0.21.7" +version = "0.22.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8d6c9f025a446bc4d18ad9632e69aec8f287aa84499ee335599fabd20c3fd8" +checksum = "99008d7ad0bbbea527ec27bddbc0e432c5b87d8175178cee68d2eec9c4a1813c" dependencies = [ "log", - "ring 0.16.20", + "ring", + "rustls-pki-types", "rustls-webpki", - "sct", + "subtle", + "zeroize", ] [[package]] name = "rustls-pemfile" -version = "1.0.3" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d3987094b1d07b653b7dfdc3f70ce9a1da9c51ac18c1b06b662e4f9a0e9f4b2" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" dependencies = [ - "base64 0.21.5", + "base64 0.22.0", + "rustls-pki-types", ] +[[package]] +name = "rustls-pki-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" + [[package]] name = "rustls-webpki" -version = "0.101.6" +version = "0.102.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe" +checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -5390,16 +5693,6 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - [[package]] name = "security-framework" version = "2.7.0" @@ -5472,18 +5765,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" dependencies = [ "serde_derive", ] [[package]] name = "serde-untagged" -version = "0.1.2" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c38885c2d9d8f038478583b7acf8f6029d020ba4b20e9dcaeb8799d67a04aae7" +checksum = "6a160535368dfc353348e7eaa299156bd508c60c45a9249725f5f6d370d82a66" dependencies = [ "erased-serde", "serde", @@ -5513,13 +5806,13 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.197" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -5529,7 +5822,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224e6a14f315852940f3ec103125aa6482f0e224732ed91ed3330ed633077c34" dependencies = [ "form_urlencoded", - "indexmap 2.0.2", + "indexmap 2.2.6", "itoa 1.0.5", "ryu", "serde", @@ -5537,11 +5830,11 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "12dc5c46daa8e9fdf4f5e71b6cf9a53f2487da0e86e55808e2d35539666497dd" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.6", "itoa 1.0.5", "ryu", "serde", @@ -5582,9 +5875,9 @@ dependencies = [ [[package]] name = "serde_spanned" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12022b835073e5b11e90a14f86838ceb1c8fb0325b72416845c487ac0fa95e80" +checksum = "eb3622f419d1296904700073ea6cc23ad690adbd66f13ea683df73298736f0c1" dependencies = [ "serde", ] @@ -5749,6 +6042,18 @@ dependencies = [ "libc", ] +[[package]] +name = "simple_asn1" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" +dependencies = [ + "num-bigint", + "num-traits", + "thiserror", + "time", +] + [[package]] name = "siphasher" version = "0.3.10" @@ -5763,18 +6068,18 @@ checksum = "04d2ecae5fcf33b122e2e6bd520a57ccf152d2dde3b38c71039df1a6867264ee" [[package]] name = "slab" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" -version = "1.10.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" [[package]] name = "snafu" @@ -5876,12 +6181,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -5944,7 +6243,7 @@ dependencies = [ "tracing-futures", "url", "user-facing-errors", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -5976,7 +6275,7 @@ dependencies = [ "tracing-futures", "tracing-opentelemetry", "user-facing-errors", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -6068,6 +6367,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "strum" version = "0.22.0" @@ -6094,8 +6399,10 @@ name = "stump_core" version = "0.0.1" dependencies = [ "alphanumeric-sort", + "async-channel", "async-trait", "chrono", + "criterion", "cuid", "data-encoding", "dirs 5.0.1", @@ -6104,14 +6411,14 @@ dependencies = [ "globset", "image", "infer 0.15.0", - "itertools 0.12.0", + "itertools 0.12.1", "libc", "pdf", "pdfium-render", "prisma-client-rust", "rayon", "regex", - "ring 0.17.7", + "ring", "serde", "serde-xml-rs", "serde_json", @@ -6119,7 +6426,7 @@ dependencies = [ "tempfile", "thiserror", "tokio", - "toml 0.8.8", + "toml 0.8.12", "tracing", "tracing-appender", "tracing-subscriber", @@ -6127,7 +6434,7 @@ dependencies = [ "unrar", "urlencoding", "utoipa", - "uuid 1.6.1", + "uuid 1.8.0", "walkdir", "webp", "xml-rs", @@ -6156,18 +6463,19 @@ dependencies = [ "axum", "axum-extra", "axum-macros", - "base64 0.21.5", + "base64 0.22.0", "bcrypt", "chrono", "cli", "futures-util", - "hyper", + "hyper 0.14.27", + "jsonwebtoken", + "linemux", "local-ip-address", - "notify", "openssl", "prisma-client-rust", "rand 0.8.5", - "reqwest", + "reqwest 0.12.3", "serde", "serde-untagged", "serde_json", @@ -6206,9 +6514,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" dependencies = [ "proc-macro2", "quote", @@ -6217,9 +6525,9 @@ dependencies = [ [[package]] name = "sync_wrapper" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "synstructure" @@ -6320,7 +6628,7 @@ dependencies = [ "scopeguard", "serde", "unicode-segmentation", - "uuid 1.6.1", + "uuid 1.8.0", "windows 0.39.0", "windows-implement", "x11-dl", @@ -6355,7 +6663,7 @@ dependencies = [ "glob", "gtk", "heck 0.4.0", - "http", + "http 0.2.9", "ignore", "notify-rust", "objc", @@ -6384,7 +6692,7 @@ dependencies = [ "thiserror", "tokio", "url", - "uuid 1.6.1", + "uuid 1.8.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -6428,7 +6736,7 @@ dependencies = [ "tauri-utils", "thiserror", "time", - "uuid 1.6.1", + "uuid 1.8.0", "walkdir", ] @@ -6453,7 +6761,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc36898ad4acb6c381878acf903c320a36cf29b68b74f6e791d6045b6557128c" dependencies = [ "gtk", - "http", + "http 0.2.9", "http-range", "rand 0.8.5", "raw-window-handle", @@ -6462,7 +6770,7 @@ dependencies = [ "tauri-utils", "thiserror", "url", - "uuid 1.6.1", + "uuid 1.8.0", "webview2-com", "windows 0.39.0", ] @@ -6481,7 +6789,7 @@ dependencies = [ "tauri-runtime", "tauri-utils", "url", - "uuid 1.6.1", + "uuid 1.8.0", "webkit2gtk", "webview2-com", "windows 0.39.0", @@ -6539,15 +6847,14 @@ dependencies = [ [[package]] name = "tempfile" -version = "3.8.1" +version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", - "redox_syscall 0.4.1", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -6589,22 +6896,22 @@ checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" [[package]] name = "thiserror" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f11c217e1416d6f036b870f14e0413d480dbf28edbee1f877abaf0206af43bb7" +checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.51" +version = "1.0.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01742297787513b79cf8e29d1056ede1313e2420b7b3b15d0a768b4921f549df" +checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -6638,12 +6945,13 @@ dependencies = [ [[package]] name = "time" -version = "0.3.30" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a34ab300f2dee6e562c10a046fc05e358b29f9bf92277f30c3c8d82275f6f5" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa 1.0.5", + "num-conv", "powerfmt", "serde", "time-core", @@ -6658,13 +6966,24 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.15" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ + "num-conv", "time-core", ] +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + [[package]] name = "tinyvec" version = "1.6.0" @@ -6682,9 +7001,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.35.0" +version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "841d45b238a16291a4e1584e61820b8ae57d696cc5015c459c229ccc6990cc1c" +checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", @@ -6707,7 +7026,7 @@ checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -6722,11 +7041,12 @@ dependencies = [ [[package]] name = "tokio-rustls" -version = "0.24.1" +version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c28327cf380ac148141087fbfb9de9d7bd4e84ab5d2c28fbc911d753de8a7081" +checksum = "775e0c0f0adb3a2f22a00c4745d728b479985fc15ee7ca6a2608388c5569860f" dependencies = [ "rustls", + "rustls-pki-types", "tokio", ] @@ -6767,9 +7087,9 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.8" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35" +checksum = "e9dd1545e8208b4a5af1aa9bbd0b4cf7e9ea08fabc5d0a5c67fcaafa17433aa3" dependencies = [ "serde", "serde_spanned", @@ -6788,11 +7108,11 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.21.0" +version = "0.22.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03" +checksum = "8e40bb779c5187258fd7aad0eb68cb8706a0a81fa712fbea808ab43c4b8374c4" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.6", "serde", "serde_spanned", "toml_datetime", @@ -6825,7 +7145,7 @@ dependencies = [ "axum-core", "cookie", "futures-util", - "http", + "http 0.2.9", "parking_lot 0.12.1", "pin-project-lite", "tower-layer", @@ -6842,8 +7162,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "http-range-header", "httpdate", "mime", @@ -6866,8 +7186,8 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http", - "http-body", + "http 0.2.9", + "http-body 0.4.5", "http-range-header", "httpdate", "mime", @@ -6901,7 +7221,7 @@ checksum = "5b51233895d1173cae657a2ce44bd414efe20ae0971216f8ddf6ca528498a20c" dependencies = [ "async-trait", "axum-core", - "http", + "http 0.2.9", "parking_lot 0.12.1", "serde", "serde_json", @@ -6910,7 +7230,7 @@ dependencies = [ "tower-cookies", "tower-layer", "tower-service", - "uuid 1.6.1", + "uuid 1.8.0", ] [[package]] @@ -6945,7 +7265,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -7072,7 +7392,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http", + "http 0.2.9", "httparse", "log", "rand 0.8.5", @@ -7150,9 +7470,9 @@ checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" [[package]] name = "unrar" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c844cee902acdbe8942c0188cb1c2b7ab268928ceb926ef1550babd921757971" +checksum = "f4994bfae776d5c2ee22493a00742c77d58bfa5adbe10febe83d1ba7aff2ebdc" dependencies = [ "bitflags 1.3.2", "regex", @@ -7180,12 +7500,6 @@ dependencies = [ "void", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -7261,7 +7575,7 @@ version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d82b1bc5417102a73e8464c686eef947bdfb99fcdfc0a4f228e81afa9526470a" dependencies = [ - "indexmap 2.0.2", + "indexmap 2.2.6", "serde", "serde_json", "utoipa-gen", @@ -7277,7 +7591,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] @@ -7307,9 +7621,9 @@ dependencies = [ [[package]] name = "uuid" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e395fcf16a7a3d8127ec99782007af141946b4795001f876d54fb0d55978560" +checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom 0.2.11", "serde", @@ -7362,9 +7676,9 @@ checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "walkdir" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", @@ -7533,9 +7847,12 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.25.3" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1778a42e8b3b90bff8d0f5032bf22250792889a5cdc752aa0020c84abe3aaf10" +checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +dependencies = [ + "rustls-pki-types", +] [[package]] name = "webview2-com" @@ -7972,9 +8289,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" [[package]] name = "winnow" -version = "0.5.15" +version = "0.6.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c2e3184b9c4e92ad5167ca73039d0c42476302ab603e2fec4487511f38ccefc" +checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352" dependencies = [ "memchr", ] @@ -7989,6 +8306,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "winres" version = "0.1.12" @@ -8015,7 +8342,7 @@ dependencies = [ "glib", "gtk", "html5ever", - "http", + "http 0.2.9", "kuchiki", "libc", "log", @@ -8068,9 +8395,9 @@ dependencies = [ [[package]] name = "xml-rs" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" +checksum = "791978798f0597cfc70478424c2b4fdc2b7a8024aaff78497ef00f24ef674193" [[package]] name = "zerocopy" @@ -8089,7 +8416,7 @@ checksum = "dd7e48ccf166952882ca8bd778a43502c64f33bf94c12ebe2a7f08e5a0f6689f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.31", + "syn 2.0.58", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 186a1a343..bbfce37ab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,14 @@ members = [ [workspace.package] version = "0.0.1" -rust-version = "1.72.1" +rust-version = "1.77.2" [workspace.dependencies] -async-trait = "0.1.74" +async-trait = "0.1.80" async-stream = "0.3.5" -bcrypt = "0.15.0" +bcrypt = "0.15.1" +futures = "0.3.30" +futures-util = "0.3.30" prisma-client-rust = { git = "https://github.com/Brendonovich/prisma-client-rust.git", tag = "0.6.11", features = [ "sqlite-create-many", "migrations", @@ -27,13 +29,13 @@ prisma-client-rust-cli = { git = "https://github.com/Brendonovich/prisma-client- "sqlite", "mocking" ], default-features = false } -reqwest = { version = "0.11.22", default-features = false, features = [ "json", "rustls-tls" ] } -serde = { version = "1.0.193", features = ["derive"] } -serde_json = "1.0.108" +reqwest = { version = "0.12.3", default-features = false, features = [ "json", "rustls-tls" ] } +serde = { version = "1.0.197", features = ["derive"] } +serde_json = "1.0.115" specta = "1.0.5" -tempfile = "3.8.1" -thiserror = "1.0.51" -tokio = { version = "1.35.0", features = [ +tempfile = "3.10.1" +thiserror = "1.0.58" +tokio = { version = "1.37.0", features = [ # Provides sender/reciever channels "sync", # Tells the Tokio runtime to use the multi-thread scheduler. diff --git a/README.md b/README.md index 055a115a7..c528a8ad4 100644 --- a/README.md +++ b/README.md @@ -37,12 +37,12 @@ Stump is a free and open source comics, manga and digital book server with OPDS - [Core](#core) - [Crates](#crates) - [Docs](#docs) - - [Interface](#interface) - [Packages](#packages) - [Similar Projects 👯](#similar-projects-) +- [License 📝](#license-) -> **🚧 Disclaimer 🚧**: Stump is under active development and is an ongoing **WIP**. Anyone is welcome to try it out, but **DO NOT** expect a fully featured or bug-free experience. I will likely flatten the migrations immediately prior to the `0.1.0` release, which will break existing Stump databases. If you'd like to contribute and help expedite Stump's first release, please review the [developer guide](#developer-guide-). +> **🚧 Disclaimer 🚧**: Stump is under active development and is an ongoing **WIP**. Anyone is welcome to try it out, but **DO NOT** expect a fully featured or bug-free experience. If you'd like to contribute and help expedite Stump's first release, please review the [developer guide](#developer-guide-). ## Roadmap 🗺 @@ -51,7 +51,7 @@ The following items are the major targets for Stump's first release: - 📃 Full OPDS + OPDS Page Streaming support - 📕 EPUB, PDF, and CBZ/CBR support - 📚 Organize libraries with collections and reading lists -- 🔐 Role-based access-control with managed user accounts +- 🔐 Granular access-control with managed user accounts - 🚀 Easy setup and deployment using Docker or bare metal - 👀 Fully responsive, built-in UI with a dark mode - 🏃 Low resource utilization with excellent performance @@ -71,7 +71,7 @@ Feel free to reach out if you have anything else you'd like to see! ## Getting Started 🚀 -Stump isn't ready for normal usage yet. To give it a spin, it is reccommended to try the nightly [Docker image](https://hub.docker.com/r/aaronleopold/stump). If you're interested in development, or trying it from source, you can follow the [developer guide](#developer-guide-). +Stump isn't ready for normal usage yet. To give it a spin, it is recommended to try the nightly [Docker image](https://hub.docker.com/r/aaronleopold/stump). If you're interested in development, or trying it from source, you can follow the [developer guide](#developer-guide-). For more information about getting started, check out the [guides](https://stumpapp.dev/guides) available on the Stump website. @@ -81,7 +81,7 @@ Contributions are very **welcome**! Please review the [CONTRIBUTING.md](https:// A quick summary of the steps required to get going: -1. Install [pnpm](https://pnpm.io/installation), [rust](https://www.rust-lang.org/tools/install) and [node](https://nodejs.org/en/download/). +1. Install [yarn](https://yarnpkg.com/), [rust](https://www.rust-lang.org/tools/install) and [node](https://nodejs.org/en/download/). - If you're running Windows, you will need [Visual C++](https://docs.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist?view=msvc-170) - If you're running macOS on Apple Silicon, you'll need to install [Rosetta](https://support.apple.com/en-us/HT211861) 2. Install [cargo-watch](https://crates.io/crates/cargo-watch) @@ -94,14 +94,14 @@ A quick summary of the steps required to get going: This isn't strictly necessary, and is mostly beneficial for Linux users (it installs some system dependencies). Feel free to skip this step if you'd like, and instead just run: ```bash - pnpm run setup + yarn run setup ``` This will build the React app, generate the Prisma client, and generate the Rust-TypeScript types. To do any of these individually, you can run: ```bash # build the React app - pnpm web build + yarn web build # generate the Prisma client and Rust-TypeScript types cargo codegen # or cargo prisma generate --schema=./core/prisma/schema.prisma for just the Prisma client ``` @@ -112,11 +112,11 @@ A quick summary of the steps required to get going: ```bash # run the webapp + server - pnpm dev:web + yarn dev:web # run the desktop app + server - pnpm start:desktop + yarn start:desktop # run the docs website - pnpm docs dev + yarn docs dev ``` Or just `cargo` for the server (and other Rust apps): @@ -129,14 +129,14 @@ And that's it! #### Where to start? -If you aren't sure where to start, I recommend taking a look at [open issues](https://github.com/stumpapp/stump/issues). You can also check out the [milestones](https://github.com/stumpapp/stump/milestones) page for a more curated list of issues that need to be addressed. +If you aren't sure where to start, I recommend taking a look at [open issues](https://github.com/stumpapp/stump/issues). You can also check out the [current project board](https://github.com/orgs/stumpapp/projects/4) to see what's actively being worked on or planned. In general, the following areas are good places to start: - Translation, so Stump is accessible to as many people as possible - - [Crowdin](https://crowdin.com/project/stump) is being used for translations + - [Crowdin](https://crowdin.com/project/stump) is used for translations - Writing comprehensive tests -- Designing UI elements/sections or improving the existing UI/UX +- Designing and/improving UI/UX - Docker build optimizations, caching, etc - CI pipelines, automated releases and release notes, etc - And lots more! @@ -148,17 +148,19 @@ In general, the following areas are good places to start:
Click to expand -Stump has a monorepo structure managed by [pnpm workspaces](https://pnpm.io/workspaces) and [cargo workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html). The project is split into a number of different packages and crates, each with their own purpose: +Stump has a monorepo structure managed by [yarn workspaces](https://yarnpkg.com/features/workspaces) and [cargo workspaces](https://doc.rust-lang.org/cargo/reference/workspaces.html). The project is split into a number of different packages and crates, each with their own purpose: ### Apps Stand-alone applications that can be run independently, at `/apps` in the root of the project: - `desktop`: A React + Tauri desktop application -- `mobile`: A React Native application ([#125](https://github.com/stumpapp/stump/issues/125)) +- `expo`: A React Native application ([#125](https://github.com/stumpapp/stump/issues/125)) - `server`: An [Axum](https://github.com/tokio-rs/axum) HTTP server - `web`: A React application, the primary UI for both the built-in web app the server serves and the desktop app +The only exception to this is the `docs` app, which is a NextJS application and is located at `/docs` in the root of the project. + ### Core A Rust crate containing Stump's core functionalities, at `/core` in the root of the project @@ -176,10 +178,6 @@ Various Rust crates, at `/crates` in the root of the project: A NextJS application for the Stump documentation site at `/docs` in the root of the project -### Interface - -A React component that is essentially the "main" UI for Stump, at `/interface` in the root of the project. It is used in both the `web` and `desktop` apps - ### Packages Various TypeScript packages, at `/packages` in the root of the project: @@ -187,6 +185,7 @@ Various TypeScript packages, at `/packages` in the root of the project: - `api`: All of the API functions used by the `client` package - `client`: React-query config, hooks, and other client-side utilities - `components`: Shared React components for the web and desktop applications +- `browser`: A React component that is essentially the "main" UI for Stump on browser-based platforms. It is isolated in order to be re-used in the two browser-based apps: `web` and `desktop` - `types`: Shared TypeScript types for interfacing with Stump's core and API
@@ -201,3 +200,7 @@ There are a number of other projects that are similar to Stump, it certainly isn - [Komga](https://github.com/gotson/komga) - [Librum](https://github.com/Librum-Reader/Librum) - [oqurum](https://github.com/oqurum) (✨*Rust*✨) + +## License 📝 + +Stump is licensed under the [MIT License](https://www.tldrlegal.com/license/mit-license). This applies to the entire repository except for subfolders/packages which contain their own license file(s). In such cases, the license file(s) in the subfolder/package take precedence. diff --git a/apps/desktop/package.json b/apps/desktop/package.json index 9e8dc29a3..680c40f6a 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -8,16 +8,16 @@ "format": "cargo fmt --package stump_desktop", "tauri": "tauri", "start": "tauri dev --no-watch", - "start-bundled": "pnpm tauri dev --no-watch -- --features bundled-server", + "start-bundled": "yarn tauri dev --no-watch -- --features bundled-server", "vite": "vite --", "dev": "tauri dev", "build": "vite build && tauri build", "build:mac": "vite build && tauri build --target universal-apple-darwin", - "nuke": "pnpm exec rimraf -rf node_modules" + "nuke": "yarn exec rimraf -rf node_modules" }, "dependencies": { - "@stump/client": "workspace:*", - "@stump/interface": "workspace:*", + "@stump/client": "*", + "@stump/browser": "*", "@tanstack/react-query": "^4.28.0", "@tauri-apps/api": "^1.2.0", "react": "^18.2.0", diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json index 68bb741b5..0876ceaf7 100644 --- a/apps/desktop/src-tauri/tauri.conf.json +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -1,7 +1,7 @@ { "$schema": "../node_modules/@tauri-apps/cli/schema.json", "build": { - "beforeDevCommand": "pnpm vite --clearScreen=false", + "beforeDevCommand": "yarn vite --clearScreen=false", "devPath": "http://localhost:3000", "distDir": "../dist" }, diff --git a/apps/desktop/src/App.tsx b/apps/desktop/src/App.tsx index 195825909..afe69a9be 100644 --- a/apps/desktop/src/App.tsx +++ b/apps/desktop/src/App.tsx @@ -1,5 +1,5 @@ +import { StumpWebClient } from '@stump/browser' import { Platform } from '@stump/client' -import StumpInterface from '@stump/interface' import { invoke, os } from '@tauri-apps/api' import { useEffect, useMemo, useState } from 'react' @@ -54,13 +54,13 @@ export default function App() { } }, [platform]) - // I want to wait until platform is properly set before rendering the interface + // I want to wait until platform is properly set before rendering the app if (!mounted) { return null } return ( - Note: You need to have the Expo CLI installed, as well as the mobile SDKs (depending on where you want to run the app). See the [Expo docs](https://docs.expo.io/get-started/installation/) for more info. + +1. Clone the repo +2. Follow the [developer guide](https://github.com/aaronleopold/stump#developer-guide-) at the root of the Stump monorepo +3. Start the mobile app and the server: + + To start the server and mobile app concurrently, you can use the following command: + + ```bash + yarn dev:expo + ``` + + If you want to start the server and mobile app separately, you can use the following commands in two separate terminals: + + ```bash + cargo run --package stump_server + yarn workspace @stump/expo start --clear + ``` + +And that's it! + +## Contributing 🤝 + +Be sure to review the [CONTRIBUTING.md](https://github.com/aaronleopold/stump/tree/develop/.github/CONTRIBUTING.md) before getting started, as it overviews the guidelines and general process for contributing to the project. Once you've done that, it's as simple as: + +1. Fork the repo +2. Create a new branch +3. Make your changes +4. Open a PR (following the title and description guidelines) +5. Wait for review +6. Merge 😍 + +## Roadmap 🗺️ + +You can find the high-level roadmap for the Stump mobile app in the [documentation](https://www.stumpapp.dev/guides/mobile/app#planned-features). For a more granular view of what is coming, you can also take a look at the [project board](https://github.com/orgs/stumpapp/projects/8). + +## Acknowledgements 🙏 + +- Thanks to [@dancamdev](https://github.com/dancamdev) for bootstrapping this Expo project template 🙌 +- Thanks to [@LRotenberger](https://github.com/LRotenberger) for building out the initial POC for the mobile app 🚀 diff --git a/apps/expo/app.d.ts b/apps/expo/app.d.ts new file mode 100644 index 000000000..a13e3136b --- /dev/null +++ b/apps/expo/app.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/expo/app.json b/apps/expo/app.json new file mode 100644 index 000000000..155f0e3f5 --- /dev/null +++ b/apps/expo/app.json @@ -0,0 +1,8 @@ +{ + "expo": { + "scheme": "stump", + "name": "stump", + "slug": "stump", + "userInterfaceStyle": "automatic" + } +} diff --git a/apps/expo/assets/fonts/SpaceMono-Regular.ttf b/apps/expo/assets/fonts/SpaceMono-Regular.ttf new file mode 100755 index 000000000..28d7ff717 Binary files /dev/null and b/apps/expo/assets/fonts/SpaceMono-Regular.ttf differ diff --git a/apps/expo/assets/images/adaptive-icon.png b/apps/expo/assets/images/adaptive-icon.png new file mode 100644 index 000000000..03d6f6b6c Binary files /dev/null and b/apps/expo/assets/images/adaptive-icon.png differ diff --git a/interface/public/assets/favicon.ico b/apps/expo/assets/images/favicon.ico similarity index 100% rename from interface/public/assets/favicon.ico rename to apps/expo/assets/images/favicon.ico diff --git a/interface/public/assets/favicon.png b/apps/expo/assets/images/favicon.png similarity index 100% rename from interface/public/assets/favicon.png rename to apps/expo/assets/images/favicon.png diff --git a/interface/public/assets/stump-logo--irregular-lg.png b/apps/expo/assets/images/icon.png similarity index 100% rename from interface/public/assets/stump-logo--irregular-lg.png rename to apps/expo/assets/images/icon.png diff --git a/apps/expo/assets/images/splash.png b/apps/expo/assets/images/splash.png new file mode 100644 index 000000000..0e89705a9 Binary files /dev/null and b/apps/expo/assets/images/splash.png differ diff --git a/apps/expo/babel.config.js b/apps/expo/babel.config.js new file mode 100644 index 000000000..22eb9351e --- /dev/null +++ b/apps/expo/babel.config.js @@ -0,0 +1,7 @@ +module.exports = function (api) { + api.cache(true) + return { + plugins: ['nativewind/babel', 'react-native-reanimated/plugin'], + presets: ['babel-preset-expo'], + } +} diff --git a/apps/expo/index.js b/apps/expo/index.js new file mode 100644 index 000000000..60973a208 --- /dev/null +++ b/apps/expo/index.js @@ -0,0 +1,5 @@ +import { registerRootComponent } from 'expo' + +import { AppEntry } from './src/AppEntry' + +registerRootComponent(AppEntry) diff --git a/apps/expo/metro.config.js b/apps/expo/metro.config.js new file mode 100644 index 000000000..89b788fef --- /dev/null +++ b/apps/expo/metro.config.js @@ -0,0 +1,22 @@ +// Learn more https://docs.expo.dev/guides/monorepos +const { getDefaultConfig } = require('expo/metro-config') +const path = require('path') + +// Find the project and workspace directories +const projectRoot = __dirname +const workspaceRoot = path.resolve(projectRoot, '../..') + +const config = getDefaultConfig(projectRoot) + +// 1. Watch all files within the monorepo +config.watchFolders = [workspaceRoot] +// 2. Let Metro know where to resolve packages and in what order +config.resolver.nodeModulesPaths = [ + path.resolve(projectRoot, 'node_modules'), + path.resolve(workspaceRoot, 'node_modules'), +] +// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths` +// TODO: Needs fix - Although not ideal, this must be set to `false` in order to avoid a dependency collision on uuid +config.resolver.disableHierarchicalLookup = false + +module.exports = config diff --git a/apps/expo/package.json b/apps/expo/package.json new file mode 100644 index 000000000..129d3609e --- /dev/null +++ b/apps/expo/package.json @@ -0,0 +1,56 @@ +{ + "main": "index.js", + "scripts": { + "start": "expo start", + "android": "expo start --android", + "ios": "expo start --ios", + "web": "expo start --web" + }, + "dependencies": { + "@epubjs-react-native/core": "^1.3.0", + "@epubjs-react-native/expo-file-system": "^1.0.0", + "@hookform/resolvers": "^3.3.2", + "@react-native-async-storage/async-storage": "1.21.0", + "@react-navigation/bottom-tabs": "^6.5.12", + "@react-navigation/native": "^6.1.10", + "@react-navigation/native-stack": "^6.9.18", + "@stump/api": "*", + "@tanstack/react-query": "^4.20.4", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "expo": "^50.0.6", + "expo-constants": "~15.4.5", + "expo-image": "^1.10.6", + "expo-splash-screen": "~0.26.4", + "expo-status-bar": "~1.11.1", + "lucide-react-native": "^0.330.0", + "nativewind": "^2.0.11", + "react": "18.2.0", + "react-dom": "18.2.0", + "react-hook-form": "^7.50.1", + "react-native": "0.73.4", + "react-native-fs": "^2.20.0", + "react-native-gesture-handler": "~2.14.0", + "react-native-reanimated": "~3.6.2", + "react-native-safe-area-context": "4.8.2", + "react-native-screens": "~3.29.0", + "react-native-svg": "14.1.0", + "react-native-web": "~0.19.10", + "react-native-webview": "13.6.4", + "tailwind-merge": "^1.14.0", + "zod": "^3.22.4", + "zustand": "^4.5.0" + }, + "devDependencies": { + "@babel/core": "^7.19.3", + "@babel/plugin-proposal-export-namespace-from": "^7.18.9", + "babel-preset-expo": "^10.0.1", + "metro": "^0.80.5", + "metro-core": "^0.80.5", + "react-native-url-polyfill": "^1.3.0", + "tailwindcss": "3.3.2" + }, + "name": "@stump/mobile", + "version": "0.0.1", + "private": true +} diff --git a/apps/expo/src/App.tsx b/apps/expo/src/App.tsx new file mode 100644 index 000000000..0f5fac98c --- /dev/null +++ b/apps/expo/src/App.tsx @@ -0,0 +1,131 @@ +import { NavigationContainer } from '@react-navigation/native' +import { createNativeStackNavigator } from '@react-navigation/native-stack' +import { checkUrl, initializeApi, isAxiosError, isUrl } from '@stump/api' +import { useAuthQuery } from '@stump/client' +import * as SplashScreen from 'expo-splash-screen' +import { useEffect, useState } from 'react' +import { SafeAreaProvider } from 'react-native-safe-area-context' + +import { AuthenticatedNavigator } from './screens/authenticated' +import LoginOrClaim from './screens/LoginOrClaim' +import ServerNotAccessible from './screens/ServerNotAccessible' +import { useAppStore, useUserStore } from './stores' + +const Stack = createNativeStackNavigator() + +export default function AppWrapper() { + const { baseUrl, setBaseUrl, setPlatform } = useAppStore((store) => ({ + baseUrl: store.baseUrl, + setBaseUrl: store.setBaseUrl, + setPlatform: store.setPlatform, + })) + + const { storeUser, setStoreUser } = useUserStore((state) => ({ + setStoreUser: state.setUser, + storeUser: state.user, + })) + const { isConnectedToServer, setIsConnectedToServer } = useAppStore((store) => ({ + isConnectedToServer: store.isConnectedWithServer, + setIsConnectedToServer: store.setIsConnectedWithServer, + })) + + const [isReady, setIsReady] = useState(false) + + const { error } = useAuthQuery({ + enabled: !storeUser && !!baseUrl && isConnectedToServer, + onSuccess: setStoreUser, + }) + + // TODO: This might not be needed anymore after refactoring the client, check this + useEffect(() => { + if (!error) return + + const axiosError = isAxiosError(error) ? error : null + const isNetworkError = axiosError?.code === 'ERR_NETWORK' + if (isNetworkError) { + setIsConnectedToServer(false) + } + }, [error, setIsConnectedToServer]) + + useEffect(() => { + // TODO: ios vs androind? + setPlatform('mobile') + }, [setPlatform]) + + // TODO: remove, just debugging stuff + useEffect(() => { + // setBaseUrl('https://demo.stumpapp.dev') + // setBaseUrl('http://localhost:10801') + setBaseUrl('http://192.168.0.202:10801') + }, [setBaseUrl]) + + useEffect(() => { + if (isReady) { + SplashScreen.hideAsync() + } + }, [isReady]) + + // console.log({ baseUrl, isConnectedToServer, isReady, storeUser }) + + /** + * An effect that will verify the baseUrl is accessible to the app. + */ + useEffect(() => { + // TODO: handle errors! + async function handleVerifyConnection() { + if (!isUrl(baseUrl)) { + console.error('Invalid URL') + } else { + const isValid = await checkUrl(baseUrl) + + if (!isValid) { + console.error(`Failed to connect to ${baseUrl}`) + } else { + initializeApi(baseUrl, 'v1') + setIsConnectedToServer(true) + } + } + + setIsReady(true) + } + + if (baseUrl) { + handleVerifyConnection() + } else { + setIsReady(true) + } + }, [baseUrl, setIsConnectedToServer]) + + // TODO: An offline-only stack which allows for reading of downloaded content + const renderApp = () => { + if (!isConnectedToServer) { + return ( + + ) + } else if (!storeUser) { + return + } else { + return ( + + ) + } + } + + if (!isReady) return null + + return ( + + + {renderApp()} + + + ) +} diff --git a/apps/expo/src/AppEntry.tsx b/apps/expo/src/AppEntry.tsx new file mode 100644 index 000000000..2b124949e --- /dev/null +++ b/apps/expo/src/AppEntry.tsx @@ -0,0 +1,41 @@ +import { StumpClientContextProvider } from '@stump/client' +import { useFonts } from 'expo-font' +import * as SplashScreen from 'expo-splash-screen' +import { useEffect } from 'react' + +import App from './App' +import { useAppStore, useUserStore } from './stores' + +// Prevent the splash screen from auto-hiding before asset loading is complete. +SplashScreen.preventAutoHideAsync() + +export function AppEntry() { + const [loaded, error] = useFonts({ + SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), + }) + + const setUser = useUserStore((store) => store.setUser) + const setIsConnectedWithServer = useAppStore((store) => store.setIsConnectedWithServer) + + // Expo Router uses Error Boundaries to catch errors in the navigation tree. + useEffect(() => { + if (error) throw error + }, [error]) + + const handleUnauthenticatedResponse = () => setUser(null) + const handleConnectionWithServerChanged = (isConnected: boolean) => + setIsConnectedWithServer(isConnected) + + if (!loaded) { + return null + } + + return ( + + + + ) +} diff --git a/apps/expo/src/components/EntityImage.tsx b/apps/expo/src/components/EntityImage.tsx new file mode 100644 index 000000000..484e47192 --- /dev/null +++ b/apps/expo/src/components/EntityImage.tsx @@ -0,0 +1,68 @@ +import { queryClient } from '@stump/client' +import { Image, ImageProps } from 'expo-image' +import { useCallback, useEffect, useState } from 'react' +import { Platform } from 'react-native' + +type Props = { + url: string +} & Omit + +const isAndroid = Platform.OS === 'android' + +export default function EntityImage({ url, ...props }: Props) { + const [base64Image, setBase64Image] = useState(null) + + // TODO: handle error and display fallback + const handleFetchImage = useCallback(async (url: string) => { + try { + const base64Image = await fetchImage(url) + setBase64Image(base64Image) + } catch (e) { + console.error(e) + } + }, []) + + useEffect(() => { + if (!base64Image && isAndroid) { + handleFetchImage(url) + } + }, [base64Image, handleFetchImage, url]) + + if (!base64Image && isAndroid) { + return null + } + + // TODO: support pinch and zoom + // https://github.com/likashefqet/react-native-image-zoom + // https://github.com/fakeheal/react-native-pan-pinch-view + // https://gist.github.com/Glazzes/357201f74fbfaddb3e933f4c258c4878 + return ( + + ) +} + +export const fetchImage = async (url: string) => { + const response = await queryClient.fetchQuery([url], async () => fetch(url)) + if (!response.bodyUsed) { + const blob = await response.blob() + const reader = new FileReader() + return new Promise((resolve, reject) => { + reader.onloadend = () => { + resolve(reader.result as string) + } + reader.onerror = reject + reader.readAsDataURL(blob) + }) + } else { + return response.url + } +} + +export const prefetchImage = async (url: string) => { + if (isAndroid) { + const base64Image = await fetchImage(url) + return Image.prefetch(base64Image) + } else { + return Image.prefetch(url) + } +} diff --git a/apps/expo/src/components/book/BookListItem.tsx b/apps/expo/src/components/book/BookListItem.tsx new file mode 100644 index 000000000..42b8300d2 --- /dev/null +++ b/apps/expo/src/components/book/BookListItem.tsx @@ -0,0 +1,31 @@ +import { getMediaThumbnail } from '@stump/api' +import { Media } from '@stump/types' +import React from 'react' +import { TouchableOpacity } from 'react-native' + +import EntityImage from '../EntityImage' +import { Text, View } from '../primitives' + +export const BOOK_LIST_ITEM_HEIGHT = 75 + +type BookListItemProps = { + book: Media + navigate: (id: string) => void +} +export const BookListItem = React.memo(({ book, navigate }: BookListItemProps) => ( + navigate(book.id)} + > + + + {book.name} + + +)) +BookListItem.displayName = 'BookListItem' diff --git a/apps/expo/src/components/book/index.ts b/apps/expo/src/components/book/index.ts new file mode 100644 index 000000000..849a2107a --- /dev/null +++ b/apps/expo/src/components/book/index.ts @@ -0,0 +1 @@ +export { BOOK_LIST_ITEM_HEIGHT, BookListItem } from './BookListItem' diff --git a/apps/expo/src/components/index.ts b/apps/expo/src/components/index.ts new file mode 100644 index 000000000..ac1aac4c4 --- /dev/null +++ b/apps/expo/src/components/index.ts @@ -0,0 +1,2 @@ +export { default as EntityImage } from './EntityImage' +export { Input, Link, ScreenRootView, Text, View } from './primitives' diff --git a/apps/expo/src/components/primitives/Input.tsx b/apps/expo/src/components/primitives/Input.tsx new file mode 100644 index 000000000..da0a2d5ed --- /dev/null +++ b/apps/expo/src/components/primitives/Input.tsx @@ -0,0 +1,21 @@ +import { styled } from 'nativewind' +import { forwardRef } from 'react' +import { TextInput, TextInputProps } from 'react-native' + +import { cn } from '../utils' + +const StyledInput = styled(TextInput) + +export const Input = forwardRef(({ className, ...props }, ref) => { + return ( + + ) +}) +Input.displayName = 'Input' diff --git a/apps/expo/src/components/primitives/Link.tsx b/apps/expo/src/components/primitives/Link.tsx new file mode 100644 index 000000000..a47828652 --- /dev/null +++ b/apps/expo/src/components/primitives/Link.tsx @@ -0,0 +1,28 @@ +import { Link as NativeLink, ParamListBase } from '@react-navigation/native' +import { styled, StyledProps } from 'nativewind' +import { ComponentProps, forwardRef } from 'react' + +import { cn } from '../utils' +import { TextProps, textVariants } from './Text' + +type Props = { + underline?: boolean +} & TextProps & + StyledProps>> +const StyledLink = styled(NativeLink) + +export const Link = forwardRef( + ({ className, muted, size, underline, ...props }, ref) => ( + + ), +) +Link.displayName = 'Link' diff --git a/apps/expo/src/components/primitives/Text.tsx b/apps/expo/src/components/primitives/Text.tsx new file mode 100644 index 000000000..5b01feacd --- /dev/null +++ b/apps/expo/src/components/primitives/Text.tsx @@ -0,0 +1,54 @@ +import { cva, VariantProps } from 'class-variance-authority' +import { styled, StyledProps } from 'nativewind' +import { forwardRef } from 'react' +import { Text as NativeText, TextProps as NativeTextProps } from 'react-native' + +import { cn } from '../utils' + +const StyledText = styled(NativeText) + +export type TextProps = { + muted?: boolean +} & VariantProps +type Props = TextProps & StyledProps +export const Text = forwardRef(({ className, size, muted, ...props }, ref) => { + return ( + + ) +}) +Text.displayName = 'Text' + +// FIXME: any dark variant classes defined here seem to be pruned... +export const textVariants = cva('', { + defaultVariants: { + size: 'md', + // variant: 'default', + }, + variants: { + size: { + '2xl': 'text-2xl', + '3xl': 'text-3xl', + '4xl': 'text-4xl', + lg: 'text-lg', + md: 'text-base', + sm: 'text-sm', + xl: 'text-xl', + xs: 'text-xs', + }, + // variant: { + // danger: 'text-red-600 dark:text-red-400', + // default: 'dark:text-white text-black', + // label: + // 'font-medium leading-none text-contrast-200 peer-disabled:cursor-not-allowed peer-disabled:opacity-70', + // muted: 'text-gray-400 dark:text-gray-300', + // }, + }, +}) diff --git a/apps/expo/src/components/primitives/View.tsx b/apps/expo/src/components/primitives/View.tsx new file mode 100644 index 000000000..90a6e4b86 --- /dev/null +++ b/apps/expo/src/components/primitives/View.tsx @@ -0,0 +1,51 @@ +import { StatusBar } from 'expo-status-bar' +import { styled, StyledProps, useColorScheme } from 'nativewind' +import { forwardRef, useMemo } from 'react' +import { View as NativeView, ViewProps } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' + +import { cn } from '../utils' + +const StyledView = styled(NativeView) + +export const View = StyledView + +type ScreenRootViewProps = StyledProps & { + classes?: string + disabledBottomInset?: boolean + applyPadding?: boolean +} +export const ScreenRootView = forwardRef( + ({ className, children, classes, disabledBottomInset, applyPadding, ...props }, ref) => { + const { colorScheme } = useColorScheme() + const insets = useSafeAreaInsets() + + const defaultStyle = useMemo(() => { + const paddingLeft = applyPadding ? 16 + insets.left : 0 + const paddingRight = applyPadding ? 16 + insets.right : 0 + return { + paddingBottom: disabledBottomInset ? undefined : insets.bottom, + paddingLeft, + paddingRight, + paddingTop: insets.top, + } + }, [insets, disabledBottomInset, applyPadding]) + + return ( + + {children} + + + ) + }, +) +ScreenRootView.displayName = 'ScreenRootView' diff --git a/apps/expo/src/components/primitives/index.ts b/apps/expo/src/components/primitives/index.ts new file mode 100644 index 000000000..cd61a086f --- /dev/null +++ b/apps/expo/src/components/primitives/index.ts @@ -0,0 +1,4 @@ +export { Input } from './Input' +export { Link } from './Link' +export { Text } from './Text' +export { ScreenRootView, View } from './View' diff --git a/apps/expo/src/components/reader/UnsupportedReader.tsx b/apps/expo/src/components/reader/UnsupportedReader.tsx new file mode 100644 index 000000000..3a6f7ad1a --- /dev/null +++ b/apps/expo/src/components/reader/UnsupportedReader.tsx @@ -0,0 +1,11 @@ +import React from 'react' + +import { ScreenRootView, Text } from '../primitives' + +export default function UnsupportedReader() { + return ( + + The book reader for this format is not supported yet. Check back later! + + ) +} diff --git a/apps/expo/src/components/reader/epub/EpubJSFooter.tsx b/apps/expo/src/components/reader/epub/EpubJSFooter.tsx new file mode 100644 index 000000000..3d7f66a03 --- /dev/null +++ b/apps/expo/src/components/reader/epub/EpubJSFooter.tsx @@ -0,0 +1,27 @@ +import { useReader } from '@epubjs-react-native/core' +import React from 'react' +import { useSafeAreaInsets } from 'react-native-safe-area-context' + +import { Text, View } from '@/components/primitives' + +export const FOOTER_HEIGHT = 24 + +export default function EpubJSFooter() { + const { currentLocation } = useReader() + + const { bottom } = useSafeAreaInsets() + + const currentPage = currentLocation?.start?.displayed.page || 1 + const totalPages = currentLocation?.end?.displayed.page || 1 + + return ( + + + {currentPage}/{totalPages} + + + ) +} diff --git a/apps/expo/src/components/reader/epub/EpubJSReader.tsx b/apps/expo/src/components/reader/epub/EpubJSReader.tsx new file mode 100644 index 000000000..d1c269138 --- /dev/null +++ b/apps/expo/src/components/reader/epub/EpubJSReader.tsx @@ -0,0 +1,125 @@ +import { Location, Reader } from '@epubjs-react-native/core' +import { useFileSystem } from '@epubjs-react-native/expo-file-system' +import { API, isAxiosError, updateEpubProgress } from '@stump/api' +import { Media } from '@stump/types' +import { useColorScheme } from 'nativewind' +import React, { useCallback, useEffect, useState } from 'react' +import { useWindowDimensions } from 'react-native' + +import EpubJSReaderContainer from './EpubJSReaderContainer' + +type Props = { + /** + * The media which is being read + */ + book: Media + /** + * The initial CFI to start the reader on + */ + initialCfi?: string + /** + * Whether the reader should be in incognito mode + */ + incognito?: boolean +} + +/** + * A reader for books that are EPUBs, using EpubJS as the reader + * + * TODO: create a custom reader component, this is a HUGE effort but will pay off in + * the long run + */ +export default function EpubJSReader({ book, initialCfi, incognito }: Props) { + /** + * The base64 representation of the book file. The reader component does not accept + * credentials in the fetch, so we just have to fetch manually and pass the base64 + * representation to the reader as the source. + */ + const [base64, setBase64] = useState(null) + + const { width, height } = useWindowDimensions() + const { colorScheme } = useColorScheme() + + /** + * An effect that fetches the book file and loads it into the reader component + * as a base64 string + */ + useEffect(() => { + async function fetchBook() { + try { + const response = await fetch(`${API.getUri()}/media/${book.id}/file`) + const data = await response.blob() + const reader = new FileReader() + reader.onloadend = () => { + const result = reader.result as string + // Note: uncomment this line to show an infinite loader... + // setBase64(result) + const adjustedResult = result.split(',')[1] || result + setBase64(adjustedResult) + } + reader.readAsDataURL(data) + } catch (e) { + console.error(e) + } + } + + fetchBook() + }, [book.id]) + + /** + * A callback that updates the read progress of the current location + * + * If the reader is in incognito mode, this will do nothing. + */ + const handleLocationChanged = useCallback( + async (_: number, currentLocation: Location, progress: number) => { + if (!incognito) { + const { + start: { cfi }, + } = currentLocation + + try { + await updateEpubProgress({ + epubcfi: cfi, + id: book.id, + is_complete: progress >= 1.0, + percentage: progress, + }) + } catch (e) { + console.error(e) + if (isAxiosError(e)) { + console.error(e.response?.data) + } + } + } + }, + [incognito, book.id], + ) + + if (!base64) { + return null + } + + return ( + + console.error(error)} + width={width} + // height={height - height * 0.08} + height={height} + fileSystem={useFileSystem} + initialLocation={initialCfi} + onLocationChange={handleLocationChanged} + // renderLoadingFileComponent={LoadingSpinner} + defaultTheme={ + colorScheme === 'dark' + ? { + body: { background: '#0F1011 !important', color: '#E8EDF4' }, + } + : { body: { color: 'black' } } + } + /> + + ) +} diff --git a/apps/expo/src/components/reader/epub/EpubJSReaderContainer.tsx b/apps/expo/src/components/reader/epub/EpubJSReaderContainer.tsx new file mode 100644 index 000000000..bc61d6ca7 --- /dev/null +++ b/apps/expo/src/components/reader/epub/EpubJSReaderContainer.tsx @@ -0,0 +1,20 @@ +import { ReaderProvider } from '@epubjs-react-native/core' +import React from 'react' + +import { ScreenRootView, View } from '@/components/primitives' + +type Props = { + children: React.ReactNode +} + +// total ass, I hate epubjs lol maybe im just dumb? I cannot get the reader to listen to the height +export default function EpubJSReaderContainer({ children }: Props) { + return ( + + + {children} + {/* */} + + + ) +} diff --git a/apps/expo/src/components/reader/epub/LoadingSpinner.tsx b/apps/expo/src/components/reader/epub/LoadingSpinner.tsx new file mode 100644 index 000000000..a8a7facc3 --- /dev/null +++ b/apps/expo/src/components/reader/epub/LoadingSpinner.tsx @@ -0,0 +1,39 @@ +import { LoadingFileProps } from '@epubjs-react-native/core' +import React, { useEffect, useState } from 'react' + +import { Text, View } from '@/components/primitives' + +// FIXME: This causes an error... +export default function LoadingSpinner({ + // downloadProgress, + // downloadError, + downloadSuccess, +}: LoadingFileProps) { + // Setup a timeout that will check if we are stuck loading, abougt 10 seconds + const [didTimeout, setDidTimeout] = useState(false) + + // If we are still loading after 10 seconds, we are stuck + useEffect(() => { + const timeout = setTimeout(() => { + setDidTimeout(true) + }, 10000) + + return () => clearTimeout(timeout) + }, []) + + if (didTimeout && !downloadSuccess) { + return ( + + It looks like we are stuck loading the book. Check your server logs + + ) + } else if (!downloadSuccess) { + return ( + + Loading... + + ) + } else { + return null + } +} diff --git a/apps/expo/src/components/reader/epub/index.ts b/apps/expo/src/components/reader/epub/index.ts new file mode 100644 index 000000000..fd31a8108 --- /dev/null +++ b/apps/expo/src/components/reader/epub/index.ts @@ -0,0 +1 @@ +export { default as EpubJSReader } from './EpubJSReader' diff --git a/apps/expo/src/components/reader/image/ImageBasedReader.tsx b/apps/expo/src/components/reader/image/ImageBasedReader.tsx new file mode 100644 index 000000000..87e471f91 --- /dev/null +++ b/apps/expo/src/components/reader/image/ImageBasedReader.tsx @@ -0,0 +1,244 @@ +import { getMediaPage, isAxiosError } from '@stump/api' +import { useUpdateMediaProgress } from '@stump/client' +import { Media } from '@stump/types' +import { useColorScheme } from 'nativewind' +import React, { useCallback, useMemo, useState } from 'react' +import { FlatList, TouchableWithoutFeedback, useWindowDimensions } from 'react-native' +import { useSafeAreaInsets } from 'react-native-safe-area-context' + +import { View } from '@/components' +import EntityImage from '@/components/EntityImage' +import { gray } from '@/constants/colors' +import { useReaderStore } from '@/stores' + +import ReaderContainer from './ReaderContainer' + +type ImageDimension = { + height: number + width: number + ratio: number +} + +// TODO: This is an image-based reader right now. Extract this to a separate component +// which is readered when the book is an image-based book. + +// TODO: Account for device orientation AND reading direction + +type Props = { + /** + * The media which is being read + */ + book: Media + /** + * The initial page to start the reader on + */ + initialPage: number + /** + * Whether the reader should be in incognito mode + */ + incognito?: boolean +} + +/** + * A reader for books that are image-based, where each page should be displayed as an image + */ +export default function ImageBasedReader({ book, initialPage, incognito }: Props) { + const { height, width } = useWindowDimensions() + const { colorScheme } = useColorScheme() + + const [imageSizes, setImageHeights] = useState>({}) + + // const lastPrefetchStart = useRef(0) + const readerMode = useReaderStore((state) => state.mode) + + const deviceOrientation = width > height ? 'landscape' : 'portrait' + + // TODO: an effect that whenever the device orienation changes to something different than before, + // recalculate the ratios of the images? Maybe. Who knows, you will though + + const { updateReadProgressAsync } = useUpdateMediaProgress(book.id) + + // FIXME: this was HARD erroring... + + // const prefetchPages = useCallback( + // async (start: number, end: number) => { + // for (let i = start; i <= end; i++) { + // const prefetched = await prefetchImage(getMediaPage(id, i)) + // console.log('Prefetched', i, prefetched) + // } + // }, + // [id], + // ) + + // useEffect(() => { + // if (lastPrefetchStart.current === 0) { + // prefetchPages(1, 5) + // lastPrefetchStart.current = 5 + // } + // }, [prefetchPages]) + + /** + * A callback that updates the read progress of the current page. This will be + * called whenever the user changes the page in the reader. + * + * If the reader is in incognito mode, this will do nothing. + */ + const handleCurrentPageChanged = useCallback( + async (page: number) => { + if (!incognito) { + try { + await updateReadProgressAsync(page) + // if (page - lastPrefetchStart.current > 5) { + // await prefetchPages(page, page + 5) + // } + // lastPrefetchStart.current = page + } catch (e) { + console.error(e) + if (isAxiosError(e)) { + console.error(e.response?.data) + } + } + } + }, + [updateReadProgressAsync, incognito], + ) + + return ( + + i)} + renderItem={({ item }) => ( + + )} + keyExtractor={(item) => item.toString()} + horizontal={readerMode === 'paged'} + pagingEnabled={readerMode === 'paged'} + onViewableItemsChanged={({ viewableItems }) => { + const fistVisibleItemIdx = viewableItems + .filter(({ isViewable }) => isViewable) + .at(0)?.index + if (fistVisibleItemIdx) { + handleCurrentPageChanged(fistVisibleItemIdx + 1) + } + }} + initialNumToRender={10} + maxToRenderPerBatch={10} + initialScrollIndex={initialPage - 1} + /> + + ) +} + +type PageProps = { + deviceOrientation: string + id: string + index: number + imageSizes: Record + setImageHeights: ( + fn: (prev: Record) => Record, + ) => void + maxWidth: number + maxHeight: number + readingDirection: 'vertical' | 'horizontal' +} +const Page = React.memo( + ({ + deviceOrientation, + id, + index, + imageSizes, + setImageHeights, + maxWidth, + maxHeight, + readingDirection, + }: PageProps) => { + const insets = useSafeAreaInsets() + + const { showToolBar, setShowToolBar } = useReaderStore((state) => ({ + setShowToolBar: state.setShowToolBar, + showToolBar: state.showToolBar, + })) + + const handlePress = useCallback(() => { + setShowToolBar(!showToolBar) + }, [showToolBar, setShowToolBar]) + + /** + * A memoized value that represents the size(s) of the image dimensions for the current page. + */ + const pageSize = useMemo(() => imageSizes[index + 1], [imageSizes, index]) + + const safeMaxHeight = maxHeight - insets.top - insets.bottom + + // We always want to display the image at the full width of the screen, + // and then calculate the height based on the aspect ratio of the image. + const { height, width } = useMemo(() => { + if (!pageSize) { + return { + height: safeMaxHeight, + width: maxWidth, + } + } + + const { ratio } = pageSize + + if (deviceOrientation == 'landscape') { + return { + height: safeMaxHeight, + width: safeMaxHeight / ratio, + } + } else { + return { + height: maxWidth / ratio, + width: maxWidth, + } + } + }, [deviceOrientation, pageSize, safeMaxHeight, maxWidth]) + + return ( + + + { + setImageHeights((prev) => ({ + ...prev, + [index + 1]: { + height, + ratio: deviceOrientation == 'landscape' ? height / width : width / height, + width, + }, + })) + }} + /> + + + ) + }, +) +Page.displayName = 'Page' diff --git a/apps/expo/src/components/reader/image/ReaderContainer.tsx b/apps/expo/src/components/reader/image/ReaderContainer.tsx new file mode 100644 index 000000000..7473286b3 --- /dev/null +++ b/apps/expo/src/components/reader/image/ReaderContainer.tsx @@ -0,0 +1,10 @@ +import React from 'react' + +import { ScreenRootView } from '@/components/primitives' + +type Props = { + children: React.ReactNode +} +export default function ReaderContainer({ children }: Props) { + return {children} +} diff --git a/apps/expo/src/components/reader/image/index.ts b/apps/expo/src/components/reader/image/index.ts new file mode 100644 index 000000000..050d06f3a --- /dev/null +++ b/apps/expo/src/components/reader/image/index.ts @@ -0,0 +1 @@ +export { default as ImageBasedReader } from './ImageBasedReader' diff --git a/apps/expo/src/components/reader/index.ts b/apps/expo/src/components/reader/index.ts new file mode 100644 index 000000000..7ac0863e1 --- /dev/null +++ b/apps/expo/src/components/reader/index.ts @@ -0,0 +1,3 @@ +export { EpubJSReader } from './epub' +export { ImageBasedReader } from './image' +export { default as UnsupportedReader } from './UnsupportedReader' diff --git a/apps/expo/src/components/utils.ts b/apps/expo/src/components/utils.ts new file mode 100644 index 000000000..dd169c6cc --- /dev/null +++ b/apps/expo/src/components/utils.ts @@ -0,0 +1,15 @@ +import { ClassValue, clsx } from 'clsx' +import { twMerge } from 'tailwind-merge' + +export type PickSelect = T[K] +export type Without = { [P in Exclude]?: never } +export type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U + +/** Normalize class names using clsx and tailwind-merge */ +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} + +export function cx(...inputs: ClassValue[]) { + return clsx(...inputs) +} diff --git a/apps/expo/src/constants/colors.ts b/apps/expo/src/constants/colors.ts new file mode 100644 index 000000000..36822392c --- /dev/null +++ b/apps/expo/src/constants/colors.ts @@ -0,0 +1,16 @@ +/* eslint-disable sort-keys-fix/sort-keys-fix */ + +export const gray = { + DEFAULT: '#292C30', + 50: '#E2E4E6', + 100: '#CCCFD3', + 200: '#A0A6AE', + 300: '#747D88', + 400: '#4F545C', + 500: '#292C30', + 600: '#242628', + 700: '#1F2123', + 800: '#1B1C1D', + 900: '#161719', + 950: '#0F1011', +} diff --git a/packages/client/src/hooks/useLayoutMode.ts b/apps/expo/src/hooks/useLayoutMode.ts similarity index 94% rename from packages/client/src/hooks/useLayoutMode.ts rename to apps/expo/src/hooks/useLayoutMode.ts index 7f37a2d2c..d03021f11 100644 --- a/packages/client/src/hooks/useLayoutMode.ts +++ b/apps/expo/src/hooks/useLayoutMode.ts @@ -1,7 +1,7 @@ import type { LayoutMode } from '@stump/types' import { useEffect, useMemo, useState } from 'react' -import { useUserStore } from '../stores' +import { useUserStore } from '@/stores' export function useLayoutMode() { const { userPreferences } = useUserStore((state) => ({ diff --git a/packages/client/src/hooks/usePreferences.ts b/apps/expo/src/hooks/usePreferences.ts similarity index 87% rename from packages/client/src/hooks/usePreferences.ts rename to apps/expo/src/hooks/usePreferences.ts index a51843675..808d4adcb 100644 --- a/packages/client/src/hooks/usePreferences.ts +++ b/apps/expo/src/hooks/usePreferences.ts @@ -1,8 +1,8 @@ +import { useUpdatePreferences } from '@stump/client' import { UpdateUserPreferences, UserPreferences } from '@stump/types' import { useCallback } from 'react' -import { useUpdatePreferences } from '../queries/user' -import { useUserStore } from '../stores/useUserStore' +import { useUserStore } from '@/stores' export function usePreferences() { const { preferences, setPreferences } = useUserStore((state) => ({ diff --git a/apps/expo/src/screens/LoginOrClaim.tsx b/apps/expo/src/screens/LoginOrClaim.tsx new file mode 100644 index 000000000..93eaea6e8 --- /dev/null +++ b/apps/expo/src/screens/LoginOrClaim.tsx @@ -0,0 +1,130 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useLoginOrRegister } from '@stump/client' +import React, { useCallback } from 'react' +import { Controller, useForm } from 'react-hook-form' +import { Image, Text, TouchableOpacity, View } from 'react-native' +import { z } from 'zod' + +import { Input, ScreenRootView } from '@/components' +import { useUserStore } from '@/stores' + +export default function LoginOrClaim() { + const setUser = useUserStore((store) => store.setUser) + const { isClaimed, isCheckingClaimed, loginUser, registerUser, isLoggingIn, isRegistering } = + useLoginOrRegister({ + onSuccess: setUser, + }) + + // TODO: generalize common form schemas between clients to the client package + // TODO: move i18n to isolated package to be used between clients + + const schema = z.object({ + password: z.string().min(1, { message: 'Password must be at least 2 characters long' }), + username: z.string().min(1, { message: 'Username is required' }), + }) + type FormValues = z.infer + + const { + control, + formState: { errors }, + handleSubmit, + } = useForm({ + resolver: zodResolver(schema), + }) + + const onSubmit = useCallback( + async ({ username, password }: FormValues) => { + try { + if (isClaimed) { + await loginUser({ password, username }) + } else { + await registerUser({ password, username }) + await loginUser({ password, username }) + } + } catch (error) { + // TODO: alert error or set error somewhere + } + }, + [isClaimed, loginUser, registerUser], + ) + + const renderHeader = () => { + if (isClaimed) { + // just return a view with the icon.png: + return ( + + + + ) + } else { + return ( + + This server is uninitialized + + Enter a username and password to claim it + + + + ) + } + } + + const isLoading = isCheckingClaimed || isLoggingIn || isRegistering + + return ( + + {renderHeader()} + + + ( + + )} + name="username" + /> + {errors.username && {errors.username.message}} + + + + ( + + )} + name="password" + /> + {errors.password && {errors.password.message}} + + + + {isClaimed ? 'Log in' : 'Create account'} + + + ) +} diff --git a/apps/expo/src/screens/ServerNotAccessible.tsx b/apps/expo/src/screens/ServerNotAccessible.tsx new file mode 100644 index 000000000..5362b7dc0 --- /dev/null +++ b/apps/expo/src/screens/ServerNotAccessible.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +import { ScreenRootView, Text } from '@/components' + +// TODO: This component needs to be aware: +// 1. If there is no server set, which is either first launch or the user initiates a reset +// 2. There is a server set, but the server is not accessible +// +// In either situation, a form should be presented to the user to enter the server URL. + +export default function ServerNotAccessible() { + return ( + + I cannot connect to the server + + ) +} diff --git a/apps/expo/src/screens/authenticated/AuthenticatedNavigator.tsx b/apps/expo/src/screens/authenticated/AuthenticatedNavigator.tsx new file mode 100644 index 000000000..0db64dc03 --- /dev/null +++ b/apps/expo/src/screens/authenticated/AuthenticatedNavigator.tsx @@ -0,0 +1,20 @@ +import { createNativeStackNavigator } from '@react-navigation/native-stack' +import React from 'react' + +import { BookStackNavigator } from './book' +import MainTabNavigator from './MainTabNavigator' + +const Stack = createNativeStackNavigator() + +export default function AuthenticatedNavigator() { + return ( + + + + + ) +} diff --git a/apps/expo/src/screens/authenticated/Home.tsx b/apps/expo/src/screens/authenticated/Home.tsx new file mode 100644 index 000000000..c36479637 --- /dev/null +++ b/apps/expo/src/screens/authenticated/Home.tsx @@ -0,0 +1,30 @@ +import { authApi } from '@stump/api' +import { queryClient } from '@stump/client' +import React from 'react' +import { Button } from 'react-native' + +import { ScreenRootView, Text } from '@/components' +import { useUserStore } from '@/stores' + +export default function Home() { + const setUser = useUserStore((state) => state.setUser) + + const handleLogout = async () => { + try { + await authApi.logout() + setUser(null) + } catch (err) { + console.error(err) + } finally { + setUser(null) + queryClient.clear() + } + } + + return ( + + I am home + - - ) -} diff --git a/interface/src/scenes/library/explorer/LibraryExplorerScene.tsx b/interface/src/scenes/library/explorer/LibraryExplorerScene.tsx deleted file mode 100644 index ecfdda355..000000000 --- a/interface/src/scenes/library/explorer/LibraryExplorerScene.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { mediaApi } from '@stump/api' -import { useDirectoryListing, useLibraryByIdQuery } from '@stump/client' -import { DirectoryListingFile } from '@stump/types' -import { Helmet } from 'react-helmet' -import toast from 'react-hot-toast' -import { useNavigate, useParams } from 'react-router-dom' -import { useMediaMatch } from 'rooks' - -import paths from '../../../paths' -import { LibraryExplorerContext } from './context' -import FileExplorer from './FileExplorer' -import FileExplorerFooter, { FOOTER_HEIGHT } from './FileExplorerFooter' -import FileExplorerHeader, { HEADER_HEIGHT } from './FileExplorerHeader' - -export default function LibraryExplorerScene() { - const navigate = useNavigate() - - const { id } = useParams() - if (!id) { - throw new Error('Library id is required') - } - - const isMobile = useMediaMatch('(max-width: 768px)') - const { library, isLoading } = useLibraryByIdQuery(id) - - // TODO: I need to store location.state somewhere so that when the user uses native navigation, - // their history, or at the very least where they left off, is persisted. - const { entries, setPath, path, goForward, goBack, canGoBack, canGoForward } = - useDirectoryListing({ - enabled: !!library?.path, - enforcedRoot: library?.path, - initialPath: library?.path, - }) - - const handleSelect = async (entry: DirectoryListingFile) => { - if (entry.is_directory) { - setPath(entry.path) - } else { - try { - const response = await mediaApi.getMedia({ - path: entry.path, - }) - const entity = response.data.data?.at(0) - - if (entity) { - navigate(paths.bookOverview(entity.id), { - state: { - forward_path: path, - }, - }) - } else { - toast.error('No associated DB entry found for this file') - } - } catch (err) { - console.error(err) - toast.error('An unknown error occurred') - } - } - } - - // TODO: loading state ugly - if (isLoading) { - return null - } else if (!library) { - // TODO: render a proper not found image or something - throw new Error('Library not found') - } - - return ( - - - Stump | {library.name} - - - -
- -
- -
- ) -} diff --git a/interface/src/scenes/library/management/QuickActions.tsx b/interface/src/scenes/library/management/QuickActions.tsx deleted file mode 100644 index 103e88a48..000000000 --- a/interface/src/scenes/library/management/QuickActions.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { libraryApi, libraryQueryKeys } from '@stump/api' -import { useMutation } from '@stump/client' -import { ButtonOrLink, Heading, Text } from '@stump/components' -import { Library } from '@stump/types' -import React from 'react' - -import { useAppContext } from '@/context' -import paths from '@/paths' - -import CleanLibrary from './CleanLibrary' -import DeleteLibraryThumbnails from './DeleteLibraryThumbnails' -import LibraryThumbnailSelector from './LibraryThumbnailSelector' -import RegenerateThumbnails from './RegenerateThumbnails' - -type Props = { - library: Library -} -export default function QuickActions({ library }: Props) { - const { isServerOwner } = useAppContext() - const { mutate } = useMutation( - [libraryQueryKeys.regenerateThumbnails, library.id], - (force: boolean) => libraryApi.regenerateThumbnails(library.id, force), - ) - - const hasThumbnailConfig = !!library.library_options.thumbnail_config - - const handleRegenerateThumbnails = (force: boolean) => { - if (!hasThumbnailConfig || !isServerOwner) { - return - } - mutate(force) - } - - return ( -
-
- Quick Actions - - Some quick actions you can take on this library - -
- -
- -
- {hasThumbnailConfig && ( - <> - - - - )} - - - Open file explorer - -
-
-
- ) -} diff --git a/interface/src/scenes/library/management/RegenerateThumbnails.tsx b/interface/src/scenes/library/management/RegenerateThumbnails.tsx deleted file mode 100644 index 8d02b5ec0..000000000 --- a/interface/src/scenes/library/management/RegenerateThumbnails.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Button, DropdownMenu } from '@stump/components' -import { AlertTriangle, ChevronDown, ImagePlus } from 'lucide-react' -import React from 'react' - -type Props = { - onRegenerate(force: boolean): void -} -export default function RegenerateThumbnails({ onRegenerate }: Props) { - const iconStyle = 'mr-2 h-4 w-4' - - return ( - - Create thumbnails - - - } - groups={[ - { - items: [ - { - label: 'Create missing only', - leftIcon: , - onClick: () => onRegenerate(false), - }, - { - label: 'Force recreate all', - leftIcon: , - onClick: () => onRegenerate(true), - }, - ], - }, - ]} - align="start" - /> - ) -} diff --git a/interface/src/scenes/library/management/create-or-update/EditLibraryScene.tsx b/interface/src/scenes/library/management/create-or-update/EditLibraryScene.tsx deleted file mode 100644 index 769fb9285..000000000 --- a/interface/src/scenes/library/management/create-or-update/EditLibraryScene.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import { useLibraries, useLibraryByIdQuery } from '@stump/client' -import { Heading, Link, Text } from '@stump/components' -import { useParams } from 'react-router' - -import { Container, ContentContainer } from '@/components/container' - -import { useLocaleContext } from '../../../../i18n/context' -import QuickActions from '../QuickActions' -import { CreateOrUpdateLibraryForm } from './form' - -export default function ManageLibraryScene() { - const { id } = useParams() - const { t } = useLocaleContext() - const { libraries } = useLibraries() - const { library, isLoading } = useLibraryByIdQuery(id || '', { enabled: !!id }) - - if (!id) { - throw new Error('Library ID is required') - } else if (isLoading) { - return null - } else if (!library) { - throw new Error('Library not found') - } - - return ( - -
- {t('manageLibraryScene.heading')} - - {t('manageLibraryScene.subtitle')}{' '} - - {t('manageLibraryScene.subtitleLink')}. - - -
- - - {libraries && ( - <> - - - - )} - -
- ) -} diff --git a/interface/src/scenes/onboarding/OnBoardingRouter.tsx b/interface/src/scenes/onboarding/OnBoardingRouter.tsx deleted file mode 100644 index 0b69d2458..000000000 --- a/interface/src/scenes/onboarding/OnBoardingRouter.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { Suspense } from 'react' -import { Route, Routes } from 'react-router' - -import { LazyComponent } from '../../AppRouter' - -export const lazily = (loader: () => unknown) => React.lazy(() => loader() as LazyComponent) -const OnBoardingScene = lazily(() => import('./OnBoardingScene')) - -export default function OnBoardingRouter() { - return ( - - - } /> - - - ) -} diff --git a/interface/src/scenes/series/SeriesOverviewScene.tsx b/interface/src/scenes/series/SeriesOverviewScene.tsx deleted file mode 100644 index 42c54591f..000000000 --- a/interface/src/scenes/series/SeriesOverviewScene.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import { useLayoutMode, usePagedMediaQuery, useSeriesByIdQuery } from '@stump/client' -import { useEffect } from 'react' -import { Helmet } from 'react-helmet' -import { useParams } from 'react-router' -import { useMediaMatch } from 'rooks' - -import { SceneContainer } from '@/components/container' -import { FilterProvider, FilterToolBar, useFilterContext } from '@/components/filters' -import MediaList from '@/components/media/MediaList' -import Pagination from '@/components/Pagination' -import useIsInView from '@/hooks/useIsInView' -import { usePageParam } from '@/hooks/usePageParam' - -import { SeriesContext, useSeriesContext } from './context' -import MediaGrid from './MediaGrid' -import SeriesOverviewTitleSection from './SeriesOverviewTitleSection' - -// TODO: fix pagination -function SeriesOverviewScene() { - const is3XLScreenOrBigger = useMediaMatch('(min-width: 1600px)') - - const [containerRef, isInView] = useIsInView() - - const { page, setPage } = usePageParam() - const { seriesId } = useSeriesContext() - - const { layoutMode } = useLayoutMode() - const { series, isLoading: isLoadingSeries } = useSeriesByIdQuery(seriesId) - const { filters } = useFilterContext() - const { - isLoading: isLoadingMedia, - isRefetching: isRefetchingMedia, - media, - pageData, - } = usePagedMediaQuery({ - page, - page_size: is3XLScreenOrBigger ? 40 : 20, - params: { - ...filters, - series: { - id: seriesId, - }, - }, - }) - - const { current_page, total_pages } = pageData || {} - const isOnFirstPage = current_page === 1 - const hasFilters = Object.keys(filters || {}).length > 0 - const hasStuff = total_pages !== undefined && current_page !== undefined - // we show on the first page, but if there are filters and no stuff we show it - const showOverview = isOnFirstPage || (hasFilters && !hasStuff) - - // TODO: detect if going from page > 1 to page = 1 and scroll to top - useEffect( - () => { - if (!isInView && !isOnFirstPage) { - containerRef.current?.scrollIntoView({ - block: 'nearest', - inline: 'start', - }) - } - }, - // eslint-disable-next-line react-hooks/exhaustive-deps - [current_page, isOnFirstPage], - ) - - if (isLoadingSeries) { - return null - } else if (!series) { - throw new Error('Series not found') - } - - return ( - - - Stump | {series.name || ''} - - - {showOverview && } - - {/* @ts-expect-error: wrong ref but is okay */} -
- - - -
- {hasStuff && ( - - )} - {layoutMode === 'GRID' ? ( - 0} - /> - ) : ( - 0} - /> - )} - {hasStuff && ( - - )} -
- - ) -} - -export default function SeriesOverviewSceneWrapper() { - const seriesId = useParams<{ id: string }>()?.id - - if (!seriesId) { - throw new Error('Series ID is required for this route.') - } - - return ( - - - - - - ) -} diff --git a/interface/src/scenes/series/SeriesRouter.tsx b/interface/src/scenes/series/SeriesRouter.tsx deleted file mode 100644 index e5daa63d3..000000000 --- a/interface/src/scenes/series/SeriesRouter.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react' -import { Navigate, Route, Routes } from 'react-router' - -import ServerOwnerRouteWrapper from '@/components/ServerOwnerRouteWrapper.tsx' - -import { LazyComponent } from '../../AppRouter.tsx' - -const lazily = (loader: () => unknown) => React.lazy(() => loader() as LazyComponent) - -const SeriesOverviewScene = lazily(() => import('./SeriesOverviewScene.tsx')) -const SeriesManagementScene = lazily(() => import('./management/SeriesManagementScene.tsx')) - -export default function SeriesRouter() { - return ( - - } /> - }> - } /> - - } /> - - ) -} diff --git a/interface/src/scenes/series/context.ts b/interface/src/scenes/series/context.ts deleted file mode 100644 index b4d6185f5..000000000 --- a/interface/src/scenes/series/context.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { createContext, useContext } from 'react' - -export type ISeriesContext = { - seriesId: string -} - -export const SeriesContext = createContext({ - seriesId: '', -}) -export const useSeriesContext = () => useContext(SeriesContext) diff --git a/interface/src/scenes/series/management/SeriesManagementScene.tsx b/interface/src/scenes/series/management/SeriesManagementScene.tsx deleted file mode 100644 index f64717300..000000000 --- a/interface/src/scenes/series/management/SeriesManagementScene.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { useSeriesByIdQuery } from '@stump/client' -import { Alert, Breadcrumbs, Heading, Text } from '@stump/components' -import { Construction } from 'lucide-react' -import React, { useMemo } from 'react' -import { Navigate, useParams } from 'react-router' - -import { SceneContainer } from '@/components/container' -import paths from '@/paths' - -import SeriesThumbnailSelector from './SeriesThumbnailSelector' - -export default function SeriesManagementScene() { - const { id } = useParams<{ id: string }>() - if (!id) { - throw new Error('Series ID is required for this route') - } - - const { series, isLoading } = useSeriesByIdQuery(id, { - params: { - load_library: true, - }, - }) - - const breadcrumbs = useMemo(() => { - if (!series) return [] - - return [ - ...(series.library - ? [ - { - label: series.library.name, - to: paths.libraryOverview(series.library.id), - }, - ] - : []), - { - label: series.metadata?.title || series.name, - to: paths.seriesOverview(series.id), - }, - ] - }, [series]) - - if (isLoading) { - return null - } else if (!series) { - return - } - - return ( - -
-
- - - Manage - - - - Make changes to this series - -
- - - - Series management is currently under development and has very limited functionality - - - - -
-
- ) -} diff --git a/interface/src/scenes/settings/app/AppSettingsRouter.tsx b/interface/src/scenes/settings/app/AppSettingsRouter.tsx deleted file mode 100644 index 7e6c893d2..000000000 --- a/interface/src/scenes/settings/app/AppSettingsRouter.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React from 'react' -import { Route, Routes } from 'react-router' - -import { LazyComponent } from '../../../AppRouter.tsx' - -const lazily = (loader: () => unknown) => React.lazy(() => loader() as LazyComponent) - -const GeneralSettingsScene = lazily(() => import('./general/GeneralSettingsScene.tsx')) -const AppearanceSettingsScene = lazily(() => import('./appearance/AppearanceSettingsScene.tsx')) - -export default function AppSettingsRouter() { - return ( - - } /> - } /> - - ) -} diff --git a/interface/src/scenes/settings/server/ServerSettingsRouter.tsx b/interface/src/scenes/settings/server/ServerSettingsRouter.tsx deleted file mode 100644 index 8defb27ae..000000000 --- a/interface/src/scenes/settings/server/ServerSettingsRouter.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' -import { Route, Routes } from 'react-router' - -import { LazyComponent } from '../../../AppRouter.tsx' -import { UsersRouter } from './users' - -const lazily = (loader: () => unknown) => React.lazy(() => loader() as LazyComponent) - -const GeneralServerSettingsScene = lazily(() => import('./general/GeneralServerSettingsScene.tsx')) -const JobSettingsScene = lazily(() => import('./jobs/JobSettingsScene.tsx')) - -export default function ServerSettingsRouter() { - return ( - - } /> - } /> - } /> - - ) -} diff --git a/interface/src/scenes/settings/server/ServerSettingsScene.tsx b/interface/src/scenes/settings/server/ServerSettingsScene.tsx deleted file mode 100644 index 205af26bf..000000000 --- a/interface/src/scenes/settings/server/ServerSettingsScene.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import { Heading, Text } from '@stump/components' -import { Helmet } from 'react-helmet' - -import { SceneContainer } from '@/components/container' -import LibrariesStats from '@/components/library/LibrariesStats' -import { useLocaleContext } from '@/i18n' - -export default function ServerSettingsScene() { - const { t } = useLocaleContext() - - return ( - - - Stump | {t('settingsScene.server.helmet')} - - -
- -
- - {t('settingsScene.server.heading')} - - {t('settingsScene.server.subtitle')} - -
- ) -} diff --git a/interface/tailwind.config.js b/interface/tailwind.config.js deleted file mode 100644 index 3dbadab44..000000000 --- a/interface/tailwind.config.js +++ /dev/null @@ -1,2 +0,0 @@ -// Note: this isn't used, but needed for intellisense. >:( -module.exports = require('../packages/components/tailwind/tailwind.js')('interface') diff --git a/interface/tsconfig.json b/interface/tsconfig.json deleted file mode 100644 index 6bb94ac27..000000000 --- a/interface/tsconfig.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "types": ["vite/client", "node"], - "skipLibCheck": true, - "allowImportingTsExtensions": true, - "jsx": "preserve", - "module": "ES2022", - "moduleResolution": "Node", - "paths": { - "@stump/api": ["../packages/api/src/index.ts"], - "@stump/api/*": ["../packages/api/src/*"], - "@stump/client": ["../packages/client/src/index.ts"], - "@stump/client/*": ["../packages/client/src/*"], - "@stump/components": ["../packages/components/src/index.ts"], - "@stump/components/*": ["../packages/components/src/*"], - "@stump/types": ["../packages/types/index.ts"], - "@stump/types/*": ["../packages/types/*"], - "@/components/*": ["./src/components/*"], - "@/hooks/*": ["./src/hooks/*"], - "@/i18n": ["./src/i18n/index.ts"], - "@/i18n/*": ["./src/i18n/*"], - "@/utils/*": ["./src/utils/*"], - "@/context": ["./src/context.ts"], - "@/paths": ["./src/paths.ts"] - }, - "resolveJsonModule": true - }, - "include": ["src", "src/**/*.json"], - "references": [ - { - "path": "../packages/api" - }, - { - "path": "../packages/client" - }, - { - "path": "../packages/components" - }, - { - "path": "../packages/types" - } - ] -} diff --git a/package.json b/package.json index 099f5cf69..67bddebd2 100644 --- a/package.json +++ b/package.json @@ -4,26 +4,35 @@ "repository": "https://github.com/stumpapp/stump.git", "author": "Aaron Leopold ", "license": "MIT", + "private": true, + "workspaces": [ + "apps/*", + "core", + "docs", + "packages/*" + ], "scripts": { "prepare": "husky install", - "setup": "pnpm i && pnpm web build && cargo codegen", + "setup": "yarn && yarn web build && cargo codegen", "lint": "eslint --ext .ts,.tsx,.cts,.mts,.js,.jsx,.cjs,.mjs --fix --report-unused-disable-directives --no-error-on-unmatched-pattern --exit-on-fatal-error --ignore-path .gitignore .", - "client": "pnpm --filter @stump/client --", - "desktop": "pnpm --filter @stump/desktop --", - "interface": "pnpm --filter @stump/interface --", - "web": "pnpm --filter @stump/web --", - "docs": "pnpm --filter @stump/docs --", - "server": "pnpm --filter @stump/server --", - "start:desktop": "concurrently -n server,desktop -c green.bold,blue.bold \"pnpm run server start\" \"pnpm desktop start\"", - "dev:web": "concurrently -n server,web -c green.bold,blue.bold \"pnpm run server dev\" \"pnpm web dev\"", - "core": "pnpm --filter @stump/core --", - "prisma": "pnpm core prisma", + "client": "yarn workspace @stump/client", + "desktop": "yarn workspace @stump/desktop", + "expo": "yarn workspace @stump/expo", + "browser": "yarn workspace @stump/browser", + "web": "yarn workspace @stump/web", + "docs": "yarn workspace @stump/docs", + "server": "yarn workspace @stump/server", + "start:desktop": "concurrently -n server,desktop -c green.bold,blue.bold \"yarn run server start\" \"yarn desktop start\"", + "dev:web": "concurrently -n server,web -c green.bold,blue.bold \"yarn run server dev\" \"yarn web dev\"", + "dev:expo": "concurrently -n server,expo -c green.bold,blue.bold \"yarn run server dev\" \"yarn expo dev\"", + "core": "yarn workspace @stump/core", + "prisma": "yarn core prisma", "codegen": "cargo run --package codegen", - "build:server": "pnpm run server build", - "build:web": "pnpm web build && pnpm build:server", - "build:desktop": "pnpm desktop build", + "build:server": "yarn run server build", + "build:web": "yarn web build && yarn build:server", + "build:desktop": "yarn desktop build", "prettify": "prettier --config prettier.config.js --ignore-path .prettierignore --write .", - "nuke": "cargo clean && cargo codegen && pnpm -r nuke && pnpm i" + "nuke": "yarn -r nuke && yarn" }, "devDependencies": { "@babel/core": "^7.23.2", @@ -58,8 +67,15 @@ "cargo fmt --check --manifest-path=apps/desktop/src-tauri/Cargo.toml --" ] }, - "packageManager": "pnpm@8.6.1", "engines": { - "node": "20.0.0" + "node": "^20.0.0" + }, + "packageManager": "yarn@1.22.21", + "yarn": { + "peerDependencyRules": { + "ignoreMissing": [ + "@babel/core" + ] + } } } diff --git a/packages/api/package.json b/packages/api/package.json index 9ea024638..9695bda3d 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -9,12 +9,12 @@ }, "license": "MIT", "devDependencies": { - "@types/qs": "^6.9.9", - "typescript": "^5.3.3" + "@types/qs": "^6.9.14", + "typescript": "^5.4.5" }, "dependencies": { - "@stump/types": "workspace:*", - "axios": "^1.5.1", - "qs": "^6.11.2" + "@stump/types": "*", + "axios": "^1.6.8", + "qs": "^6.12.1" } } diff --git a/packages/api/src/auth.ts b/packages/api/src/auth.ts index c2fbd9436..089550ad3 100644 --- a/packages/api/src/auth.ts +++ b/packages/api/src/auth.ts @@ -1,23 +1,23 @@ import type { LoginOrRegisterArgs, User } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' // TODO: types -export function me(): Promise> { +export function me(): Promise> { return API.get('/auth/me') } -export function login(input: LoginOrRegisterArgs): Promise> { +export function login(input: LoginOrRegisterArgs): Promise> { return API.post('/auth/login', input) } -export function register(payload: LoginOrRegisterArgs) { +export function register(payload: LoginOrRegisterArgs): Promise> { return API.post('/auth/register', payload) } -export function logout(): Promise> { +export function logout(): Promise> { return API.post('/auth/logout') } diff --git a/packages/api/src/bookClub.ts b/packages/api/src/bookClub.ts index cade3d361..867caffe3 100644 --- a/packages/api/src/bookClub.ts +++ b/packages/api/src/bookClub.ts @@ -15,11 +15,11 @@ import { UpdateBookClubMember, } from '@stump/types' -import { toUrlParams, urlWithParams } from '.' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' +import { toUrlParams, urlWithParams } from './utils' -export async function getBookClubs(params?: GetBookClubsParams): Promise> { +export async function getBookClubs(params?: GetBookClubsParams): Promise> { if (params) { const searchParams = toUrlParams(params) return API.get(urlWithParams('/book-clubs', searchParams)) @@ -28,31 +28,31 @@ export async function getBookClubs(params?: GetBookClubsParams): Promise> { +export async function createBookClub(payload: CreateBookClub): Promise> { return API.post('/book-clubs', payload) } -export async function getBookClubById(id: string): Promise> { +export async function getBookClubById(id: string): Promise> { return API.get(`/book-clubs/${id}`) } export async function updateBookClub( id: string, payload: UpdateBookClub, -): Promise> { +): Promise> { return API.put(`/book-clubs/${id}`, payload) } export async function getBookClubInvitations( bookClubId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/invitations`) } export async function createBookClubInvitation( bookClubId: string, payload: CreateBookClubInvitation, -): Promise> { +): Promise> { return API.post(`/book-clubs/${bookClubId}/invitations`, payload) } @@ -60,25 +60,25 @@ export async function respondToBookClubInvitation( bookClubId: string, invitationId: string, payload: BookClubInvitationAnswer, -): Promise> { +): Promise> { return API.put(`/book-clubs/${bookClubId}/invitations/${invitationId}`, payload) } -export async function getBookClubMembers(bookClubId: string): Promise> { +export async function getBookClubMembers(bookClubId: string): Promise> { return API.get(`/book-clubs/${bookClubId}/members`) } export async function createBookClubMember( bookClubId: string, payload: CreateBookClubMember, -): Promise> { +): Promise> { return API.post(`/book-clubs/${bookClubId}/members`, payload) } export async function getBookClubMember( bookClubId: string, memberId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/members/${memberId}`) } @@ -86,44 +86,44 @@ export async function updateBookClubMember( bookClubId: string, memberId: string, payload: CreateBookClubMember, -): Promise> { +): Promise> { return API.put(`/book-clubs/${bookClubId}/members/${memberId}`, payload) } export async function deleteBookClubMember( bookClubId: string, memberId: string, -): Promise> { +): Promise> { return API.delete(`/book-clubs/${bookClubId}/members/${memberId}`) } export async function getBookClubSchedule( bookClubId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/schedule`) } export async function createBookClubSchedule( bookClubId: string, -): Promise> { +): Promise> { return API.post(`/book-clubs/${bookClubId}/schedule`) } -export async function getBookClubCurrentBook(bookClubId: string): Promise> { +export async function getBookClubCurrentBook(bookClubId: string): Promise> { // TODO: maybe remove /schedule from the endpoint return API.get(`/book-clubs/${bookClubId}/schedule/current-book`) } export async function getBookClubCurrentChat( bookClubId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/chats/current`) } export async function getBookClubChatById( bookClubId: string, chatId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/chats/${chatId}`) } @@ -131,7 +131,7 @@ export async function getBookClubChatThread( bookClubId: string, chatId: string, threadId: string, -): Promise> { +): Promise> { return API.get(`/book-clubs/${bookClubId}/chats/${chatId}/threads/${threadId}`) } diff --git a/packages/api/src/config.ts b/packages/api/src/config.ts new file mode 100644 index 000000000..20e123caf --- /dev/null +++ b/packages/api/src/config.ts @@ -0,0 +1,12 @@ +import type { ClaimResponse } from '@stump/types' + +import { API } from '.' +import { ApiResult } from './types' + +export function ping(): Promise> { + return API.get('/ping') +} + +export async function checkIsClaimed(): Promise> { + return API.get('/claim') +} diff --git a/packages/api/src/epub.ts b/packages/api/src/epub.ts index 497c9c629..c9d072d12 100644 --- a/packages/api/src/epub.ts +++ b/packages/api/src/epub.ts @@ -8,19 +8,19 @@ import type { } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' export function getEpubBaseUrl(id: string): string { return `${API.getUri()}/epub/${id}` } -export function getEpubById(id: string): Promise> { +export function getEpubById(id: string): Promise> { return API.get(`/epub/${id}`) } export function updateEpubProgress( payload: UpdateEpubProgress & { id: string }, -): Promise> { +): Promise> { return API.put(`/epub/${payload.id}/progress`, payload) } @@ -30,22 +30,22 @@ export function getEpubResource(payload: { id: string root?: string resourceId: string -}): Promise> { +}): Promise> { return API.get(`/epub/${payload.id}/${payload.root ?? 'META-INF'}/${payload.resourceId}`) } -export function getBookmarks(id: string): Promise> { +export function getBookmarks(id: string): Promise> { return API.get(`/epub/${id}/bookmarks`) } export function createBookmark( id: string, payload: CreateOrUpdateBookmark, -): Promise> { +): Promise> { return API.post(`/epub/${id}/bookmarks`, payload) } -export function deleteBookmark(id: string, payload: DeleteBookmark): Promise> { +export function deleteBookmark(id: string, payload: DeleteBookmark): Promise> { return API.delete(`/epub/${id}/bookmarks`, { data: payload }) } diff --git a/packages/api/src/filesystem.ts b/packages/api/src/filesystem.ts index 03d96db76..5492ef8ee 100644 --- a/packages/api/src/filesystem.ts +++ b/packages/api/src/filesystem.ts @@ -1,7 +1,7 @@ import type { DirectoryListing, DirectoryListingInput, Pageable } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' interface ListDirectoryFnInput extends DirectoryListingInput { page?: number @@ -9,7 +9,7 @@ interface ListDirectoryFnInput extends DirectoryListingInput { export function listDirectory( input?: ListDirectoryFnInput, -): Promise>> { +): Promise>> { if (input?.page != null) { return API.post(`/filesystem?page=${input.page}`, input) } diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 3a9a799c0..d97a9a4bc 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,3 @@ -// TODO: remove these export * export { authApi, authQueryKeys } from './auth' export { API, apiIsInitialized, checkUrl, initializeApi, isUrl } from './axios' export { bookClubApi, bookClubQueryKeys } from './bookClub' @@ -6,7 +5,7 @@ export { epubApi, epubQueryKeys, getEpubResource, updateEpubProgress } from './e export { filesystemApi, filesystemQueryKeys } from './filesystem' export * from './job' export { getLibraryThumbnail, libraryApi, libraryQueryKeys } from './library' -export * from './log' +export { logApi, logQueryKeys } from './log' export { getMediaDownloadUrl, getMediaPage, diff --git a/packages/api/src/job.ts b/packages/api/src/job.ts index 9f9c104ac..f1f74f5d1 100644 --- a/packages/api/src/job.ts +++ b/packages/api/src/job.ts @@ -1,10 +1,12 @@ -import type { JobDetail, JobSchedulerConfig, UpdateSchedulerConfig } from '@stump/types' +import type { JobSchedulerConfig, PersistedJob, UpdateSchedulerConfig } from '@stump/types' import { API } from './axios' -import { ApiResult, PageableApiResult } from './types' +import { APIResult, PageableAPIResult } from './types' import { toUrlParams } from './utils' -export function getJobs(params?: Record): Promise> { +export function getJobs( + params?: Record, +): Promise> { if (params) { const searchParams = toUrlParams(params) return API.get(`/jobs?${searchParams.toString()}`) @@ -13,25 +15,25 @@ export function getJobs(params?: Record): Promise> { +export function cancelJob(id: string): Promise> { return API.delete(`/jobs/${id}/cancel`) } -export function deleteJob(id: string): Promise> { +export function deleteJob(id: string): Promise> { return API.delete(`/jobs/${id}`) } -export function deleteAllJobs(): Promise> { +export function deleteAllJobs(): Promise> { return API.delete('/jobs') } -export function getJobSchedulerConfig(): Promise> { +export function getJobSchedulerConfig(): Promise> { return API.get('/jobs/scheduler-config') } export function updateJobSchedulerConfig( config: UpdateSchedulerConfig, -): Promise> { +): Promise> { return API.post('/jobs/scheduler-config', config) } diff --git a/packages/api/src/library.ts b/packages/api/src/library.ts index 3d29b435d..1c625d238 100644 --- a/packages/api/src/library.ts +++ b/packages/api/src/library.ts @@ -1,29 +1,40 @@ import type { CleanLibraryResponse, CreateLibrary, - LibrariesStats, Library, LibraryScanMode, + LibraryStats, PatchLibraryThumbnail, Series, UpdateLibrary, } from '@stump/types' import { API } from './axios' -import { ApiResult, PageableApiResult, PagedQueryParams } from './types' +import { APIResult, PageableAPIResult, PagedQueryParams } from './types' import { mergePageParams, toUrlParams, urlWithParams } from './utils' export function getLibraries( params: Record = { unpaged: true }, -): Promise> { +): Promise> { return API.get(urlWithParams('/libraries', toUrlParams(params))) } -export function getLibrariesStats(): Promise> { +export function getTotalLibraryStats(): Promise> { return API.get('/libraries/stats') } -export function getLibraryById(id: string): Promise> { +export function getLibraryStats( + id: string, + params?: Record, +): Promise> { + if (params) { + return API.get(`/libraries/${id}/stats?${toUrlParams(params)}`) + } else { + return API.get(`/libraries/${id}/stats`) + } +} + +export function getLibraryById(id: string): Promise> { return API.get(`/libraries/${id}`) } @@ -34,7 +45,7 @@ export function getLibraryThumbnail(id: string): string { export function getLibrarySeries( id: string, { page, page_size, params }: PagedQueryParams, -): Promise> { +): Promise> { const searchParams = mergePageParams({ page, page_size, params }) return API.get(urlWithParams(`/libraries/${id}/series`, searchParams)) } @@ -45,11 +56,11 @@ export function getLibrarySeries( export function scanLibary(params: { id: string mode?: LibraryScanMode -}): Promise> { +}): Promise> { return API.get(`/libraries/${params.id}/scan?scan_mode=${params.mode ?? 'BATCHED'}`) } -export function cleanLibrary(id: string): Promise> { +export function cleanLibrary(id: string): Promise> { return API.put(`/libraries/${id}/clean`) } @@ -68,11 +79,11 @@ export function regenerateThumbnails(id: string, force?: boolean) { return API.post(`/libraries/${id}/thumbnail/generate`, { force_regenerate: !!force }) } -export function createLibrary(payload: CreateLibrary): Promise> { +export function createLibrary(payload: CreateLibrary): Promise> { return API.post('/libraries', payload) } -export function editLibrary(payload: UpdateLibrary): Promise> { +export function editLibrary(payload: UpdateLibrary): Promise> { return API.put(`/libraries/${payload.id}`, payload) } @@ -94,24 +105,35 @@ export function visitLibrary(id: string) { return API.put(`/libraries/last-visited/${id}`) } -export function getLastVisitedLibrary(): Promise> { +export function getLastVisitedLibrary(): Promise> { return API.get('/libraries/last-visited') } +export function getExcludedUsers(id: string) { + return API.get(`/libraries/${id}/excluded-users`) +} + +export function updateExcludedUsers(id: string, user_ids: string[]) { + return API.post(`/libraries/${id}/excluded-users`, { user_ids }) +} + export const libraryApi = { cleanLibrary, createLibrary, deleteLibrary, deleteLibraryThumbnails, editLibrary, + getExcludedUsers, getLastVisitedLibrary, getLibraries, - getLibrariesStats, getLibraryById, getLibrarySeries, + getLibraryStats, + getTotalLibraryStats, patchLibraryThumbnail, regenerateThumbnails, scanLibary, + updateExcludedUsers, uploadLibraryThumbnail, visitLibrary, } @@ -122,14 +144,17 @@ export const libraryQueryKeys: Record = { deleteLibrary: 'library.deleteLibrary', deleteLibraryThumbnails: 'library.deleteLibraryThumbnails', editLibrary: 'library.editLibrary', + getExcludedUsers: 'library.getExcludedUsers', getLastVisitedLibrary: 'library.getLastVisitedLibrary', getLibraries: 'library.getLibraries', - getLibrariesStats: 'library.getLibrariesStats', getLibraryById: 'library.getLibraryById', getLibrarySeries: 'library.getLibrarySeries', + getLibraryStats: 'library.getLibraryStats', + getTotalLibraryStats: 'library.getTotalLibraryStats', patchLibraryThumbnail: 'library.patchLibraryThumbnail', regenerateThumbnails: 'library.regenerateThumbnails', scanLibary: 'library.scanLibary', + updateExcludedUsers: 'library.updateExcludedUsers', uploadLibraryThumbnail: 'library.uploadLibraryThumbnail', visitLibrary: 'library.visitLibrary', } diff --git a/packages/api/src/log.ts b/packages/api/src/log.ts index 1a9daaa82..1d5c0768a 100644 --- a/packages/api/src/log.ts +++ b/packages/api/src/log.ts @@ -1,22 +1,34 @@ -import type { LogMetadata } from '@stump/types' +import type { Log, LogMetadata } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult, PageableAPIResult } from './types' +import { toUrlParams } from './utils' -export function getLogFileMeta(): Promise> { - return API.get('/logs') +export function getLogs(params?: Record): Promise> { + if (params) { + const searchParams = toUrlParams(params) + return API.get(`/logs?${searchParams.toString()}`) + } else { + return API.get('/logs') + } +} + +export function getLogFileMeta(): Promise> { + return API.get('/logs/file/info') } export function clearLogFile() { - return API.delete('/logs') + return API.delete('/logs/file') } -const logApi = { +export const logApi = { clearLogFile, getLogFileMeta, + getLogs, } export const logQueryKeys: Record = { clearLogFile: 'log.clearLogFile', getLogFileMeta: 'log.getLogFileMeta', + getLogs: 'log.getLogs', } diff --git a/packages/api/src/media.ts b/packages/api/src/media.ts index 536f6321c..46f31192f 100644 --- a/packages/api/src/media.ts +++ b/packages/api/src/media.ts @@ -7,12 +7,12 @@ import type { } from '@stump/types' import { API } from './axios' -import { ApiResult, CursorQueryParams, PageableApiResult } from './types' +import { APIResult, CursorQueryParams, PageableAPIResult } from './types' import { mergeCursorParams, toUrlParams, urlWithParams } from './utils' -type GetMediaById = ApiResult +type GetMediaById = APIResult -export function getMedia(filters?: Record): Promise> { +export function getMedia(filters?: Record): Promise> { const params = toUrlParams(filters) return API.get(urlWithParams('/media', params)) } @@ -21,17 +21,16 @@ export function getMediaWithCursor({ afterId, limit, params, -}: CursorQueryParams): Promise> { +}: CursorQueryParams): Promise> { const searchParams = mergeCursorParams({ afterId, limit, params }) return API.get(urlWithParams('/media', searchParams)) } -export function getPaginatedMedia(page: number): Promise> { +export function getPaginatedMedia(page: number): Promise> { return API.get(`/media?page=${page}`) } export function getMediaById(id: string, params?: Record): Promise { - // return API.get(`/media/${id}?load_series=true`) if (params) { return API.get(`/media/${id}?${toUrlParams(params)}`) } else { @@ -39,7 +38,7 @@ export function getMediaById(id: string, params?: Record): Prom } } -export function getMediaByPath(path: string): Promise> { +export function getMediaByPath(path: string): Promise> { return API.get(`/media/path/${encodeURIComponent(path)}`) } @@ -47,7 +46,7 @@ export function getRecentlyAddedMedia({ afterId, limit, params, -}: CursorQueryParams): Promise> { +}: CursorQueryParams): Promise> { const searchParams = mergeCursorParams({ afterId, limit, params }) return API.get(urlWithParams('/media/recently-added', searchParams)) } @@ -56,7 +55,7 @@ export function getInProgressMedia({ afterId, limit, params, -}: CursorQueryParams): Promise> { +}: CursorQueryParams): Promise> { const searchParams = mergeCursorParams({ afterId, limit, params }) return API.get(urlWithParams('/media/keep-reading', searchParams)) } @@ -74,7 +73,7 @@ export function getMediaPage(id: string, page: number): string { return `${API.getUri()}/media/${id}/page/${page}` } -export function updateMediaProgress(id: string, page: number): Promise> { +export function updateMediaProgress(id: string, page: number): Promise> { return API.put(`/media/${id}/progress/${page}`) } @@ -95,7 +94,7 @@ export function uploadMediaThumbnail(id: string, file: File) { export function putMediaCompletion( id: string, payload: PutMediaCompletionStatus, -): Promise> { +): Promise> { return API.put(`/media/${id}/progress/complete`, payload) } diff --git a/packages/api/src/metadata.ts b/packages/api/src/metadata.ts index b7b8cfeee..2ed8c0f05 100644 --- a/packages/api/src/metadata.ts +++ b/packages/api/src/metadata.ts @@ -1,52 +1,52 @@ import { MediaMetadataOverview } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' import { toUrlParams, urlWithParams } from './utils' export function getMediaMetadataOverview( params?: Record, -): Promise> { +): Promise> { return API.get(urlWithParams('/metadata/media', toUrlParams(params))) } -export function getGenres(): Promise> { +export function getGenres(): Promise> { return API.get('/metadata/media/genres') } -export function getWriters(): Promise> { +export function getWriters(): Promise> { return API.get('/metadata/media/writers') } -export function getPencillers(): Promise> { +export function getPencillers(): Promise> { return API.get('/metadata/media/pencillers') } -export function getInkers(): Promise> { +export function getInkers(): Promise> { return API.get('/metadata/media/inkers') } -export function getColorists(): Promise> { +export function getColorists(): Promise> { return API.get('/metadata/media/colorists') } -export function getLetterers(): Promise> { +export function getLetterers(): Promise> { return API.get('/metadata/media/letterers') } -export function getEditors(): Promise> { +export function getEditors(): Promise> { return API.get('/metadata/media/editors') } -export function getPublishers(): Promise> { +export function getPublishers(): Promise> { return API.get('/metadata/media/publishers') } -export function getCharacters(): Promise> { +export function getCharacters(): Promise> { return API.get('/metadata/media/characters') } -export function getTeams(): Promise> { +export function getTeams(): Promise> { return API.get('/metadata/media/teams') } diff --git a/packages/api/src/series.ts b/packages/api/src/series.ts index 7d69f9b75..78c501079 100644 --- a/packages/api/src/series.ts +++ b/packages/api/src/series.ts @@ -2,10 +2,10 @@ import type { Media, PatchSeriesThumbnail, Series } from '@stump/types' import { API } from './axios' import { mediaApi } from './media' -import { ApiResult, CursorQueryParams, PageableApiResult, PagedQueryParams } from './types' +import { APIResult, CursorQueryParams, PageableAPIResult, PagedQueryParams } from './types' import { mergeCursorParams, mergePageParams, toUrlParams, urlWithParams } from './utils' -export function getSeries(filters?: Record): Promise> { +export function getSeries(filters?: Record): Promise> { const params = toUrlParams(filters) return API.get(urlWithParams('/series', params)) } @@ -13,13 +13,13 @@ export function getSeries(filters?: Record): Promise, -): Promise> { +): Promise> { return API.get(urlWithParams(`/series/${id}`, toUrlParams(params))) } export function getSeriesWithCursor( params: CursorQueryParams, -): Promise> { +): Promise> { const searchParams = mergeCursorParams(params) return API.get(urlWithParams('/series', searchParams)) } @@ -27,7 +27,7 @@ export function getSeriesWithCursor( export function getSeriesMedia( id: string, { page, page_size, params }: PagedQueryParams, -): Promise> { +): Promise> { const searchParams = mergePageParams({ page, page_size, params }) return API.get(urlWithParams(`/series/${id}/media`, searchParams)) } @@ -35,7 +35,7 @@ export function getSeriesMedia( export function getRecentlyAddedSeries( page: number, params?: URLSearchParams, -): Promise> { +): Promise> { if (params) { params.set('page', page.toString()) return API.get(`/series/recently-added?${params.toString()}`) @@ -44,7 +44,7 @@ export function getRecentlyAddedSeries( return API.get(`/series/recently-added?page=${page}`) } -export function getNextInSeries(id: string): Promise> { +export function getNextInSeries(id: string): Promise> { return API.get(`/series/${id}/media/next`) } @@ -55,7 +55,7 @@ export function getNextMediaInSeries( series_id: string, media_id: string, limit = 25, -): Promise> { +): Promise> { return mediaApi.getMedia({ cursor: media_id, limit: limit.toString(), diff --git a/packages/api/src/server.ts b/packages/api/src/server.ts index e8140a7e3..ba9356513 100644 --- a/packages/api/src/server.ts +++ b/packages/api/src/server.ts @@ -1,9 +1,9 @@ import type { ClaimResponse, StumpVersion, UpdateCheck } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' -export function getStumpVersion(): Promise> { +export function getStumpVersion(): Promise> { return API.post('/version') } @@ -15,7 +15,7 @@ export function ping() { return API.get('/ping') } -export async function checkIsClaimed(): Promise> { +export async function checkIsClaimed(): Promise> { return API.get('/claim') } diff --git a/packages/api/src/smartList.ts b/packages/api/src/smartList.ts index f0203c28a..78208c3cd 100644 --- a/packages/api/src/smartList.ts +++ b/packages/api/src/smartList.ts @@ -10,10 +10,10 @@ import { } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' import { toUrlParams, urlWithParams } from './utils' -export async function getSmartLists(params?: GetSmartListsParams): Promise> { +export async function getSmartLists(params?: GetSmartListsParams): Promise> { if (params) { const searchParams = toUrlParams(params) return API.get(urlWithParams('/smart-lists', searchParams)) @@ -22,14 +22,14 @@ export async function getSmartLists(params?: GetSmartListsParams): Promise> { +export async function createSmartList(payload: SmartList): Promise> { return API.post('/smart-lists', payload) } export async function getSmartListById( id: string, params?: SmartListRelationOptions, -): Promise> { +): Promise> { if (params) { const searchParams = toUrlParams(params) return API.get(urlWithParams(`/smart-lists/${id}`, searchParams)) @@ -38,11 +38,11 @@ export async function getSmartListById( } } -export async function getSmartListMeta(id: string): Promise> { +export async function getSmartListMeta(id: string): Promise> { return API.get(`/smart-lists/${id}/meta`) } -export async function getSmartListItems(id: string): Promise> { +export async function getSmartListItems(id: string): Promise> { return API.get(`/smart-lists/${id}/items`) } @@ -50,18 +50,18 @@ export async function getSmartListItems(id: string): Promise> { +): Promise> { return API.put(`/smart-lists/${id}`, payload) } -export async function deleteSmartList(id: string): Promise> { +export async function deleteSmartList(id: string): Promise> { return API.delete(`/smart-lists/${id}`) } export async function createSmartListView( listId: string, params: CreateOrUpdateSmartListView, -): Promise> { +): Promise> { return API.post(`/smart-lists/${listId}/views`, params) } @@ -69,7 +69,7 @@ export async function updateSmartListView( listId: string, name: string, params: CreateOrUpdateSmartListView, -): Promise> { +): Promise> { return API.put(`/smart-lists/${listId}/views/${name}`, params) } diff --git a/packages/api/src/tag.ts b/packages/api/src/tag.ts index 699927ffa..437e36040 100644 --- a/packages/api/src/tag.ts +++ b/packages/api/src/tag.ts @@ -1,13 +1,13 @@ import type { Tag } from '@stump/types' import { API } from './axios' -import { ApiResult } from './types' +import { APIResult } from './types' -export function getAllTags(): Promise> { +export function getAllTags(): Promise> { return API.get('/tags') } -export function createTags(tags: string[]): Promise> { +export function createTags(tags: string[]): Promise> { return API.post('/tags', { tags }) } diff --git a/packages/api/src/types.ts b/packages/api/src/types.ts index 3fc98e708..5afb8f02f 100644 --- a/packages/api/src/types.ts +++ b/packages/api/src/types.ts @@ -1,7 +1,7 @@ -import { ApiError, Pageable } from '@stump/types' +import { APIError, Pageable } from '@stump/types' -export type ApiResult = import('axios').AxiosResponse> -export type PageableApiResult = ApiResult> +export type APIResult = import('axios').AxiosResponse> +export type PageableAPIResult = APIResult> export type PagedQueryParams = { page?: number diff --git a/packages/api/src/user.ts b/packages/api/src/user.ts index 061ab62c9..d77f90253 100644 --- a/packages/api/src/user.ts +++ b/packages/api/src/user.ts @@ -1,6 +1,8 @@ import type { + Arrangement, CreateUser, LoginActivity, + NavigationItem, UpdateUser, UpdateUserPreferences, User, @@ -8,10 +10,10 @@ import type { } from '@stump/types' import { API } from './axios' -import { ApiResult, PageableApiResult } from './types' +import { APIResult, PageableAPIResult } from './types' import { toUrlParams } from './utils' -export function getUsers(params?: Record): Promise> { +export function getUsers(params?: Record): Promise> { if (params) { const searchParams = toUrlParams(params) return API.get(`/users?${searchParams.toString()}`) @@ -20,11 +22,11 @@ export function getUsers(params?: Record): Promise> { +export function getUserById(userId: string): Promise> { return API.get(`/users/${userId}`) } -export function getUserPreferences(userId: string): Promise> { +export function getUserPreferences(userId: string): Promise> { return API.get(`/users/${userId}/preferences`) } @@ -33,7 +35,7 @@ export function getUserPreferences(userId: string): Promise> { +): Promise> { return API.put(`/users/me/preferences`, preferences) } @@ -43,19 +45,19 @@ export function updatePreferences( export function updateUserPreferences( userId: string, preferences: UserPreferences, -): Promise> { +): Promise> { return API.put(`/users/${userId}/preferences`, preferences) } -export function createUser(params: CreateUser): Promise> { +export function createUser(params: CreateUser): Promise> { return API.post(`/users`, params) } -export function updateUser(userId: string, params: UpdateUser): Promise> { +export function updateUser(userId: string, params: UpdateUser): Promise> { return API.put(`/users/${userId}`, params) } -export function updateViewer(params: UpdateUser): Promise> { +export function updateViewer(params: UpdateUser): Promise> { return API.put(`/users/me`, params) } @@ -64,7 +66,7 @@ type DeleteUser = { hardDelete?: boolean } -export function deleteUser({ userId, hardDelete }: DeleteUser): Promise> { +export function deleteUser({ userId, hardDelete }: DeleteUser): Promise> { return API.delete(`/users/${userId}`, { data: { hard_delete: hardDelete, @@ -72,28 +74,40 @@ export function deleteUser({ userId, hardDelete }: DeleteUser): Promise> { +export function getLoginActivityForUser(userId: string): Promise> { return API.get(`/users/${userId}/login-activity`) } -export function getLoginActivity(): Promise> { +export function getLoginActivity(): Promise> { return API.get(`/users/login-activity`) } -export function deleteAllLoginActivity(): Promise> { +export function deleteAllLoginActivity(): Promise> { return API.delete(`/users/login-activity`) } -export function setLockStatus(userId: string, lock: boolean): Promise> { +export function setLockStatus(userId: string, lock: boolean): Promise> { return API.put(`/users/${userId}/lock`, { lock, }) } -export function deleteUserSessions(userId: string): Promise> { +export function deleteUserSessions(userId: string): Promise> { return API.delete(`/users/${userId}/sessions`) } +export function getPreferredNavigationArrangement(): Promise< + APIResult> +> { + return API.get('/users/me/navigation-arrangement') +} + +export function setPreferredNavigationArrangement( + arrangement: Arrangement, +): Promise>> { + return API.put('/users/me/navigation-arrangement', arrangement) +} + export const userApi = { createUser, deleteAllLoginActivity, @@ -101,10 +115,12 @@ export const userApi = { deleteUserSessions, getLoginActivity, getLoginActivityForUser, + getPreferredNavigationArrangement, getUserById, getUserPreferences, getUsers, setLockStatus, + setPreferredNavigationArrangement, updatePreferences, updateUser, updateUserPreferences, @@ -118,10 +134,12 @@ export const userQueryKeys: Record = { deleteUserSessions: 'user.deleteUserSessions', getLoginActivity: 'user.getLoginActivity', getLoginActivityForUser: 'user.getLoginActivityForUser', + getPreferredNavigationArrangement: 'user.getPreferredNavigationArrangement', getUserById: 'user.getUserById', getUserPreferences: 'user.getUserPreferences', getUsers: 'user.getUsers', setLockStatus: 'user.setLockStatus', + setPreferredNavigationArrangement: 'user.setPreferredNavigationArrangement', updatePreferences: 'user.updatePreferences', updateUser: 'user.updateUser', updateUserPreferences: 'user.updateUserPreferences', diff --git a/packages/browser/package.json b/packages/browser/package.json new file mode 100644 index 000000000..8c593bcd4 --- /dev/null +++ b/packages/browser/package.json @@ -0,0 +1,77 @@ +{ + "name": "@stump/browser", + "version": "0.0.1", + "description": "", + "license": "MIT", + "private": true, + "main": "src/index.ts", + "exports": { + ".": "./src/index.ts", + "./assets/*": "./src/assets/*" + }, + "dependencies": { + "@dnd-kit/core": "^6.1.0", + "@dnd-kit/sortable": "^8.0.0", + "@hookform/resolvers": "^3.3.4", + "@stump/api": "*", + "@stump/client": "*", + "@stump/components": "*", + "@stump/i18n": "*", + "@stump/types": "*", + "@tanstack/react-query": "^4.36.1", + "@tanstack/react-query-devtools": "^4.36.1", + "@tanstack/react-table": "^8.16.0", + "@tanstack/react-virtual": "3.0.0-beta.18", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.0", + "dayjs": "^1.11.10", + "epubjs": "^0.3.93", + "framer-motion": "^10.18.0", + "i18next": "^23.11.2", + "immer": "^10.0.4", + "lodash.isequal": "^4.5.0", + "lodash.uniqby": "^4.7.0", + "lucide-react": "^0.368.0", + "nprogress": "^0.2.0", + "pluralize": "^8.0.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", + "react-error-boundary": "^4.0.13", + "react-helmet": "^6.1.0", + "react-hook-form": "^7.51.3", + "react-hot-toast": "^2.4.1", + "react-hotkeys-hook": "^4.5.0", + "react-i18next": "^14.1.0", + "react-markdown": "^9.0.1", + "react-router": "^6.22.3", + "react-router-dom": "^6.22.3", + "react-swipeable": "^7.0.1", + "react-virtualized-auto-sizer": "^1.0.24", + "react-virtuoso": "^4.7.8", + "remark-directive": "^3.0.0", + "remark-directive-rehype": "^0.4.2", + "rooks": "^7.14.1", + "ts-pattern": "^5.1.1", + "use-count-up": "^3.0.1", + "zod": "^3.22.4", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@types/lodash.isequal": "^4.5.8", + "@types/lodash.uniqby": "^4.7.9", + "@types/node": "^20.12.7", + "@types/nprogress": "^0.2.3", + "@types/pluralize": "^0.0.33", + "@types/react": "^18.2.78", + "@types/react-dom": "^18.2.25", + "@types/react-helmet": "^6.1.11", + "@types/react-router-dom": "^5.3.3", + "@vitejs/plugin-react": "^4.2.1", + "typescript": "^5.4.5", + "vite": "^5.2.8" + }, + "peerDependencies": { + "react-router-dom": "^6.22.3" + } +} diff --git a/interface/public/assets/fallbacks/image-file.svg b/packages/browser/public/assets/fallbacks/image-file.svg similarity index 100% rename from interface/public/assets/fallbacks/image-file.svg rename to packages/browser/public/assets/fallbacks/image-file.svg diff --git a/interface/public/favicon.ico b/packages/browser/public/assets/favicon.ico similarity index 100% rename from interface/public/favicon.ico rename to packages/browser/public/assets/favicon.ico diff --git a/interface/public/assets/stump-logo--irregular.png b/packages/browser/public/assets/favicon.png similarity index 100% rename from interface/public/assets/stump-logo--irregular.png rename to packages/browser/public/assets/favicon.png diff --git a/interface/public/assets/icons/archive.svg b/packages/browser/public/assets/icons/archive.svg similarity index 100% rename from interface/public/assets/icons/archive.svg rename to packages/browser/public/assets/icons/archive.svg diff --git a/interface/public/assets/icons/epub.svg b/packages/browser/public/assets/icons/epub.svg similarity index 100% rename from interface/public/assets/icons/epub.svg rename to packages/browser/public/assets/icons/epub.svg diff --git a/interface/public/assets/icons/folder.png b/packages/browser/public/assets/icons/folder.png similarity index 100% rename from interface/public/assets/icons/folder.png rename to packages/browser/public/assets/icons/folder.png diff --git a/interface/public/assets/icons/pdf.svg b/packages/browser/public/assets/icons/pdf.svg similarity index 100% rename from interface/public/assets/icons/pdf.svg rename to packages/browser/public/assets/icons/pdf.svg diff --git a/packages/browser/public/assets/stump-logo--irregular-lg.png b/packages/browser/public/assets/stump-logo--irregular-lg.png new file mode 100644 index 000000000..0ea4f365b Binary files /dev/null and b/packages/browser/public/assets/stump-logo--irregular-lg.png differ diff --git a/interface/public/assets/stump-logo--irregular-sm.png b/packages/browser/public/assets/stump-logo--irregular-sm.png similarity index 100% rename from interface/public/assets/stump-logo--irregular-sm.png rename to packages/browser/public/assets/stump-logo--irregular-sm.png diff --git a/interface/public/assets/stump-logo--irregular-xs.png b/packages/browser/public/assets/stump-logo--irregular-xs.png similarity index 100% rename from interface/public/assets/stump-logo--irregular-xs.png rename to packages/browser/public/assets/stump-logo--irregular-xs.png diff --git a/packages/browser/public/assets/stump-logo--irregular.png b/packages/browser/public/assets/stump-logo--irregular.png new file mode 100644 index 000000000..0ea4f365b Binary files /dev/null and b/packages/browser/public/assets/stump-logo--irregular.png differ diff --git a/interface/public/assets/stump-logo--square.png b/packages/browser/public/assets/stump-logo--square.png similarity index 100% rename from interface/public/assets/stump-logo--square.png rename to packages/browser/public/assets/stump-logo--square.png diff --git a/interface/public/assets/stump-splash.gif b/packages/browser/public/assets/stump-splash.gif similarity index 100% rename from interface/public/assets/stump-splash.gif rename to packages/browser/public/assets/stump-splash.gif diff --git a/packages/browser/public/assets/svg/404.svg b/packages/browser/public/assets/svg/404.svg new file mode 100644 index 000000000..5eb0e1121 --- /dev/null +++ b/packages/browser/public/assets/svg/404.svg @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/browser/public/assets/svg/bomb.svg b/packages/browser/public/assets/svg/bomb.svg new file mode 100644 index 000000000..d4e796b49 --- /dev/null +++ b/packages/browser/public/assets/svg/bomb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/browser/public/assets/svg/construction-site-2.svg b/packages/browser/public/assets/svg/construction-site-2.svg new file mode 100644 index 000000000..140df6b90 --- /dev/null +++ b/packages/browser/public/assets/svg/construction-site-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/browser/public/assets/svg/construction-site.svg b/packages/browser/public/assets/svg/construction-site.svg new file mode 100644 index 000000000..6715aa689 --- /dev/null +++ b/packages/browser/public/assets/svg/construction-site.svg @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/browser/public/favicon.ico b/packages/browser/public/favicon.ico new file mode 100644 index 000000000..29e1498fe Binary files /dev/null and b/packages/browser/public/favicon.ico differ diff --git a/interface/src/App.tsx b/packages/browser/src/App.tsx similarity index 54% rename from interface/src/App.tsx rename to packages/browser/src/App.tsx index 68b7ffd1f..55f129aa7 100644 --- a/interface/src/App.tsx +++ b/packages/browser/src/App.tsx @@ -2,14 +2,7 @@ import './styles/index.css' import '@stump/components/styles/overrides.css' import { initializeApi } from '@stump/api' -import { - AppProps, - AppPropsContext, - JobContextProvider, - StumpClientContextProvider, - useStumpStore, - useUserStore, -} from '@stump/client' +import { StumpClientContextProvider, StumpClientProps } from '@stump/client' import { defaultContext } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { useEffect, useState } from 'react' @@ -22,33 +15,43 @@ import Notifications from '@/components/Notifications' import { AppRouter } from './AppRouter' import { API_VERSION } from './index' +import { useAppStore, useUserStore } from './stores' const IS_DEVELOPMENT = import.meta.env.MODE === 'development' -function RouterContainer(props: { appProps: AppProps }) { +export default function StumpWebClient(props: StumpClientProps) { + return ( + + + + + + ) +} + +function RouterContainer(props: StumpClientProps) { const location = useLocation() const navigate = useNavigate() const [mounted, setMounted] = useState(false) - const [appProps, setAppProps] = useState(props.appProps) - const { userPreferences } = useUserStore(({ userPreferences }) => ({ userPreferences })) - const { baseUrl, setBaseUrl } = useStumpStore(({ baseUrl, setBaseUrl }) => ({ - baseUrl, - setBaseUrl, + const { setUser, userPreferences } = useUserStore((store) => ({ + setUser: store.setUser, + userPreferences: store.userPreferences, + })) + const { baseUrl, setBaseUrl, setPlatform, setIsConnectedWithServer } = useAppStore((store) => ({ + baseUrl: store.baseUrl, + setBaseUrl: store.setBaseUrl, + setIsConnectedWithServer: store.setIsConnectedWithServer, + setPlatform: store.setPlatform, })) useEffect( () => { - if (!baseUrl && appProps.baseUrl) { - setBaseUrl(appProps.baseUrl) + if (!baseUrl && props.baseUrl) { + setBaseUrl(props.baseUrl) } else if (baseUrl) { initializeApi(baseUrl, API_VERSION) - - setAppProps((appProps) => ({ - ...appProps, - baseUrl, - })) } setMounted(true) @@ -58,6 +61,10 @@ function RouterContainer(props: { appProps: AppProps }) { [baseUrl], ) + useEffect(() => { + setPlatform(props.platform) + }, [props.platform, setPlatform]) + const appTheme = (userPreferences?.app_theme ?? 'light').toLowerCase() useEffect(() => { const html = document.querySelector('html') @@ -65,7 +72,7 @@ function RouterContainer(props: { appProps: AppProps }) { html?.classList.add(appTheme) }, [appTheme]) - const { setUseDiscordPresence, setDiscordPresence } = appProps + const { setUseDiscordPresence, setDiscordPresence } = props const discordPresenceEnabled = userPreferences?.enable_discord_presence ?? false useEffect(() => { setUseDiscordPresence?.(discordPresenceEnabled) @@ -83,35 +90,35 @@ function RouterContainer(props: { appProps: AppProps }) { }) } + const handleUnathenticatedResponse = (redirectUrl?: string) => { + setUser(null) + if (redirectUrl) { + handleRedirect(redirectUrl) + } + } + + const handleConnectionWithServerChanged = (wasReached: boolean) => { + setIsConnectedWithServer(wasReached) + navigate('/server-connection-error') + } + if (!mounted) { - // TODO: suspend return null } return ( - + {IS_DEVELOPMENT && } - - - Stump - - - - - + + Stump + + ) } - -export default function StumpInterface(props: AppProps) { - return ( - <> - - - - - - - ) -} diff --git a/interface/src/AppLayout.tsx b/packages/browser/src/AppLayout.tsx similarity index 84% rename from interface/src/AppLayout.tsx rename to packages/browser/src/AppLayout.tsx index 1e0e386a9..f797ea256 100644 --- a/interface/src/AppLayout.tsx +++ b/packages/browser/src/AppLayout.tsx @@ -1,5 +1,5 @@ import { isAxiosError } from '@stump/api' -import { useAppProps, useAuthQuery, useCoreEventHandler, useUserStore } from '@stump/client' +import { useAuthQuery, useCoreEventHandler } from '@stump/client' import { cx } from '@stump/components' import { UserPermission, UserPreferences } from '@stump/types' import { Suspense, useCallback, useMemo } from 'react' @@ -13,10 +13,10 @@ import RouteLoadingIndicator from '@/components/RouteLoadingIndicator' import ServerStatusOverlay from '@/components/ServerStatusOverlay' import { AppContext, PermissionEnforcerOptions } from './context' +import { useAppStore, useUserStore } from './stores' export function AppLayout() { - const appProps = useAppProps() - + const platform = useAppStore((state) => state.platform) const location = useLocation() const navigate = useNavigate() const isMobile = useMediaMatch('(max-width: 768px)') @@ -52,6 +52,16 @@ export function AppLayout() { } }, [location, storeUser]) + /** + * If enabled, the client will refetch certain queries to hydrate the UI with + * new data. Otherwise, the client will wait for the job output before deciding + * what data to refetch. + */ + const liveRefetch = useMemo( + () => (storeUser?.user_preferences ?? ({} as UserPreferences)).enable_live_refetch || false, + [storeUser], + ) + /** * Whenever we are in a Stump reader, we remove all navigation elements from * the DOM @@ -64,7 +74,7 @@ export function AppLayout() { const hideSidebar = hideAllNavigation || preferTopBar const hideTopBar = isMobile || hideAllNavigation || !preferTopBar - useCoreEventHandler() + useCoreEventHandler({ liveRefetch }) /** * A callback to enforce a permission on the currently logged in user. @@ -115,7 +125,9 @@ export function AppLayout() { {!hideAllNavigation && } {!hideTopBar && }
- {!hideSidebar &&
- {appProps?.platform !== 'browser' && } + {platform !== 'browser' && } {!location.pathname.match(/\/settings\/jobs/) && } diff --git a/interface/src/AppRouter.tsx b/packages/browser/src/AppRouter.tsx similarity index 56% rename from interface/src/AppRouter.tsx rename to packages/browser/src/AppRouter.tsx index f3c61575b..8aec95865 100644 --- a/interface/src/AppRouter.tsx +++ b/packages/browser/src/AppRouter.tsx @@ -1,40 +1,35 @@ -import { useAppProps } from '@stump/client' -import React from 'react' +import { LocaleProvider } from '@stump/i18n' +import { type AllowedLocale } from '@stump/i18n' +import React, { lazy } from 'react' import { Route, Routes } from 'react-router-dom' import { AppLayout } from './AppLayout.tsx' -import LocaleProvider from './i18n/LocaleProvider.tsx' import BookRouter from './scenes/book/BookRouter.tsx' import BookClubRouter from './scenes/book-club/BookClubRouter.tsx' import LibraryRouter from './scenes/library/LibraryRouter.tsx' import OnBoardingRouter from './scenes/onboarding/OnBoardingRouter.tsx' import SeriesRouter from './scenes/series/SeriesRouter.tsx' import SettingsRouter from './scenes/settings/SettingsRouter.tsx' -import { SmartListRouter } from './scenes/smart-list' +import { SmartListRouter } from './scenes/smart-list/index.ts' +import { useAppStore, useUserStore } from './stores' -// FIXME: this is really annoying -export type LazyComponent = Promise<{ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - default: React.ComponentType -}> - -// I'm still so annoyed at this lol -export const lazily = (loader: () => unknown) => React.lazy(() => loader() as LazyComponent) - -const HomeScene = lazily(() => import('./scenes/home/HomeScene.tsx')) -const FourOhFour = lazily(() => import('./scenes/error/FourOhFour.tsx')) -const ServerConnectionErrorScene = lazily( +const HomeScene = lazy(() => import('./scenes/home/HomeScene.tsx')) +const FourOhFour = lazy(() => import('./scenes/error/FourOhFour.tsx')) +const ServerConnectionErrorScene = lazy( () => import('./scenes/error/ServerConnectionErrorScene.tsx'), ) -const LoginOrClaimScene = lazily(() => import('./scenes/auth/LoginOrClaimScene.tsx')) - -const IS_DEVELOPMENT = import.meta.env.MODE === 'development' +const LoginOrClaimScene = lazy(() => import('./scenes/auth/LoginOrClaimScene.tsx')) export function AppRouter() { - const appProps = useAppProps() + const locale = useUserStore((store) => store.userPreferences?.locale) + + const { baseUrl, platform } = useAppStore((store) => ({ + baseUrl: store.baseUrl, + platform: store.platform, + })) - if (!appProps?.baseUrl) { - if (appProps?.platform === 'browser') { + if (!baseUrl) { + if (platform === 'browser') { throw new Error('Base URL is not set') } @@ -42,14 +37,14 @@ export function AppRouter() { } return ( - + }> } /> } /> } /> } /> - {IS_DEVELOPMENT && } />} + } /> } /> } /> diff --git a/interface/src/components/BackgroundFetchIndicator.tsx b/packages/browser/src/components/BackgroundFetchIndicator.tsx similarity index 100% rename from interface/src/components/BackgroundFetchIndicator.tsx rename to packages/browser/src/components/BackgroundFetchIndicator.tsx diff --git a/interface/src/components/DirectoryPickerModal.tsx b/packages/browser/src/components/DirectoryPickerModal.tsx similarity index 100% rename from interface/src/components/DirectoryPickerModal.tsx rename to packages/browser/src/components/DirectoryPickerModal.tsx diff --git a/interface/src/components/ErrorFallback.tsx b/packages/browser/src/components/ErrorFallback.tsx similarity index 91% rename from interface/src/components/ErrorFallback.tsx rename to packages/browser/src/components/ErrorFallback.tsx index 0ecbc33d6..49fa913ff 100644 --- a/interface/src/components/ErrorFallback.tsx +++ b/packages/browser/src/components/ErrorFallback.tsx @@ -22,6 +22,11 @@ export function ErrorFallback({ error, resetErrorBoundary }: FallbackProps) { data-tauri-drag-region className="flex h-full w-full flex-col items-center justify-center overflow-hidden" > + Construction illustration

A critical error occurred

diff --git a/interface/src/components/FileStatusBadge.tsx b/packages/browser/src/components/FileStatusBadge.tsx similarity index 100% rename from interface/src/components/FileStatusBadge.tsx rename to packages/browser/src/components/FileStatusBadge.tsx diff --git a/interface/src/components/GenericEmptyState.tsx b/packages/browser/src/components/GenericEmptyState.tsx similarity index 100% rename from interface/src/components/GenericEmptyState.tsx rename to packages/browser/src/components/GenericEmptyState.tsx diff --git a/interface/src/components/HorizontalCardList.tsx b/packages/browser/src/components/HorizontalCardList.tsx similarity index 100% rename from interface/src/components/HorizontalCardList.tsx rename to packages/browser/src/components/HorizontalCardList.tsx diff --git a/interface/src/components/LazyImage.tsx b/packages/browser/src/components/LazyImage.tsx similarity index 100% rename from interface/src/components/LazyImage.tsx rename to packages/browser/src/components/LazyImage.tsx diff --git a/interface/src/components/LinkBadge.tsx b/packages/browser/src/components/LinkBadge.tsx similarity index 100% rename from interface/src/components/LinkBadge.tsx rename to packages/browser/src/components/LinkBadge.tsx diff --git a/interface/src/components/ListItem.tsx b/packages/browser/src/components/ListItem.tsx similarity index 100% rename from interface/src/components/ListItem.tsx rename to packages/browser/src/components/ListItem.tsx diff --git a/interface/src/components/Notifications.tsx b/packages/browser/src/components/Notifications.tsx similarity index 100% rename from interface/src/components/Notifications.tsx rename to packages/browser/src/components/Notifications.tsx diff --git a/interface/src/components/PagePopoverForm.tsx b/packages/browser/src/components/PagePopoverForm.tsx similarity index 100% rename from interface/src/components/PagePopoverForm.tsx rename to packages/browser/src/components/PagePopoverForm.tsx diff --git a/interface/src/components/Pagination.tsx b/packages/browser/src/components/Pagination.tsx similarity index 80% rename from interface/src/components/Pagination.tsx rename to packages/browser/src/components/Pagination.tsx index 9147caace..7f1da5660 100644 --- a/interface/src/components/Pagination.tsx +++ b/packages/browser/src/components/Pagination.tsx @@ -1,18 +1,19 @@ import { cn, cx } from '@stump/components' import { ArrowLeft, ArrowRight, MoreHorizontal } from 'lucide-react' -import { useMemo } from 'react' +import { useCallback, useMemo } from 'react' import { useWindowSize } from 'rooks' import { usePagination } from '../hooks/usePagination' import PagePopoverForm from './PagePopoverForm' -interface PaginationArrowProps { +type PaginationArrowProps = { kind: 'previous' | 'next' isDisabled?: boolean onClick: () => void + onMouseEnter?: () => void } -function PaginationArrow({ kind, isDisabled, onClick }: PaginationArrowProps) { +function PaginationArrow({ kind, isDisabled, onClick, onMouseEnter }: PaginationArrowProps) { const ArrowIcon = kind === 'previous' ? ArrowLeft : ArrowRight // NOTE: notice I am wrapping the link (which will have pointer-events-none when @@ -23,6 +24,7 @@ function PaginationArrow({ kind, isDisabled, onClick }: PaginationArrowProps) { className={cx('items-center', kind === 'next' ? 'justify-end text-right' : 'justify-start', { 'cursor-not-allowed': isDisabled, })} + onMouseEnter={!isDisabled ? onMouseEnter : undefined} >