├── .bashrc ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── release.yml ├── .gitignore ├── .gitlab-ci-local └── .gitignore ├── .gitlab-ci.yml ├── .gitmodules ├── .nvmrc ├── .vscode └── extensions.json ├── DEBUG.md ├── LICENSE ├── README.md ├── app.vue ├── assets ├── main.scss └── wallpaper.jpg ├── changelog.md ├── components ├── GameOptions │ └── Launch.vue ├── GameOptionsModal.vue ├── GameStatusButton.vue ├── Header.vue ├── HeaderButton.vue ├── HeaderQueueWidget.vue ├── HeaderUserWidget.vue ├── HeaderWidget.vue ├── InitiateAuthModule.vue ├── LibrarySearch.vue ├── Logo.vue ├── MiniHeader.vue ├── OfflineHeaderWidget.vue ├── PageWidget.vue ├── WindowControl.vue └── Wordmark.vue ├── composables ├── app-state.ts ├── current-page-engine.ts ├── downloads.ts ├── game.ts ├── generateGameMeta.ts ├── state-navigation.ts └── use-object.ts ├── drop.svg ├── error.vue ├── layouts ├── default.vue └── mini.vue ├── nuxt.config.ts ├── nvidia-prop-dev.sh ├── package.json ├── pages ├── auth │ ├── failed.vue │ ├── index.vue │ ├── processing.vue │ └── signedout.vue ├── error │ └── serverunavailable.vue ├── index.vue ├── library.vue ├── library │ ├── [id] │ │ └── index.vue │ └── index.vue ├── queue.vue ├── quit.vue ├── settings.vue ├── settings │ ├── account.vue │ ├── debug.vue │ ├── downloads.vue │ ├── index.vue │ └── interface.vue ├── setup │ ├── index.vue │ └── server.vue └── store │ └── index.vue ├── plugins ├── global-error-handler.ts └── vuedraggable.ts ├── public └── fonts │ ├── helvetica │ ├── Helvetica-Bold.woff │ ├── Helvetica-BoldOblique.woff │ ├── Helvetica-Oblique.woff │ ├── Helvetica.woff │ ├── helvetica-compressed-5871d14b6903a.woff │ ├── helvetica-light-587ebe5a59211.woff │ ├── helvetica-light-587ebe5a59211.woff2 │ └── helvetica-rounded-bold-5871d05ead8de.woff │ ├── inter │ ├── InterVariable-Italic.ttf │ └── InterVariable.ttf │ └── motiva │ ├── MotivaSansBlack.woff.ttf │ ├── MotivaSansBold.woff.ttf │ ├── MotivaSansExtraBold.ttf │ ├── MotivaSansLight.woff.ttf │ ├── MotivaSansMedium.woff.ttf │ ├── MotivaSansRegular.woff.ttf │ └── MotivaSansThin.ttf ├── src-tauri ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── capabilities │ └── default.json ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── android │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ │ └── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── ic_launcher_round.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── ios │ │ ├── AppIcon-20x20@1x.png │ │ ├── AppIcon-20x20@2x-1.png │ │ ├── AppIcon-20x20@2x.png │ │ ├── AppIcon-20x20@3x.png │ │ ├── AppIcon-29x29@1x.png │ │ ├── AppIcon-29x29@2x-1.png │ │ ├── AppIcon-29x29@2x.png │ │ ├── AppIcon-29x29@3x.png │ │ ├── AppIcon-40x40@1x.png │ │ ├── AppIcon-40x40@2x-1.png │ │ ├── AppIcon-40x40@2x.png │ │ ├── AppIcon-40x40@3x.png │ │ ├── AppIcon-512@2x.png │ │ ├── AppIcon-60x60@2x.png │ │ ├── AppIcon-60x60@3x.png │ │ ├── AppIcon-76x76@1x.png │ │ ├── AppIcon-76x76@2x.png │ │ └── AppIcon-83.5x83.5@2x.png ├── rust-toolchain.toml ├── src │ ├── client │ │ ├── autostart.rs │ │ ├── cleanup.rs │ │ ├── commands.rs │ │ └── mod.rs │ ├── cloud_saves │ │ ├── backup_manager.rs │ │ ├── conditions.rs │ │ ├── metadata.rs │ │ ├── mod.rs │ │ ├── normalise.rs │ │ ├── path.rs │ │ ├── placeholder.rs │ │ ├── resolver.rs │ │ └── strict_path.rs │ ├── database │ │ ├── commands.rs │ │ ├── db.rs │ │ ├── debug.rs │ │ ├── mod.rs │ │ └── models.rs │ ├── download_manager │ │ ├── commands.rs │ │ ├── download_manager.rs │ │ ├── download_manager_builder.rs │ │ ├── downloadable.rs │ │ ├── mod.rs │ │ └── util │ │ │ ├── download_thread_control_flag.rs │ │ │ ├── mod.rs │ │ │ ├── progress_object.rs │ │ │ ├── queue.rs │ │ │ └── rolling_progress_updates.rs │ ├── error │ │ ├── application_download_error.rs │ │ ├── backup_error.rs │ │ ├── download_manager_error.rs │ │ ├── drop_server_error.rs │ │ ├── library_error.rs │ │ ├── mod.rs │ │ ├── process_error.rs │ │ ├── remote_access_error.rs │ │ └── setup_error.rs │ ├── games │ │ ├── collections │ │ │ ├── collection.rs │ │ │ ├── commands.rs │ │ │ └── mod.rs │ │ ├── commands.rs │ │ ├── downloads │ │ │ ├── commands.rs │ │ │ ├── download_agent.rs │ │ │ ├── download_logic.rs │ │ │ ├── manifest.rs │ │ │ ├── mod.rs │ │ │ └── stored_manifest.rs │ │ ├── library.rs │ │ ├── mod.rs │ │ └── state.rs │ ├── lib.rs │ ├── main.rs │ ├── process │ │ ├── commands.rs │ │ ├── compat.rs │ │ ├── mod.rs │ │ └── process_manager.rs │ └── remote │ │ ├── auth.rs │ │ ├── cache.rs │ │ ├── commands.rs │ │ ├── fetch_object.rs │ │ ├── mod.rs │ │ ├── remote.rs │ │ ├── requests.rs │ │ └── server_proto.rs ├── tailscale │ ├── .gitignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── build.rs │ └── src │ │ ├── bindings.rs │ │ ├── lib.rs │ │ └── test.rs └── tauri.conf.json ├── tailwind.config.js ├── tsconfig.json ├── types.ts └── yarn.lock /.bashrc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drop-OSS/drop-app/065eb2356affe9fe709932e5b143ca9f705b041c/.bashrc -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Arch Linux, Windows] 28 | - App Version [e.g. 22] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: 'publish' 2 | 3 | on: 4 | workflow_dispatch: {} 5 | release: 6 | types: [published] 7 | # This can be used to automatically publish nightlies at UTC nighttime 8 | # schedule: 9 | # - cron: "0 2 * * *" # run at 2 AM UTC 10 | 11 | # This workflow will trigger on each push to the `release` branch to create or update a GitHub release, build your app, and upload the artifacts to the release. 12 | 13 | jobs: 14 | publish-tauri: 15 | permissions: 16 | contents: write 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | include: 21 | - platform: 'macos-latest' # for Arm based macs (M1 and above). 22 | args: '--target aarch64-apple-darwin' 23 | - platform: 'macos-latest' # for Intel based macs. 24 | args: '--target x86_64-apple-darwin' 25 | - platform: 'ubuntu-24.04' # for Tauri v1 you could replace this with ubuntu-20.04. 26 | args: '' 27 | - platform: 'ubuntu-24.04-arm' 28 | args: '--target aarch64-unknown-linux-gnu' 29 | - platform: 'windows-latest' 30 | args: '' 31 | 32 | runs-on: ${{ matrix.platform }} 33 | steps: 34 | - uses: actions/checkout@v4 35 | with: 36 | submodules: true 37 | token: ${{ secrets.GITHUB_TOKEN }} 38 | 39 | - name: setup node 40 | uses: actions/setup-node@v4 41 | with: 42 | node-version: lts/* 43 | 44 | - name: install Rust nightly 45 | uses: dtolnay/rust-toolchain@nightly 46 | with: 47 | # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. 48 | targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} 49 | 50 | - name: install dependencies (ubuntu only) 51 | if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. 52 | run: | 53 | sudo apt-get update 54 | sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libgtk2.0-dev libsoup3.0-dev 55 | # webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2. 56 | # You can remove the one that doesn't apply to your app to speed up the workflow a bit. 57 | 58 | - name: install frontend dependencies 59 | run: yarn install # change this to npm, pnpm or bun depending on which one you use. 60 | 61 | - uses: tauri-apps/tauri-action@v0 62 | env: 63 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 64 | with: 65 | tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version. 66 | releaseName: 'Auto-release v__VERSION__' 67 | releaseBody: 'See the assets to download this version and install. This release was created automatically.' 68 | releaseDraft: false 69 | prerelease: true 70 | args: ${{ matrix.args }} 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .nuxt 26 | .output 27 | 28 | src-tauri/flamegraph.svg 29 | src-tauri/perf* -------------------------------------------------------------------------------- /.gitlab-ci-local/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | 4 | build-linux: 5 | stage: build 6 | image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/rustlang/rust:nightly 7 | script: 8 | - apt-get update -y 9 | - apt-get install yarnpkg libsoup-3.0-0 libsoup-3.0-dev libatk-adaptor libgtk-3-dev libjavascriptcoregtk-4.1-dev libwebkit2gtk-4.1-dev -y 10 | - yarnpkg 11 | - export 12 | - export RUST_LOG=warn 13 | - yarnpkg tauri build 14 | - cp src-tauri/target/release/bundle/deb/*.deb . 15 | - cp src-tauri/target/release/bundle/rpm/*.rpm . 16 | - cp src-tauri/target/release/bundle/appimage/*.AppImage . 17 | artifacts: 18 | paths: 19 | - "*.{deb,rpm,AppImage}" 20 | 21 | build-windows: 22 | stage: build 23 | tags: 24 | - windows 25 | script: 26 | - yarn 27 | - yarn tauri build 28 | - cp src-tauri/target/release/bundle/nsis/*.exe . 29 | artifacts: 30 | paths: 31 | - "*.exe" 32 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "drop-base"] 2 | path = drop-base 3 | url = https://github.com/drop-oss/drop-base 4 | [submodule "src-tauri/tailscale/libtailscale"] 5 | path = src-tauri/tailscale/libtailscale 6 | url = https://github.com/tailscale/libtailscale.git 7 | [submodule "src-tauri/umu/umu-launcher"] 8 | path = src-tauri/umu/umu-launcher 9 | url = https://github.com/Open-Wine-Components/umu-launcher.git 10 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 23 2 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "tauri-apps.tauri-vscode", 5 | "rust-lang.rust-analyzer" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /DEBUG.md: -------------------------------------------------------------------------------- 1 | # How to create Flamegraph 2 | 3 | Run this in `src-tauri`: 4 | ``` 5 | WEBKIT_DISABLE_DMABUF_RENDERER=1 CARGO_PROFILE_RELEASE_DEBUG=true cargo flamegraph --release 6 | ``` 7 | 8 | You can leave out `WEBKIT_DISABLE_DMABUF_RENDERER=1` if you're not on NVIDIA/Linux 9 | 10 | And then run this in the root dir: 11 | ``` 12 | yarn dev --port 1432 13 | ``` 14 | 15 | And then do what you want, and it'll create the flamegraph for you 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Drop App 2 | 3 | Drop app is the companion app for [Drop](https://github.com/Drop-OSS/drop). It uses a Tauri base with Nuxt 3 + TailwindCSS on top of it, so we can re-use components from the web UI. 4 | 5 | ## Running 6 | Before setting up the drop app, be sure that you have a server set up. 7 | The instructions for this can be found on the [Drop Wiki](https://wiki.droposs.org/guides/quickstart.html) 8 | 9 | ## Current features 10 | Currently supported are the following features: 11 | - Signin (with custom server) 12 | - Database registering & recovery 13 | - Dynamic library fetching from server 14 | - Installing & uninstalling games 15 | - Download progress monitoring 16 | - Launching / playing games 17 | 18 | ## Development 19 | 20 | Install dependencies with `yarn` 21 | 22 | Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh` 23 | 24 | To manually specify the logging level, add the environment variable `RUST_LOG=[debug, info, warn, error]` to `yarn tauri dev`: 25 | 26 | e.g. `RUST_LOG=debug yarn tauri dev` 27 | 28 | ## Contributing 29 | Check the original [Drop repo](https://github.com/Drop-OSS/drop/blob/main/CONTRIBUTING.md) for contributing guidelines. -------------------------------------------------------------------------------- /app.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 44 | -------------------------------------------------------------------------------- /assets/main.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body { 7 | -ms-overflow-style: none; /* IE and Edge / 8 | scrollbar-width: none; / Firefox */ 9 | } 10 | 11 | /* Hide scrollbar for Chrome, Safari and Opera */ 12 | html::-webkit-scrollbar { 13 | display: none; 14 | } 15 | 16 | $motiva: ( 17 | ("MotivaSansThin.ttf", "ttf", 100, normal), 18 | ("MotivaSansLight.woff.ttf", "woff", 300, normal), 19 | ("MotivaSansRegular.woff.ttf", "woff", 400, normal), 20 | ("MotivaSansMedium.woff.ttf", "woff", 500, normal), 21 | ("MotivaSansBold.woff.ttf", "woff", 600, normal), 22 | ("MotivaSansExtraBold.ttf", "woff", 700, normal), 23 | ("MotivaSansBlack.woff.ttf", "woff", 900, normal) 24 | ); 25 | 26 | $helvetica: ( 27 | ("Helvetica.woff", "woff", 400, normal), 28 | ("Helvetica-Oblique.woff", "woff", 400, italic), 29 | ("Helvetica-Bold.woff", "woff", 600, normal), 30 | ("Helvetica-BoldOblique.woff", "woff", 600, italic), 31 | ("helvetica-light-587ebe5a59211.woff2", "woff2", 300, normal) 32 | ); 33 | 34 | @each $file, $format, $weight, $style in $motiva { 35 | @font-face { 36 | font-family: "Motiva Sans"; 37 | src: url("/fonts/motiva/#{$file}") format($format); 38 | font-weight: $weight; 39 | font-style: $style; 40 | } 41 | } 42 | 43 | @each $file, $format, $weight, $style in $helvetica { 44 | @font-face { 45 | font-family: "Helvetica"; 46 | src: url("/fonts/helvetica/#{$file}") format($format); 47 | font-weight: $weight; 48 | font-style: $style; 49 | } 50 | } 51 | 52 | @font-face { 53 | font-family: "Inter"; 54 | src: url("/fonts/inter/InterVariable.ttf"); 55 | font-style: normal; 56 | } 57 | 58 | @font-face { 59 | font-family: "Inter"; 60 | src: url("/fonts/inter/InterVariable-Italic.ttf"); 61 | font-style: italic; 62 | } 63 | 64 | /* ===== Scrollbar CSS ===== */ 65 | /* Firefox */ 66 | * { 67 | scrollbar-width: 4px; 68 | scrollbar-color: #52525b #00000000; 69 | } 70 | 71 | /* Chrome, Edge, and Safari */ 72 | *::-webkit-scrollbar { 73 | width: 4px; 74 | } 75 | 76 | *::-webkit-scrollbar-track { 77 | background: transparent; 78 | } 79 | 80 | *::-webkit-scrollbar-thumb { 81 | background-color: #52525b; 82 | border-radius: 10px; 83 | border: 3px solid #52525b; 84 | } 85 | -------------------------------------------------------------------------------- /assets/wallpaper.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Drop-OSS/drop-app/065eb2356affe9fe709932e5b143ca9f705b041c/assets/wallpaper.jpg -------------------------------------------------------------------------------- /components/GameOptions/Launch.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 32 | -------------------------------------------------------------------------------- /components/GameOptionsModal.vue: -------------------------------------------------------------------------------- 1 | 72 | 73 | 123 | -------------------------------------------------------------------------------- /components/GameStatusButton.vue: -------------------------------------------------------------------------------- 1 | 83 | 84 | 166 | -------------------------------------------------------------------------------- /components/Header.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 97 | -------------------------------------------------------------------------------- /components/HeaderButton.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/HeaderQueueWidget.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 27 | -------------------------------------------------------------------------------- /components/HeaderUserWidget.vue: -------------------------------------------------------------------------------- 1 | 73 | 74 | 114 | -------------------------------------------------------------------------------- /components/HeaderWidget.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /components/InitiateAuthModule.vue: -------------------------------------------------------------------------------- 1 | 121 | 122 | 166 | -------------------------------------------------------------------------------- /components/LibrarySearch.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 160 | 161 | 178 | -------------------------------------------------------------------------------- /components/Logo.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/MiniHeader.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | -------------------------------------------------------------------------------- /components/OfflineHeaderWidget.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /components/PageWidget.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /components/WindowControl.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 25 | -------------------------------------------------------------------------------- /components/Wordmark.vue: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /composables/app-state.ts: -------------------------------------------------------------------------------- 1 | import type { AppState } from "~/types"; 2 | 3 | export const useAppState = () => useState("state"); -------------------------------------------------------------------------------- /composables/current-page-engine.ts: -------------------------------------------------------------------------------- 1 | import type { RouteLocationNormalized } from "vue-router"; 2 | import type { NavigationItem } from "~/types"; 3 | 4 | export const useCurrentNavigationIndex = ( 5 | navigation: Array 6 | ) => { 7 | const router = useRouter(); 8 | const route = useRoute(); 9 | 10 | const currentNavigation = ref(-1); 11 | 12 | function calculateCurrentNavIndex(to: RouteLocationNormalized) { 13 | const validOptions = navigation 14 | .map((e, i) => ({ ...e, index: i })) 15 | .filter((e) => to.fullPath.startsWith(e.prefix)); 16 | const bestOption = validOptions 17 | .sort((a, b) => b.route.length - a.route.length) 18 | .at(0); 19 | 20 | return bestOption?.index ?? -1; 21 | } 22 | 23 | currentNavigation.value = calculateCurrentNavIndex(route); 24 | 25 | router.afterEach((to) => { 26 | currentNavigation.value = calculateCurrentNavIndex(to); 27 | }); 28 | 29 | return {currentNavigation, recalculateNavigation: () => { 30 | currentNavigation.value = calculateCurrentNavIndex(route); 31 | }}; 32 | }; 33 | -------------------------------------------------------------------------------- /composables/downloads.ts: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import type { DownloadableMetadata } from "~/types"; 3 | 4 | export type QueueState = { 5 | queue: Array<{ 6 | meta: DownloadableMetadata; 7 | status: string; 8 | progress: number | null; 9 | current: number; 10 | max: number; 11 | }>; 12 | status: string; 13 | }; 14 | 15 | export type StatsState = { 16 | speed: number; // Bytes per second 17 | time: number; // Seconds, 18 | }; 19 | 20 | export const useQueueState = () => 21 | useState("queue", () => ({ queue: [], status: "Unknown" })); 22 | 23 | export const useStatsState = () => 24 | useState("stats", () => ({ speed: 0, time: 0 })); 25 | 26 | listen("update_queue", (event) => { 27 | const queue = useQueueState(); 28 | queue.value = event.payload as QueueState; 29 | }); 30 | 31 | listen("update_stats", (event) => { 32 | const stats = useStatsState(); 33 | stats.value = event.payload as StatsState; 34 | }); 35 | -------------------------------------------------------------------------------- /composables/game.ts: -------------------------------------------------------------------------------- 1 | import { invoke } from "@tauri-apps/api/core"; 2 | import { listen } from "@tauri-apps/api/event"; 3 | import type { Game, GameStatus, GameStatusEnum, GameVersion } from "~/types"; 4 | 5 | const gameRegistry: { [key: string]: { game: Game; version?: GameVersion } } = 6 | {}; 7 | 8 | const gameStatusRegistry: { [key: string]: Ref } = {}; 9 | 10 | type OptionGameStatus = { [key in GameStatusEnum]: { version_name?: string } }; 11 | export type SerializedGameStatus = [ 12 | { type: GameStatusEnum }, 13 | OptionGameStatus | null 14 | ]; 15 | 16 | export const parseStatus = (status: SerializedGameStatus): GameStatus => { 17 | console.log(status); 18 | if (status[0]) { 19 | return { 20 | type: status[0].type, 21 | }; 22 | } else if (status[1]) { 23 | const [[gameStatus, options]] = Object.entries(status[1]); 24 | return { 25 | type: gameStatus as GameStatusEnum, 26 | ...options, 27 | }; 28 | } else { 29 | throw new Error("No game status"); 30 | } 31 | }; 32 | 33 | export const useGame = async (gameId: string) => { 34 | if (!gameRegistry[gameId]) { 35 | const data: { 36 | game: Game; 37 | status: SerializedGameStatus; 38 | version?: GameVersion; 39 | } = await invoke("fetch_game", { 40 | gameId, 41 | }); 42 | gameRegistry[gameId] = { game: data.game, version: data.version }; 43 | if (!gameStatusRegistry[gameId]) { 44 | gameStatusRegistry[gameId] = ref(parseStatus(data.status)); 45 | 46 | listen(`update_game/${gameId}`, (event) => { 47 | const payload: { 48 | status: SerializedGameStatus; 49 | version?: GameVersion; 50 | } = event.payload as any; 51 | console.log(payload.status); 52 | gameStatusRegistry[gameId].value = parseStatus(payload.status); 53 | 54 | /** 55 | * I am not super happy about this. 56 | * 57 | * This will mean that we will still have a version assigned if we have a game installed then uninstall it. 58 | * It is necessary because a flag to check if we should overwrite seems excessive, and this function gets called 59 | * on transient state updates. 60 | */ 61 | if (payload.version) { 62 | gameRegistry[gameId].version = payload.version; 63 | } 64 | }); 65 | } 66 | } 67 | 68 | const game = gameRegistry[gameId]; 69 | const status = gameStatusRegistry[gameId]; 70 | return { ...game, status }; 71 | }; 72 | 73 | export type FrontendGameConfiguration = { 74 | launchString: string; 75 | }; 76 | -------------------------------------------------------------------------------- /composables/generateGameMeta.ts: -------------------------------------------------------------------------------- 1 | import { type DownloadableMetadata, DownloadableType } from '~/types' 2 | 3 | export default function generateGameMeta(gameId: string, version: string): DownloadableMetadata { 4 | return { 5 | id: gameId, 6 | version, 7 | downloadType: DownloadableType.Game 8 | } 9 | } -------------------------------------------------------------------------------- /composables/state-navigation.ts: -------------------------------------------------------------------------------- 1 | import { listen } from "@tauri-apps/api/event"; 2 | import { data } from "autoprefixer"; 3 | import { AppStatus, type AppState } from "~/types"; 4 | 5 | export function setupHooks() { 6 | const router = useRouter(); 7 | 8 | listen("auth/processing", (event) => { 9 | router.push("/auth/processing"); 10 | }); 11 | 12 | listen("auth/failed", (event) => { 13 | router.push( 14 | `/auth/failed?error=${encodeURIComponent(event.payload as string)}` 15 | ); 16 | }); 17 | 18 | listen("auth/finished", (event) => { 19 | router.push("/store"); 20 | }); 21 | 22 | listen("download_error", (event) => { 23 | createModal( 24 | ModalType.Notification, 25 | { 26 | title: "Drop encountered an error while downloading", 27 | description: `Drop encountered an error while downloading your game: "${( 28 | event.payload as unknown as string 29 | ).toString()}"`, 30 | buttonText: "Close" 31 | }, 32 | (e, c) => c() 33 | ); 34 | }); 35 | 36 | /* 37 | 38 | document.addEventListener("contextmenu", (event) => { 39 | event.target?.dispatchEvent(new Event("contextmenu")); 40 | event.preventDefault(); 41 | }); 42 | 43 | */ 44 | } 45 | 46 | export function initialNavigation(state: Ref) { 47 | const router = useRouter(); 48 | 49 | switch (state.value.status) { 50 | case AppStatus.NotConfigured: 51 | router.push({ path: "/setup" }); 52 | break; 53 | case AppStatus.SignedOut: 54 | router.push("/auth"); 55 | break; 56 | case AppStatus.SignedInNeedsReauth: 57 | router.push("/auth/signedout"); 58 | break; 59 | case AppStatus.ServerUnavailable: 60 | router.push("/error/serverunavailable"); 61 | break; 62 | default: 63 | router.push("/store"); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /composables/use-object.ts: -------------------------------------------------------------------------------- 1 | import { convertFileSrc } from "@tauri-apps/api/core"; 2 | 3 | export const useObject = async (id: string) => { 4 | return convertFileSrc(id, "object"); 5 | }; 6 | -------------------------------------------------------------------------------- /drop.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /error.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 91 | -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 79 | 80 | 83 | -------------------------------------------------------------------------------- /layouts/mini.vue: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // https://nuxt.com/docs/api/configuration/nuxt-config 2 | export default defineNuxtConfig({ 3 | compatibilityDate: "2024-04-03", 4 | 5 | postcss: { 6 | plugins: { 7 | tailwindcss: {}, 8 | autoprefixer: {}, 9 | }, 10 | }, 11 | 12 | css: ["~/assets/main.scss"], 13 | 14 | ssr: false, 15 | 16 | extends: [["./drop-base"]], 17 | }); 18 | -------------------------------------------------------------------------------- /nvidia-prop-dev.sh: -------------------------------------------------------------------------------- 1 | WEBKIT_DISABLE_DMABUF_RENDERER=1 yarn tauri dev -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "drop-app", 3 | "private": true, 4 | "version": "0.3.0-rc-2", 5 | "type": "module", 6 | "scripts": { 7 | "build": "nuxt build", 8 | "dev": "nuxt dev", 9 | "generate": "nuxt generate", 10 | "preview": "nuxt preview", 11 | "postinstall": "nuxt prepare", 12 | "tauri": "tauri" 13 | }, 14 | "dependencies": { 15 | "@headlessui/vue": "^1.7.23", 16 | "@heroicons/vue": "^2.1.5", 17 | "@nuxtjs/tailwindcss": "^6.12.2", 18 | "@tauri-apps/api": ">=2.0.0", 19 | "@tauri-apps/plugin-deep-link": "~2", 20 | "@tauri-apps/plugin-dialog": "^2.0.1", 21 | "@tauri-apps/plugin-os": "~2", 22 | "@tauri-apps/plugin-shell": "^2.2.1", 23 | "koa": "^2.16.1", 24 | "markdown-it": "^14.1.0", 25 | "micromark": "^4.0.1", 26 | "nuxt": "^3.16.0", 27 | "scss": "^0.2.4", 28 | "vue": "latest", 29 | "vue-router": "latest", 30 | "vuedraggable": "^4.1.0" 31 | }, 32 | "devDependencies": { 33 | "@tailwindcss/forms": "^0.5.9", 34 | "@tailwindcss/typography": "^0.5.15", 35 | "@tauri-apps/cli": ">=2.0.0", 36 | "@types/markdown-it": "^14.1.2", 37 | "autoprefixer": "^10.4.20", 38 | "postcss": "^8.4.47", 39 | "sass-embedded": "^1.79.4", 40 | "tailwindcss": "^3.4.13", 41 | "typescript": "^5.8.3", 42 | "vue-tsc": "^2.2.10" 43 | }, 44 | "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e" 45 | } 46 | -------------------------------------------------------------------------------- /pages/auth/failed.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 35 | -------------------------------------------------------------------------------- /pages/auth/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /pages/auth/processing.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 42 | -------------------------------------------------------------------------------- /pages/auth/signedout.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /pages/error/serverunavailable.vue: -------------------------------------------------------------------------------- 1 | 75 | 76 | 89 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 |