Compare commits

..

2 Commits

Author SHA1 Message Date
f1bc7bf418 fix: update all versions to -mac specific 2025-08-02 12:02:34 +10:00
cb2295dd7c feat: add macos signing args 2025-08-02 12:00:25 +10:00
162 changed files with 9697 additions and 26582 deletions

View File

@ -51,40 +51,9 @@ jobs:
if: matrix.platform == 'ubuntu-22.04' || matrix.platform == 'ubuntu-22.04-arm' # This must match the platform value defined above.
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
# webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2.
- name: Import Apple Developer Certificate
if: matrix.platform == 'macos-latest'
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
run: |
echo $APPLE_CERTIFICATE | base64 --decode > certificate.p12
security create-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security default-keychain -s build.keychain
security unlock-keychain -p "$KEYCHAIN_PASSWORD" build.keychain
security set-keychain-settings -t 3600 -u build.keychain
curl https://droposs.org/drop.crt --output drop.pem
sudo security authorizationdb write com.apple.trust-settings.user allow
security add-trusted-cert -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
sudo security authorizationdb remove com.apple.trust-settings.user
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" build.keychain
security find-identity -v -p codesigning build.keychain
- name: Verify Certificate
if: matrix.platform == 'macos-latest'
run: |
CERT_INFO=$(security find-identity -v -p codesigning build.keychain | grep "Drop OSS")
CERT_ID=$(echo "$CERT_INFO" | awk -F'"' '{print $2}')
echo "CERT_ID=$CERT_ID" >> $GITHUB_ENV
echo "Certificate imported. Using identity: $CERT_ID"
- name: install frontend dependencies
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
@ -93,12 +62,11 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ env.CERT_ID }}
NO_STRIP: true
APPLE_SIGNING_IDENTITY: ${{ env.APPLE_SIGNING_IDENTITY }}
with:
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
releaseName: 'Auto-release v__VERSION__'
releaseBody: 'See the assets to download this version and install. This release was created automatically.'
releaseDraft: false
prerelease: true
args: ${{ matrix.args }}
args: ${{ matrix.args }}

5
.gitignore vendored
View File

@ -26,7 +26,4 @@ dist-ssr
.output
src-tauri/flamegraph.svg
src-tauri/perf*
/*.AppImage
/squashfs-root
src-tauri/perf*

9
.gitmodules vendored
View File

@ -1,6 +1,9 @@
[submodule "drop-base"]
path = drop-base
url = https://github.com/drop-oss/drop-base
[submodule "src-tauri/tailscale/libtailscale"]
path = src-tauri/tailscale/libtailscale
url = https://github.com/tailscale/libtailscale.git
[submodule "libs/drop-base"]
path = libs/drop-base
url = https://github.com/drop-oss/drop-base.git
[submodule "src-tauri/umu/umu-launcher"]
path = src-tauri/umu/umu-launcher
url = https://github.com/Open-Wine-Components/umu-launcher.git

View File

@ -1,21 +1,29 @@
# Drop Desktop Client
# Drop App
The Drop Desktop Client is the companion app for [Drop](https://github.com/Drop-OSS/drop). It is the official & intended way to download and play games on your Drop server.
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.
## Internals
## Running
Before setting up the drop app, be sure that you have a server set up.
The instructions for this can be found on the [Drop Docs](https://docs.droposs.org/docs/guides/quickstart)
It uses a Tauri base with Nuxt 3 + TailwindCSS on top of it, so we can re-use components from the web UI.
## Current features
Currently supported are the following features:
- Signin (with custom server)
- Database registering & recovery
- Dynamic library fetching from server
- Installing & uninstalling games
- Download progress monitoring
- Launching / playing games
## Development
Before setting up a development environemnt, be sure that you have a server set up. The instructions for this can be found on the [Drop Docs](https://docs.droposs.org/docs/guides/quickstart).
Then, install dependencies with `yarn`. This'll install the custom builder's dependencies. Then, check everything works properly with `yarn tauri build`.
Install dependencies with `yarn`
Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh`
Run the app in development with `yarn tauri dev`. NVIDIA users on Linux, use shell script `./nvidia-prop-dev.sh`
To manually specify the logging level, add the environment variable `RUST_LOG=[debug, info, warn, error]` to `yarn tauri dev`:
e.g. `RUST_LOG=debug yarn tauri dev`
## Contributing
Check out the contributing guide on our Developer Docs: [Drop Developer Docs - Contributing](https://developer.droposs.org/contributing).
Check the original [Drop repo](https://github.com/Drop-OSS/drop/blob/main/CONTRIBUTING.md) for contributing guidelines.

View File

@ -1,5 +1,5 @@
<template>
<NuxtLoadingIndicator color="#2563eb" />
<LoadingIndicator />
<NuxtLayout class="select-none w-screen h-screen">
<NuxtPage />
<ModalStack />
@ -10,6 +10,8 @@
import "~/composables/downloads.js";
import { invoke } from "@tauri-apps/api/core";
import { AppStatus } from "~/types";
import { listen } from "@tauri-apps/api/event";
import { useAppState } from "./composables/app-state.js";
import {
initialNavigation,
@ -19,26 +21,18 @@ import {
const router = useRouter();
const state = useAppState();
try {
state.value = JSON.parse(await invoke("fetch_state"));
} catch (e) {
console.error("failed to parse state", e);
}
async function fetchState() {
router.beforeEach(async () => {
try {
state.value = JSON.parse(await invoke("fetch_state"));
if (!state.value)
throw createError({
statusCode: 500,
statusMessage: `App state is: ${state.value}`,
fatal: true,
});
} catch (e) {
console.error("failed to parse state", e);
throw e;
}
}
await fetchState();
// This is inefficient but apparently we do it lol
router.beforeEach(async () => {
await fetchState();
});
setupHooks();

View File

Before

Width:  |  Height:  |  Size: 6.5 MiB

After

Width:  |  Height:  |  Size: 6.5 MiB

View File

@ -1,55 +0,0 @@
import fs from "fs";
import process from "process";
import childProcess from "child_process";
import createLogger from "pino";
const OUTPUT = "./.output";
const logger = createLogger({ transport: { target: "pino-pretty" } });
async function spawn(exec, opts) {
const output = childProcess.spawn(exec, { ...opts, shell: true });
output.stdout.on("data", (data) => {
process.stdout.write(data);
});
output.stderr.on("data", (data) => {
process.stderr.write(data);
});
return await new Promise((resolve, reject) => {
output.on("error", (err) => reject(err));
output.on("exit", () => resolve());
});
}
const expectedLibs = ["drop-base/package.json"];
for (const lib of expectedLibs) {
const path = `./libs/${lib}`;
if (!fs.existsSync(path)) throw `Missing "${expectedLibs}". Run "git submodule update --init --recursive"`;
}
const views = fs.readdirSync(".").filter((view) => {
const expectedPath = `./${view}/package.json`;
return fs.existsSync(expectedPath);
});
fs.mkdirSync(OUTPUT, { recursive: true });
for (const view of views) {
const loggerChild = logger.child({});
process.chdir(`./${view}`);
loggerChild.info(`Install deps for "${view}"`);
await spawn("yarn");
loggerChild.info(`Building "${view}"`);
await spawn("yarn build", {
env: { ...process.env, NUXT_APP_BASE_URL: `/${view}/` },
});
process.chdir("..");
fs.cpSync(`./${view}/.output/public`, `${OUTPUT}/${view}`, {
recursive: true,
});
}

View File

@ -1,78 +1,52 @@
<template>
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
<div class="inline-flex divide-x divide-zinc-900">
<button
type="button"
@click="() => buttonActions[props.status.type]()"
:class="[
styles[props.status.type],
showDropdown ? 'rounded-l-md' : 'rounded-md',
'inline-flex uppercase font-display items-center gap-x-2 px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]"
>
<component
:is="buttonIcons[props.status.type]"
class="-mr-0.5 size-5"
aria-hidden="true"
/>
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
styles[props.status.type],
showDropdown ? 'rounded-l-md' : 'rounded-md',
'inline-flex uppercase font-display items-center gap-x-2 px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]">
<component :is="buttonIcons[props.status.type]" class="-mr-0.5 size-5" aria-hidden="true" />
{{ buttonNames[props.status.type] }}
</button>
<Menu
v-if="showDropdown"
as="div"
class="relative inline-block text-left grow"
>
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
<div class="h-full">
<MenuButton
:class="[
styles[props.status.type],
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]"
>
<MenuButton :class="[
styles[props.status.type],
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
]">
<ChevronDownIcon class="size-5" aria-hidden="true" />
</MenuButton>
</div>
<transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95"
>
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
<MenuItems
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none"
>
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none">
<div class="py-1">
<MenuItem v-if="showOptions" v-slot="{ active }">
<button
@click="() => emit('options')"
:class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]"
>
Options
<Cog6ToothIcon class="size-5" />
</button>
<MenuItem v-slot="{ active }">
<button @click="() => emit('options')" :class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]">
Options
<Cog6ToothIcon class="size-5" />
</button>
</MenuItem>
<MenuItem v-slot="{ active }">
<button
@click="() => emit('uninstall')"
:class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]"
>
Uninstall
<TrashIcon class="size-5" />
</button>
<button @click="() => emit('uninstall')" :class="[
active
? 'bg-zinc-800 text-zinc-100 outline-none'
: 'text-zinc-400',
'w-full block px-4 py-2 text-sm inline-flex justify-between',
]">
Uninstall
<TrashIcon class="size-5" />
</button>
</MenuItem>
</div>
</MenuItems>
@ -87,7 +61,6 @@ import {
ChevronDownIcon,
PlayIcon,
QueueListIcon,
ServerIcon,
StopIcon,
WrenchIcon,
} from "@heroicons/vue/20/solid";
@ -105,7 +78,7 @@ const emit = defineEmits<{
(e: "uninstall"): void;
(e: "kill"): void;
(e: "options"): void;
(e: "resume"): void;
(e: "resume"): void
}>();
const showDropdown = computed(
@ -115,10 +88,6 @@ const showDropdown = computed(
props.status.type === GameStatusEnum.PartiallyInstalled
);
const showOptions = computed(
() => props.status.type === GameStatusEnum.Installed
);
const styles: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]:
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
@ -126,8 +95,6 @@ const styles: { [key in GameStatusEnum]: string } = {
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.Downloading]:
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.Validating]:
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.SetupRequired]:
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
[GameStatusEnum.Installed]:
@ -139,45 +106,42 @@ const styles: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Running]:
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
[GameStatusEnum.PartiallyInstalled]:
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
"bg-gray-600 text-white hover:bg-gray-500 focus-visible:outline-gray-600 hover:bg-gray-500"
};
const buttonNames: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]: "Install",
[GameStatusEnum.Queued]: "Queued",
[GameStatusEnum.Downloading]: "Downloading",
[GameStatusEnum.Validating]: "Validating",
[GameStatusEnum.SetupRequired]: "Setup",
[GameStatusEnum.Installed]: "Play",
[GameStatusEnum.Updating]: "Updating",
[GameStatusEnum.Uninstalling]: "Uninstalling",
[GameStatusEnum.Running]: "Stop",
[GameStatusEnum.PartiallyInstalled]: "Resume",
[GameStatusEnum.PartiallyInstalled]: "Resume"
};
const buttonIcons: { [key in GameStatusEnum]: Component } = {
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
[GameStatusEnum.Queued]: QueueListIcon,
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
[GameStatusEnum.Validating]: ServerIcon,
[GameStatusEnum.SetupRequired]: WrenchIcon,
[GameStatusEnum.Installed]: PlayIcon,
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
[GameStatusEnum.Uninstalling]: TrashIcon,
[GameStatusEnum.Running]: StopIcon,
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon,
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon
};
const buttonActions: { [key in GameStatusEnum]: () => void } = {
[GameStatusEnum.Remote]: () => emit("install"),
[GameStatusEnum.Queued]: () => emit("queue"),
[GameStatusEnum.Downloading]: () => emit("queue"),
[GameStatusEnum.Validating]: () => emit("queue"),
[GameStatusEnum.SetupRequired]: () => emit("launch"),
[GameStatusEnum.Installed]: () => emit("launch"),
[GameStatusEnum.Updating]: () => emit("queue"),
[GameStatusEnum.Uninstalling]: () => {},
[GameStatusEnum.Uninstalling]: () => { },
[GameStatusEnum.Running]: () => emit("kill"),
[GameStatusEnum.PartiallyInstalled]: () => emit("resume"),
[GameStatusEnum.PartiallyInstalled]: () => emit("resume")
};
</script>

View File

@ -2,7 +2,9 @@
<div class="h-16 bg-zinc-950 flex flex-row justify-between">
<div class="flex flex-row grow items-center pl-5 pr-2 py-3">
<div class="inline-flex items-center gap-x-10">
<Wordmark class="h-8 mb-0.5" />
<NuxtLink to="/store">
<Wordmark class="h-8 mb-0.5" />
</NuxtLink>
<nav class="inline-flex items-center mt-0.5">
<ol class="inline-flex items-center gap-x-6">
<NuxtLink
@ -35,12 +37,12 @@
<component class="h-5" :is="item.icon" />
</HeaderWidget>
</li>
<OfflineHeaderWidget v-if="state?.status === AppStatus.Offline" />
<OfflineHeaderWidget v-if="state.status === AppStatus.Offline" />
<HeaderUserWidget />
</ol>
</div>
</div>
<WindowControl />
<WindowControl />
</div>
</template>

View File

@ -1,5 +1,5 @@
<template>
<Menu v-if="state?.user" as="div" class="relative inline-block">
<Menu v-if="state.user" as="div" class="relative inline-block">
<MenuButton>
<HeaderWidget>
<div class="inline-flex items-center text-zinc-300 hover:text-white">
@ -37,7 +37,7 @@
</NuxtLink>
<div class="h-0.5 rounded-full w-full bg-zinc-800" />
<div class="flex flex-col mb-1">
<MenuItem v-if="state.user.admin" v-slot="{ active }">
<MenuItem v-slot="{ active }">
<a
:href="adminUrl"
target="_blank"
@ -76,6 +76,7 @@ import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
import { ChevronDownIcon } from "@heroicons/vue/16/solid";
import type { NavigationItem } from "../types";
import HeaderWidget from "./HeaderWidget.vue";
import { useAppState } from "~/composables/app-state";
import { invoke } from "@tauri-apps/api/core";
const open = ref(false);
@ -86,7 +87,7 @@ router.afterEach(() => {
const state = useAppState();
const profilePictureUrl: string = await useObject(
state.value?.user?.profilePictureObjectId ?? ""
state.value.user?.profilePictureObjectId ?? ""
);
const adminUrl: string = await invoke("gen_drop_url", {
path: "/admin",

View File

@ -0,0 +1,194 @@
<template>
<div>
<div class="mb-3 inline-flex gap-x-2">
<div
class="relative transition-transform duration-300 hover:scale-105 active:scale-95"
>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<MagnifyingGlassIcon
class="h-5 w-5 text-zinc-400"
aria-hidden="true"
/>
</div>
<input
type="text"
v-model="searchQuery"
class="block w-full rounded-lg border-0 bg-zinc-800/50 py-2 pl-10 pr-3 text-zinc-100 placeholder:text-zinc-500 focus:bg-zinc-800 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
placeholder="Search library..."
/>
</div>
<button
@click="() => calculateGames(true)"
class="p-1 flex items-center justify-center transition-transform duration-300 size-10 hover:scale-110 active:scale-90 rounded-lg bg-zinc-800/50 text-zinc-100"
>
<ArrowPathIcon class="size-4" />
</button>
</div>
<TransitionGroup name="list" tag="ul" class="flex flex-col gap-y-1.5">
<NuxtLink
v-for="nav in filteredNavigation"
:key="nav.id"
:class="[
'transition-all duration-300 rounded-lg flex items-center py-2 px-3 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
nav.index === currentNavigation
? 'bg-zinc-800 text-zinc-100 shadow-md shadow-zinc-950/20'
: nav.isInstalled.value
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
: 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300',
]"
:href="nav.route"
>
<div class="flex items-center w-full gap-x-3">
<div
class="flex-none transition-transform duration-300 hover:-rotate-2"
>
<img
class="size-8 object-cover bg-zinc-900 rounded-lg transition-all duration-300 shadow-sm"
:src="icons[nav.id]"
alt=""
/>
</div>
<div class="flex flex-col flex-1">
<p
class="truncate text-xs font-display leading-5 flex-1 font-semibold"
>
{{ nav.label }}
</p>
<p
class="text-xs font-medium"
:class="[gameStatusTextStyle[games[nav.id].status.value.type]]"
>
{{ gameStatusText[games[nav.id].status.value.type] }}
</p>
</div>
</div>
</NuxtLink>
</TransitionGroup>
</div>
</template>
<script setup lang="ts">
import { ArrowPathIcon, MagnifyingGlassIcon } from "@heroicons/vue/20/solid";
import { invoke } from "@tauri-apps/api/core";
import { GameStatusEnum, type Game, type GameStatus } from "~/types";
import { TransitionGroup } from "vue";
import { listen } from "@tauri-apps/api/event";
// Style information
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Installed]: "text-green-500",
[GameStatusEnum.Downloading]: "text-blue-500",
[GameStatusEnum.Running]: "text-green-500",
[GameStatusEnum.Remote]: "text-zinc-500",
[GameStatusEnum.Queued]: "text-blue-500",
[GameStatusEnum.Updating]: "text-blue-500",
[GameStatusEnum.Uninstalling]: "text-zinc-100",
[GameStatusEnum.SetupRequired]: "text-yellow-500",
[GameStatusEnum.PartiallyInstalled]: "text-gray-600",
};
const gameStatusText: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]: "Not installed",
[GameStatusEnum.Queued]: "Queued",
[GameStatusEnum.Downloading]: "Downloading...",
[GameStatusEnum.Installed]: "Installed",
[GameStatusEnum.Updating]: "Updating...",
[GameStatusEnum.Uninstalling]: "Uninstalling...",
[GameStatusEnum.SetupRequired]: "Setup required",
[GameStatusEnum.Running]: "Running",
[GameStatusEnum.PartiallyInstalled]: "Partially installed",
};
const router = useRouter();
const searchQuery = ref("");
const games: {
[key: string]: { game: Game; status: Ref<GameStatus, GameStatus> };
} = {};
const icons: { [key: string]: string } = {};
const rawGames: Ref<Game[], Game[]> = ref([]);
async function calculateGames(clearAll = false) {
if (clearAll) rawGames.value = [];
// If we update immediately, the navigation gets re-rendered before we
// add all the necessary state, and it freaks tf out
const newGames = await invoke<typeof rawGames.value>("fetch_library");
for (const game of newGames) {
if (games[game.id]) continue;
games[game.id] = await useGame(game.id);
}
for (const game of newGames) {
if (icons[game.id]) continue;
icons[game.id] = await useObject(game.mIconObjectId);
}
rawGames.value = newGames;
}
await calculateGames();
const navigation = computed(() =>
rawGames.value.map((game) => {
const status = games[game.id].status;
const isInstalled = computed(
() =>
status.value.type == GameStatusEnum.Installed ||
status.value.type == GameStatusEnum.SetupRequired
);
const item = {
label: game.mName,
route: `/library/${game.id}`,
prefix: `/library/${game.id}`,
isInstalled,
id: game.id,
};
return item;
})
);
const { currentNavigation, recalculateNavigation } = useCurrentNavigationIndex(
navigation.value
);
const filteredNavigation = computed(() => {
if (!searchQuery.value)
return navigation.value.map((e, i) => ({ ...e, index: i }));
const query = searchQuery.value.toLowerCase();
return navigation.value
.filter((nav) => nav.label.toLowerCase().includes(query))
.map((e, i) => ({ ...e, index: i }));
});
listen("update_library", async (event) => {
console.log("Updating library");
let oldNavigation = navigation.value[currentNavigation.value];
await calculateGames();
recalculateNavigation();
if (oldNavigation !== navigation.value[currentNavigation.value]) {
console.log("Triggered");
router.push("/library");
}
});
</script>
<style scoped>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.list-leave-active {
position: absolute;
}
</style>

View File

@ -0,0 +1,7 @@
<template></template>
<script setup lang="ts">
const loading = useLoadingIndicator();
watch(loading.isLoading, console.log);
</script>

View File

@ -0,0 +1,7 @@
<template>
<NuxtLink
class="inline-flex items-center gap-x-2 px-1 py-0.5 rounded bg-blue-900 text-zinc-100 hover:bg-blue-800"
>
<slot />
</NuxtLink>
</template>

3
composables/app-state.ts Normal file
View File

@ -0,0 +1,3 @@
import type { AppState } from "~/types";
export const useAppState = () => useState<AppState>("state");

View File

@ -32,5 +32,3 @@ listen("update_stats", (event) => {
const stats = useStatsState();
stats.value = event.payload as StatsState;
});
export const useDownloadHistory = () => useState<Array<number>>('history', () => []);

View File

@ -43,7 +43,6 @@ export const useGame = async (gameId: string) => {
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
listen(`update_game/${gameId}`, (event) => {
console.log(event);
const payload: {
status: SerializedGameStatus;
version?: GameVersion;

View File

@ -65,13 +65,7 @@ export function setupHooks() {
*/
}
export function initialNavigation(state: ReturnType<typeof useAppState>) {
if (!state.value)
throw createError({
statusCode: 500,
statusMessage: "App state not valid",
fatal: true,
});
export function initialNavigation(state: Ref<AppState>) {
const router = useRouter();
switch (state.value.status) {

View File

@ -1,5 +1,5 @@
import { convertFileSrc } from "@tauri-apps/api/core";
export const useObject = (id: string) => {
export const useObject = async (id: string) => {
return convertFileSrc(id, "object");
};

View File

@ -7,7 +7,6 @@
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
>
<Logo class="h-10 w-auto sm:h-12" />
</header>
<main
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"

View File

@ -1,43 +0,0 @@
<template>
<NuxtLoadingIndicator color="#2563eb" />
<NuxtLayout class="select-none w-screen h-screen">
<NuxtPage />
<ModalStack />
</NuxtLayout>
</template>
<script setup lang="ts">
import { invoke } from "@tauri-apps/api/core";
const router = useRouter();
const state = useAppState();
async function fetchState() {
try {
state.value = JSON.parse(await invoke("fetch_state"));
if (!state.value)
throw createError({
statusCode: 500,
statusMessage: `App state is: ${state.value}`,
fatal: true,
});
} catch (e) {
console.error("failed to parse state", e);
throw e;
}
}
await fetchState();
// This is inefficient but apparently we do it lol
router.beforeEach(async () => {
await fetchState();
});
setupHooks();
initialNavigation(state);
useHead({
title: "Drop",
});
</script>

View File

@ -1,303 +0,0 @@
<template>
<div class="flex flex-col h-full">
<div class="mb-3 inline-flex gap-x-2">
<div
class="relative transition-transform duration-300 hover:scale-105 active:scale-95"
>
<div
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
>
<MagnifyingGlassIcon
class="h-5 w-5 text-zinc-400"
aria-hidden="true"
/>
</div>
<input
type="text"
v-model="searchQuery"
class="block w-full rounded-lg border-0 bg-zinc-800/50 py-2 pl-10 pr-3 text-zinc-100 placeholder:text-zinc-500 focus:bg-zinc-800 focus:ring-2 focus:ring-inset focus:ring-blue-500 sm:text-sm sm:leading-6"
placeholder="Search library..."
/>
</div>
<button
@click="() => calculateGames(true, true)"
class="p-1 flex items-center justify-center transition-transform duration-300 size-10 hover:scale-110 active:scale-90 rounded-lg bg-zinc-800/50 text-zinc-100"
>
<ArrowPathIcon class="size-4" />
</button>
</div>
<TransitionGroup name="list" tag="ul" class="flex flex-col gap-y-1.5">
<Disclosure
as="div"
v-for="(nav, navIndex) in filteredNavigation"
:key="nav.id"
class="first:pt-0 last:pb-0"
v-slot="{ open }"
:default-open="nav.deft"
>
<dt>
<DisclosureButton
class="flex w-full items-center justify-between text-left text-gray-900 dark:text-white"
>
<span class="text-sm font-semibold font-display">{{
nav.name
}}</span>
<span class="ml-6 flex h-7 items-center">
<PlusSmallIcon v-if="!open" class="size-6" aria-hidden="true" />
<MinusSmallIcon v-else class="size-6" aria-hidden="true" />
</span>
</DisclosureButton>
</dt>
<DisclosurePanel as="dd" class="mt-2 flex flex-col gap-y-1.5">
<NuxtLink
v-for="item in nav.items"
:key="nav.id"
:class="[
'transition-all duration-300 rounded-lg flex items-center px-1 py-1.5 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
currentNavigation == item.id
? 'bg-zinc-800 text-zinc-100 shadow-md shadow-zinc-950/20'
: item.isInstalled.value
? 'text-zinc-300 hover:bg-zinc-800/90 hover:text-zinc-200'
: 'text-zinc-500 hover:bg-zinc-800/70 hover:text-zinc-300',
]"
:href="item.route"
>
<div class="flex items-center w-full gap-x-2">
<div
class="flex-none transition-transform duration-300 hover:-rotate-2"
>
<img
class="size-6 object-cover bg-zinc-900 rounded transition-all duration-300 shadow-sm"
:src="icons[item.id]"
alt=""
/>
</div>
<div class="flex flex-col gap-x-2">
<p
class="text-sm whitespace-nowrap font-display font-semibold"
>
{{ item.label }}
</p>
<p
class="truncate text-[10px] font-bold uppercase font-display"
:class="[
gameStatusTextStyle[games[item.id].status.value.type],
]"
>
{{ gameStatusText[games[item.id].status.value.type] }}
</p>
</div>
</div>
</NuxtLink>
</DisclosurePanel>
</Disclosure>
</TransitionGroup>
<div
v-if="loading"
class="h-full grow flex p-8 justify-center text-zinc-100"
>
<div role="status">
<svg
aria-hidden="true"
class="w-6 h-6 text-transparent animate-spin fill-zinc-600"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span class="sr-only">Loading...</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Disclosure, DisclosureButton, DisclosurePanel } from "@headlessui/vue";
import {
ArrowPathIcon,
MagnifyingGlassIcon,
MinusSmallIcon,
PlusSmallIcon,
} from "@heroicons/vue/20/solid";
import { invoke } from "@tauri-apps/api/core";
import {
GameStatusEnum,
type Collection as Collection,
type Game,
type GameStatus,
} from "~/types";
import { TransitionGroup } from "vue";
import { listen } from "@tauri-apps/api/event";
// Style information
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Installed]: "text-green-500",
[GameStatusEnum.Downloading]: "text-zinc-400",
[GameStatusEnum.Validating]: "text-blue-300",
[GameStatusEnum.Running]: "text-green-500",
[GameStatusEnum.Remote]: "text-zinc-700",
[GameStatusEnum.Queued]: "text-zinc-400",
[GameStatusEnum.Updating]: "text-zinc-400",
[GameStatusEnum.Uninstalling]: "text-zinc-100",
[GameStatusEnum.SetupRequired]: "text-yellow-500",
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
};
const gameStatusText: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Remote]: "Not installed",
[GameStatusEnum.Queued]: "Queued",
[GameStatusEnum.Downloading]: "Downloading...",
[GameStatusEnum.Validating]: "Validating...",
[GameStatusEnum.Installed]: "Installed",
[GameStatusEnum.Updating]: "Updating...",
[GameStatusEnum.Uninstalling]: "Uninstalling...",
[GameStatusEnum.SetupRequired]: "Setup required",
[GameStatusEnum.Running]: "Running",
[GameStatusEnum.PartiallyInstalled]: "Partially installed",
};
const router = useRouter();
const searchQuery = ref("");
const loading = ref(false);
const games: {
[key: string]: { game: Game; status: Ref<GameStatus, GameStatus> };
} = {};
const icons: { [key: string]: string } = {};
const collections: Ref<Collection[]> = ref([]);
async function calculateGames(clearAll = false, forceRefresh = false) {
if (clearAll) {
collections.value = [];
loading.value = true;
}
// If we update immediately, the navigation gets re-rendered before we
// add all the necessary state, and it freaks tf out
const newGames = await invoke<Game[]>("fetch_library", {
hardRefresh: forceRefresh,
});
const otherCollections = await invoke<Collection[]>("fetch_collections", {
hardRefresh: forceRefresh,
});
const allGames = [
...newGames,
...otherCollections
.map((e) => e.entries)
.flat()
.map((e) => e.game),
].filter((v, i, a) => a.indexOf(v) === i);
for (const game of allGames) {
if (games[game.id]) continue;
games[game.id] = await useGame(game.id);
}
for (const game of allGames) {
if (icons[game.id]) continue;
icons[game.id] = await useObject(game.mIconObjectId);
}
const libraryCollection = {
id: "library",
name: "Library",
isDefault: true,
entries: newGames.map((e) => ({ gameId: e.id, game: e })),
} satisfies Collection;
loading.value = false;
collections.value = [libraryCollection, ...otherCollections];
}
// Wait up to 300 ms for the library to load, otherwise
// show the loading state while we while
await new Promise<void>((r) => {
let hasResolved = false;
const resolveFunc = () => {
if (!hasResolved) r();
hasResolved = true;
};
calculateGames(true).then(resolveFunc);
setTimeout(resolveFunc, 300);
});
const navigation = computed(() =>
collections.value.map((collection) => {
const items = collection.entries.map(({ game }) => {
const status = games[game.id].status;
const isInstalled = computed(
() => status.value.type != GameStatusEnum.Remote
);
const item = {
label: game.mName,
route: `/library/${game.id}`,
prefix: `/library/${game.id}`,
isInstalled,
id: game.id,
};
return item;
});
return {
id: collection.id,
name: collection.name,
deft: collection.isDefault,
items,
};
})
);
const route = useRoute();
const currentNavigation = computed(() => {
return route.path.slice("/library/".length);
});
const filteredNavigation = computed(() => {
if (!searchQuery.value)
return navigation.value.map((e, i) => ({ ...e, index: i }));
const query = searchQuery.value.toLowerCase();
return navigation.value
.map((c) => ({
...c,
items: c.items.filter((nav) => nav.label.toLowerCase().includes(query)),
}))
.filter((e) => e.items.length > 0);
});
listen("update_library", async (event) => {
console.log("Updating library");
let oldNavigation = currentNavigation.value;
await calculateGames();
if (oldNavigation !== currentNavigation.value) {
router.push("/library");
}
});
</script>
<style scoped>
.list-move,
.list-enter-active,
.list-leave-active {
transition: all 0.3s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
transform: translateX(-30px);
}
.list-leave-active {
position: absolute;
}
</style>

View File

@ -1,91 +0,0 @@
<template>
<NuxtLayout name="default">
<div
class="grid min-h-full grid-cols-1 grid-rows-[1fr,auto,1fr] lg:grid-cols-[max(50%,36rem),1fr]"
>
<header
class="mx-auto w-full max-w-7xl px-6 pt-6 sm:pt-10 lg:col-span-2 lg:col-start-1 lg:row-start-1 lg:px-8"
>
<Logo class="h-10 w-auto sm:h-12" />
</header>
<main
class="mx-auto w-full max-w-7xl px-6 py-24 sm:py-32 lg:col-span-2 lg:col-start-1 lg:row-start-2 lg:px-8"
>
<div class="max-w-lg">
<p class="text-base font-semibold leading-8 text-blue-600">
{{ error?.statusCode }}
</p>
<h1
class="mt-4 text-3xl font-bold font-display tracking-tight text-zinc-100 sm:text-5xl"
>
Oh no!
</h1>
<p
v-if="message"
class="mt-3 font-bold text-base leading-7 text-red-500"
>
{{ message }}
</p>
<p class="mt-6 text-base leading-7 text-zinc-400">
An error occurred while responding to your request. If you believe
this to be a bug, please report it. Try signing in and see if it
resolves the issue.
</p>
<div class="mt-10">
<!-- full app reload to fix errors -->
<a
href="/store"
class="text-sm font-semibold leading-7 text-blue-600"
><span aria-hidden="true">&larr;</span> Back to store</a
>
</div>
</div>
</main>
<footer class="self-end lg:col-span-2 lg:col-start-1 lg:row-start-3">
<div class="border-t border-zinc-700 bg-zinc-900 py-10">
<nav
class="mx-auto flex w-full max-w-7xl items-center gap-x-4 px-6 text-sm leading-7 text-zinc-400 lg:px-8"
>
<NuxtLink href="/docs">Documentation</NuxtLink>
<svg
viewBox="0 0 2 2"
aria-hidden="true"
class="h-0.5 w-0.5 fill-zinc-600"
>
<circle cx="1" cy="1" r="1" />
</svg>
<a href="https://discord.gg/NHx46XKJWA" target="_blank"
>Support Discord</a
>
</nav>
</div>
</footer>
<div
class="hidden lg:relative lg:col-start-2 lg:row-start-1 lg:row-end-4 lg:block"
>
<img
src="@/assets/wallpaper.jpg"
alt=""
class="absolute inset-0 h-full w-full object-cover"
/>
</div>
</div>
</NuxtLayout>
</template>
<script setup lang="ts">
import type { NuxtError } from "#app";
const props = defineProps({
error: Object as () => NuxtError,
});
const statusCode = props.error?.statusCode;
const message =
props.error?.statusMessage ||
props.error?.message ||
"An unknown error occurred.";
console.error(props.error);
</script>

View File

@ -1,25 +0,0 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: "2024-04-03",
postcss: {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
},
ssr: false,
extends: ["../shared", "../libs/drop-base"],
app: {
baseURL: "/main",
},
devtools: {
enabled: false,
},
});

View File

@ -1,37 +0,0 @@
{
"name": "view",
"private": true,
"version": "0.3.3",
"type": "module",
"scripts": {
"build": "nuxt generate",
"dev": "nuxt dev",
"postinstall": "nuxt prepare",
"tauri": "tauri"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": "^2.7.0",
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/markdown-it": "^14.1.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass-embedded": "^1.79.4",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -1,25 +0,0 @@
<template>
<div class="grow w-full h-full flex items-center justify-center">
<div class="flex flex-col items-center">
<WrenchScrewdriverIcon
class="h-12 w-12 text-blue-600"
aria-hidden="true"
/>
<div class="mt-3 text-center sm:mt-5">
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
Under construction
</h1>
<div class="mt-4">
<p class="text-sm text-zinc-400 max-w-lg">
This page hasn't been implemented yet.
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
WrenchScrewdriverIcon,
} from "@heroicons/vue/20/solid";
</script>

View File

@ -1,25 +0,0 @@
<template>
<div class="grow w-full h-full flex items-center justify-center">
<div class="flex flex-col items-center">
<WrenchScrewdriverIcon
class="h-12 w-12 text-blue-600"
aria-hidden="true"
/>
<div class="mt-3 text-center sm:mt-5">
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
Under construction
</h1>
<div class="mt-4">
<p class="text-sm text-zinc-400 max-w-lg">
This page hasn't been implemented yet.
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import {
WrenchScrewdriverIcon,
} from "@heroicons/vue/20/solid";
</script>

View File

@ -1,23 +0,0 @@
<template>
<div class="grow w-full h-full flex items-center justify-center">
<div class="flex flex-col items-center">
<WrenchScrewdriverIcon
class="h-12 w-12 text-blue-600"
aria-hidden="true"
/>
<div class="mt-3 text-center sm:mt-5">
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
Under construction
</h1>
<div class="mt-4">
<p class="text-sm text-zinc-400 max-w-lg">
This page hasn't been implemented yet.
</p>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { WrenchScrewdriverIcon } from "@heroicons/vue/20/solid";
</script>

View File

@ -1,21 +0,0 @@
/** @type {import('tailwindcss').Config} */
export default {
content: [
"./components/**/*.{js,vue,ts}",
"./layouts/**/*.vue",
"./pages/**/*.vue",
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
"../shared/components/**/*.vue"
],
theme: {
extend: {
fontFamily: {
sans: ["Inter"],
display: ["Motiva Sans"],
},
},
},
plugins: [require("@tailwindcss/forms"), require('@tailwindcss/typography')],
};

View File

@ -1,5 +0,0 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"exclude": ["src-tauri/**/*"]
}

View File

@ -1,96 +0,0 @@
import type { Component } from "vue";
export type NavigationItem = {
prefix: string;
route: string;
label: string;
};
export type QuickActionNav = {
icon: Component;
notifications?: number;
action: () => Promise<void>;
};
export type User = {
id: string;
username: string;
admin: boolean;
displayName: string;
profilePictureObjectId: string;
};
export type AppState = {
status: AppStatus;
user?: User;
};
export type Game = {
id: string;
mName: string;
mShortDescription: string;
mDescription: string;
mIconObjectId: string;
mBannerObjectId: string;
mCoverObjectId: string;
mImageLibraryObjectIds: string[];
mImageCarouselObjectIds: string[];
};
export type Collection = {
id: string;
name: string;
isDefault: boolean;
entries: Array<{ gameId: string; game: Game }>;
};
export type GameVersion = {
launchCommandTemplate: string;
};
export enum AppStatus {
NotConfigured = "NotConfigured",
Offline = "Offline",
SignedOut = "SignedOut",
SignedIn = "SignedIn",
SignedInNeedsReauth = "SignedInNeedsReauth",
ServerUnavailable = "ServerUnavailable",
}
export enum GameStatusEnum {
Remote = "Remote",
Queued = "Queued",
Downloading = "Downloading",
Validating = "Validating",
Installed = "Installed",
Updating = "Updating",
Uninstalling = "Uninstalling",
SetupRequired = "SetupRequired",
Running = "Running",
PartiallyInstalled = "PartiallyInstalled",
}
export type GameStatus = {
type: GameStatusEnum;
version_name?: string;
install_dir?: string;
};
export enum DownloadableType {
Game = "Game",
Tool = "Tool",
DLC = "DLC",
Mod = "Mod",
}
export type DownloadableMetadata = {
id: string;
version: string;
downloadType: DownloadableType;
};
export type Settings = {
autostart: boolean;
maxDownloadThreads: number;
forceOffline: boolean;
};

File diff suppressed because it is too large Load Diff

View File

@ -13,13 +13,5 @@ export default defineNuxtConfig({
ssr: false,
extends: [["../libs/drop-base"]],
app: {
baseURL: "/main",
},
devtools: {
enabled: false,
},
extends: [["./drop-base"]],
});

View File

@ -1,22 +0,0 @@
## This script is largely useless, because there's not much we can do about AppImage size
ARCH=$(uname -m)
# build tauri apps
# NO_STRIP=true yarn tauri build -- --verbose
# unpack appimage
APPIMAGE=$(ls ./src-tauri/target/release/bundle/appimage/*.AppImage)
"$APPIMAGE" --appimage-extract
# strip binary
APPIMAGE_UNPACK="./squashfs-root"
find $APPIMAGE_UNPACK -type f -exec strip -s {} \;
APPIMAGETOOL=$(echo "obsolete-appimagetool-$ARCH.AppImage")
wget -O $APPIMAGETOOL "https://github.com/AppImage/AppImageKit/releases/download/13/$APPIMAGETOOL"
chmod +x $APPIMAGETOOL
APPIMAGE_OUTPUT=$(./$APPIMAGETOOL $APPIMAGE_UNPACK | grep ".AppImage" | grep squashfs-root | awk '{ print $6 }')
mv $APPIMAGE_OUTPUT "$APPIMAGE"

View File

@ -1,22 +1,46 @@
{
"name": "drop-app",
"private": true,
"version": "0.3.1-mac",
"type": "module",
"scripts": {
"build": "node ./build.mjs",
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2.7.0",
"@tauri-apps/plugin-deep-link": "^2.4.1",
"@tauri-apps/plugin-dialog": "^2.3.2",
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": ">=2.0.0",
"@tauri-apps/plugin-deep-link": "~2",
"@tauri-apps/plugin-dialog": "^2.0.1",
"@tauri-apps/plugin-opener": "^2.4.0",
"@tauri-apps/plugin-os": "^2.3.0",
"@tauri-apps/plugin-shell": "^2.3.0",
"pino": "^9.7.0",
"pino-pretty": "^13.1.1"
"@tauri-apps/plugin-os": "~2",
"@tauri-apps/plugin-shell": "^2.2.1",
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue": "latest",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tauri-apps/cli": "^2.7.1"
}
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@tauri-apps/cli": ">=2.0.0",
"@types/markdown-it": "^14.1.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass-embedded": "^1.79.4",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -243,10 +243,7 @@
</div>
</Listbox>
</div>
<div
v-else-if="versionOptions === null || versionOptions?.length == 0"
class="mt-1 rounded-md bg-red-600/10 p-4"
>
<div v-else class="mt-1 rounded-md bg-red-600/10 p-4">
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
@ -259,27 +256,6 @@
</div>
</div>
</div>
<div v-else class="w-full flex items-center justify-center p-4">
<div role="status">
<svg
aria-hidden="true"
class="w-7 h-7 text-transparent animate-spin fill-white"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
fill="currentFill"
/>
</svg>
<span class="sr-only">Loading...</span>
</div>
</div>
<div v-if="installDirs">
<Listbox as="div" v-model="installDir">
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
@ -392,13 +368,6 @@
</template>
</ModalTemplate>
<!--
Dear future DecDuck,
This v-if is necessary for Vue rendering reasons
(it tries to access the game version for not installed games)
You have already tried to remove it
Don't.
-->
<GameOptionsModal
v-if="status.type === GameStatusEnum.Installed"
v-model="configureModalOpen"
@ -534,13 +503,13 @@ async function installFlow() {
installDirs.value = undefined;
try {
versionOptions.value = await invoke("fetch_game_version_options", {
versionOptions.value = await invoke("fetch_game_verion_options", {
gameId: game.value.id,
});
console.log(versionOptions.value);
installDirs.value = await invoke("fetch_download_dir_stats");
} catch (error) {
installError.value = (error as string).toString();
versionOptions.value = undefined;
}
}

View File

@ -4,18 +4,18 @@
class="h-16 overflow-hidden relative rounded-xl flex flex-row border border-zinc-900"
>
<div
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 font-display items-left justify-center pl-2"
class="bg-zinc-900 z-10 w-32 flex flex-col gap-x-2 text-blue-400 font-display items-left justify-center pl-2"
>
<span class="font-bold text-zinc-100">{{ formatKilobytes(stats.speed) }}B/s</span>
<span v-if="stats.time > 0" class="text-xs text-zinc-400"
<span class="font-semibold">{{ formatKilobytes(stats.speed) }}/s</span>
<span v-if="stats.time > 0" class="text-sm"
>{{ formatTime(stats.time) }} left</span
>
</div>
<div class="absolute inset-0 h-full flex flex-row items-end justify-end space-x-[1px]">
<div class="absolute inset-0 h-full flex flex-row items-end justify-end">
<div
v-for="bar in speedHistory"
:style="{ height: `${(bar / speedMax) * 100}%` }"
class="w-[3px] bg-blue-600 rounded-t-full"
class="w-[8px] bg-blue-600/40"
/>
</div>
</div>
@ -62,9 +62,9 @@
class="mt-2 inline-flex items-center gap-x-1 text-zinc-400 text-sm font-display"
><span class="text-zinc-300">{{
formatKilobytes(element.current / 1000)
}}B</span>
}}</span>
/
<span class="">{{ formatKilobytes(element.max / 1000) }}B</span
<span class="">{{ formatKilobytes(element.max / 1000) }}</span
><ServerIcon class="size-5"
/></span>
</div>
@ -91,7 +91,7 @@
<script setup lang="ts">
import { ServerIcon, XMarkIcon } from "@heroicons/vue/20/solid";
import { invoke } from "@tauri-apps/api/core";
import { type DownloadableMetadata, type Game, type GameStatus } from "~/types";
import { GameStatusEnum, type DownloadableMetadata, type Game, type GameStatus } from "~/types";
// const actionNames = {
// [GameStatusEnum.Downloading]: "downloading",
@ -105,12 +105,12 @@ window.addEventListener("resize", (event) => {
const queue = useQueueState();
const stats = useStatsState();
const speedHistory = useDownloadHistory();
const speedHistoryMax = computed(() => windowWidth.value / 4);
const speedHistory = useState<Array<number>>(() => []);
const speedHistoryMax = computed(() => windowWidth.value / 8);
const speedMax = computed(
() => speedHistory.value.reduce((a, b) => (a > b ? a : b)) * 1.1
() => speedHistory.value.reduce((a, b) => (a > b ? a : b)) * 1.3
);
const previousGameId = useState<string | undefined>('previous_game');
const previousGameId = ref<string | undefined>();
const games: Ref<{
[key: string]: { game: Game; status: Ref<GameStatus>; cover: string };
@ -122,15 +122,14 @@ function resetHistoryGraph() {
}
function checkReset(v: QueueState) {
const currentGame = v.queue.at(0)?.meta.id;
// If we don't have a game
if (!currentGame) return;
// If we're finished
if (!currentGame && previousGameId.value) {
previousGameId.value = undefined;
resetHistoryGraph();
return;
}
// If we don't have a game
if (!currentGame) return;
// If we started a new download
if (currentGame && !previousGameId.value) {
previousGameId.value = currentGame;
@ -150,10 +149,9 @@ watch(queue, (v) => {
});
watch(stats, (v) => {
if(v.speed == 0) return;
const newLength = speedHistory.value.push(v.speed);
if (newLength > speedHistoryMax.value) {
speedHistory.value.splice(0, newLength - speedHistoryMax.value);
speedHistory.value.splice(0, 1);
}
checkReset(queue.value);
});
@ -185,7 +183,7 @@ async function cancelGame(meta: DownloadableMetadata) {
}
function formatKilobytes(bytes: number): string {
const units = ["K", "M", "G", "T", "P"];
const units = ["KB", "MB", "GB", "TB", "PB"];
let value = bytes;
let unitIndex = 0;
const scalar = 1000;

View File

@ -0,0 +1,7 @@
<template>
</template>
<script setup lang="ts">
</script>

View File

@ -1,84 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html,
body {
-ms-overflow-style: none; /* IE and Edge /
scrollbar-width: none; / Firefox */
}
/* Hide scrollbar for Chrome, Safari and Opera */
html::-webkit-scrollbar {
display: none;
}
$motiva: (
("MotivaSansThin.ttf", "ttf", 100, normal),
("MotivaSansLight.woff.ttf", "woff", 300, normal),
("MotivaSansRegular.woff.ttf", "woff", 400, normal),
("MotivaSansMedium.woff.ttf", "woff", 500, normal),
("MotivaSansBold.woff.ttf", "woff", 600, normal),
("MotivaSansExtraBold.ttf", "woff", 700, normal),
("MotivaSansBlack.woff.ttf", "woff", 900, normal)
);
$helvetica: (
("Helvetica.woff", "woff", 400, normal),
("Helvetica-Oblique.woff", "woff", 400, italic),
("Helvetica-Bold.woff", "woff", 600, normal),
("Helvetica-BoldOblique.woff", "woff", 600, italic),
("helvetica-light-587ebe5a59211.woff2", "woff2", 300, normal)
);
@each $file, $format, $weight, $style in $motiva {
@font-face {
font-family: "Motiva Sans";
src: url("/fonts/motiva/#{$file}") format($format);
font-weight: $weight;
font-style: $style;
}
}
@each $file, $format, $weight, $style in $helvetica {
@font-face {
font-family: "Helvetica";
src: url("/fonts/helvetica/#{$file}") format($format);
font-weight: $weight;
font-style: $style;
}
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter/InterVariable.ttf");
font-style: normal;
}
@font-face {
font-family: "Inter";
src: url("/fonts/inter/InterVariable-Italic.ttf");
font-style: italic;
}
/* ===== Scrollbar CSS ===== */
/* Firefox */
* {
scrollbar-width: 4px;
scrollbar-color: #52525b #00000000;
}
/* Chrome, Edge, and Safari */
*::-webkit-scrollbar {
width: 4px;
}
*::-webkit-scrollbar-track {
background: transparent;
}
*::-webkit-scrollbar-thumb {
background-color: #52525b;
border-radius: 10px;
border: 3px solid #52525b;
}

View File

@ -1,3 +0,0 @@
import type { AppState } from "~/types";
export const useAppState = () => useState<AppState | undefined>("state");

View File

@ -1,37 +0,0 @@
{
"name": "view",
"private": true,
"version": "0.3.3",
"type": "module",
"scripts": {
"build": "nuxt generate",
"dev": "nuxt dev",
"postinstall": "nuxt prepare",
"tauri": "tauri"
},
"dependencies": {
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": "^2.7.0",
"koa": "^2.16.1",
"markdown-it": "^14.1.0",
"micromark": "^4.0.1",
"nuxt": "^3.16.0",
"scss": "^0.2.4",
"vue-router": "latest",
"vuedraggable": "^4.1.0"
},
"devDependencies": {
"@tailwindcss/forms": "^0.5.9",
"@tailwindcss/typography": "^0.5.15",
"@types/markdown-it": "^14.1.2",
"autoprefixer": "^10.4.20",
"postcss": "^8.4.47",
"sass-embedded": "^1.79.4",
"tailwindcss": "^3.4.13",
"typescript": "^5.8.3",
"vue-tsc": "^2.2.10"
},
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}

View File

@ -1,5 +0,0 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"exclude": ["src-tauri/**/*"]
}

View File

@ -1,96 +0,0 @@
import type { Component } from "vue";
export type NavigationItem = {
prefix: string;
route: string;
label: string;
};
export type QuickActionNav = {
icon: Component;
notifications?: number;
action: () => Promise<void>;
};
export type User = {
id: string;
username: string;
admin: boolean;
displayName: string;
profilePictureObjectId: string;
};
export type AppState = {
status: AppStatus;
user?: User;
};
export type Game = {
id: string;
mName: string;
mShortDescription: string;
mDescription: string;
mIconObjectId: string;
mBannerObjectId: string;
mCoverObjectId: string;
mImageLibraryObjectIds: string[];
mImageCarouselObjectIds: string[];
};
export type Collection = {
id: string;
name: string;
isDefault: boolean;
entries: Array<{ gameId: string; game: Game }>;
};
export type GameVersion = {
launchCommandTemplate: string;
};
export enum AppStatus {
NotConfigured = "NotConfigured",
Offline = "Offline",
SignedOut = "SignedOut",
SignedIn = "SignedIn",
SignedInNeedsReauth = "SignedInNeedsReauth",
ServerUnavailable = "ServerUnavailable",
}
export enum GameStatusEnum {
Remote = "Remote",
Queued = "Queued",
Downloading = "Downloading",
Validating = "Validating",
Installed = "Installed",
Updating = "Updating",
Uninstalling = "Uninstalling",
SetupRequired = "SetupRequired",
Running = "Running",
PartiallyInstalled = "PartiallyInstalled",
}
export type GameStatus = {
type: GameStatusEnum;
version_name?: string;
install_dir?: string;
};
export enum DownloadableType {
Game = "Game",
Tool = "Tool",
DLC = "DLC",
Mod = "Mod",
}
export type DownloadableMetadata = {
id: string;
version: string;
downloadType: DownloadableType;
};
export type Settings = {
autostart: boolean;
maxDownloadThreads: number;
forceOffline: boolean;
};

File diff suppressed because it is too large Load Diff

309
src-tauri/Cargo.lock generated
View File

@ -898,7 +898,7 @@ dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
"core-graphics-types",
"foreign-types 0.5.0",
"foreign-types",
"libc",
]
@ -1284,12 +1284,11 @@ dependencies = [
[[package]]
name = "drop-app"
version = "0.3.3"
version = "0.3.1-mac"
dependencies = [
"atomic-instant-full",
"bitcode",
"boxcar",
"bytes",
"cacache 13.1.0",
"chrono",
"deranged",
@ -1297,24 +1296,21 @@ dependencies = [
"droplet-rs",
"dynfmt",
"filetime",
"futures-core",
"futures-lite",
"gethostname",
"hex 0.4.3",
"http 1.3.1",
"http-serde 2.1.1",
"humansize",
"known-folders",
"log",
"log4rs",
"md5",
"native_model",
"page_size",
"parking_lot 0.12.3",
"rand 0.9.1",
"rayon",
"regex",
"reqwest 0.12.22",
"reqwest 0.12.16",
"reqwest-middleware 0.4.2",
"reqwest-middleware-cache",
"reqwest-websocket",
@ -1327,7 +1323,6 @@ dependencies = [
"sha1",
"shared_child",
"slice-deque",
"sysinfo",
"tar",
"tauri",
"tauri-build",
@ -1341,7 +1336,6 @@ dependencies = [
"tempfile",
"throttle_my_fn",
"tokio",
"tokio-util",
"umu-wrapper-lib",
"url",
"urlencoding",
@ -1591,15 +1585,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared 0.1.1",
]
[[package]]
name = "foreign-types"
version = "0.5.0"
@ -1607,7 +1592,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
dependencies = [
"foreign-types-macros",
"foreign-types-shared 0.3.1",
"foreign-types-shared",
]
[[package]]
@ -1621,12 +1606,6 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "foreign-types-shared"
version = "0.3.1"
@ -2315,15 +2294,6 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "humantime"
version = "2.2.0"
@ -2384,7 +2354,6 @@ dependencies = [
"hyper 1.6.0",
"hyper-util",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"tokio",
"tokio-rustls",
@ -2392,22 +2361,6 @@ dependencies = [
"webpki-roots",
]
[[package]]
name = "hyper-tls"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
dependencies = [
"bytes",
"http-body-util",
"hyper 1.6.0",
"hyper-util",
"native-tls",
"tokio",
"tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.13"
@ -2820,9 +2773,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.172"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
[[package]]
name = "libloading"
@ -2834,12 +2787,6 @@ dependencies = [
"winapi",
]
[[package]]
name = "libm"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
[[package]]
name = "libredox"
version = "0.1.3"
@ -3102,32 +3049,15 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "native-tls"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e"
dependencies = [
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework 2.11.1",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "native_model"
version = "0.6.4"
source = "git+https://github.com/Drop-OSS/native_model.git#a91b422cbd53116df1f20b2459fb3d8257458bfd"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7050d759e3da6673361dddda4f4a743492279dd2c6484a21fbee0a8278620df0"
dependencies = [
"anyhow",
"bincode",
"doc-comment",
"log",
"native_model_macro",
"rmp-serde",
"serde",
@ -3137,10 +3067,10 @@ dependencies = [
[[package]]
name = "native_model_macro"
version = "0.6.4"
source = "git+https://github.com/Drop-OSS/native_model.git#a91b422cbd53116df1f20b2459fb3d8257458bfd"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1577a0bebf5ed1754e240baf5d9b1845f51e598b20600aa894f55e11cd20cc6c"
dependencies = [
"log",
"proc-macro2",
"quote",
"syn 2.0.101",
@ -3211,15 +3141,6 @@ dependencies = [
"minimal-lexical",
]
[[package]]
name = "ntapi"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4"
dependencies = [
"winapi",
]
[[package]]
name = "num-bigint"
version = "0.4.6"
@ -3416,16 +3337,6 @@ dependencies = [
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
dependencies = [
"libc",
"objc2-core-foundation",
]
[[package]]
name = "objc2-io-surface"
version = "0.3.1"
@ -3556,50 +3467,6 @@ dependencies = [
"pathdiff",
]
[[package]]
name = "openssl"
version = "0.10.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8"
dependencies = [
"bitflags 2.9.1",
"cfg-if",
"foreign-types 0.3.2",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.101",
]
[[package]]
name = "openssl-probe"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]]
name = "openssl-sys"
version = "0.9.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "option-ext"
version = "0.2.0"
@ -3656,16 +3523,6 @@ dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "pango"
version = "0.18.3"
@ -4423,9 +4280,9 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.12.22"
version = "0.12.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc931937e6ca3a06e3b6c0aa7841849b160a90351d6ab467a8b9b9959767531"
checksum = "2bf597b113be201cb2269b4c39b39a804d01b99ee95a4278f0ed04e45cff1c71"
dependencies = [
"base64 0.22.1",
"bytes",
@ -4438,23 +4295,22 @@ dependencies = [
"http-body-util",
"hyper 1.6.0",
"hyper-rustls",
"hyper-tls",
"hyper-util",
"ipnet",
"js-sys",
"log",
"native-tls",
"mime",
"once_cell",
"percent-encoding",
"pin-project-lite",
"quinn",
"rustls",
"rustls-native-certs",
"rustls-pki-types",
"serde",
"serde_json",
"serde_urlencoded",
"sync_wrapper 1.0.2",
"tokio",
"tokio-native-tls",
"tokio-rustls",
"tokio-util",
"tower",
@ -4493,7 +4349,7 @@ dependencies = [
"anyhow",
"async-trait",
"http 1.3.1",
"reqwest 0.12.22",
"reqwest 0.12.16",
"serde",
"thiserror 1.0.69",
"tower-service",
@ -4528,7 +4384,7 @@ dependencies = [
"async-tungstenite",
"bytes",
"futures-util",
"reqwest 0.12.22",
"reqwest 0.12.16",
"thiserror 2.0.12",
"tokio",
"tokio-util",
@ -4691,18 +4547,6 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rustls-native-certs"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcff2dd52b58a8d98a70243663a0d234c4e2b79235637849d15913394a247d3"
dependencies = [
"openssl-probe",
"rustls-pki-types",
"schannel",
"security-framework 3.2.0",
]
[[package]]
name = "rustls-pki-types"
version = "1.12.0"
@ -4745,15 +4589,6 @@ dependencies = [
"winapi-util",
]
[[package]]
name = "schannel"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d"
dependencies = [
"windows-sys 0.59.0",
]
[[package]]
name = "schemars"
version = "0.8.22"
@ -4787,42 +4622,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "security-framework"
version = "2.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.9.4",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework"
version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271720403f46ca04f7ba6f55d438f8bd878d6b8ca0a1046e8228c4145bcbb316"
dependencies = [
"bitflags 2.9.1",
"core-foundation 0.10.1",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "selectors"
version = "0.24.0"
@ -5192,7 +4991,7 @@ dependencies = [
"bytemuck",
"cfg_aliases",
"core-graphics",
"foreign-types 0.5.0",
"foreign-types",
"js-sys",
"log",
"objc2 0.5.2",
@ -5380,20 +5179,6 @@ dependencies = [
"libc",
]
[[package]]
name = "sysinfo"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "252800745060e7b9ffb7b2badbd8b31cfa4aa2e61af879d0a3bf2a317c20217d"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows",
]
[[package]]
name = "system-configuration"
version = "0.5.1"
@ -5506,9 +5291,9 @@ dependencies = [
[[package]]
name = "tauri"
version = "2.7.0"
version = "2.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "352a4bc7bf6c25f5624227e3641adf475a6535707451b09bb83271df8b7a6ac7"
checksum = "124e129c9c0faa6bec792c5948c89e86c90094133b0b9044df0ce5f0a8efaa0d"
dependencies = [
"anyhow",
"bytes",
@ -5533,7 +5318,7 @@ dependencies = [
"percent-encoding",
"plist",
"raw-window-handle",
"reqwest 0.12.22",
"reqwest 0.12.16",
"serde",
"serde_json",
"serde_repr",
@ -5557,9 +5342,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "182d688496c06bf08ea896459bf483eb29cdff35c1c4c115fb14053514303064"
checksum = "12f025c389d3adb83114bec704da973142e82fc6ec799c7c750c5e21cefaec83"
dependencies = [
"anyhow",
"cargo_toml",
@ -5579,9 +5364,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.3.1"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b54a99a6cd8e01abcfa61508177e6096a4fe2681efecee9214e962f2f073ae4a"
checksum = "f5df493a1075a241065bc865ed5ef8d0fbc1e76c7afdc0bf0eccfaa7d4f0e406"
dependencies = [
"base64 0.22.1",
"brotli",
@ -5606,9 +5391,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.3.2"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7945b14dc45e23532f2ded6e120170bbdd4af5ceaa45784a6b33d250fbce3f9e"
checksum = "f237fbea5866fa5f2a60a21bea807a2d6e0379db070d89c3a10ac0f2d4649bbc"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@ -5788,9 +5573,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.7.1"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b1cc885be806ea15ff7b0eb47098a7b16323d9228876afda329e34e2d6c4676"
checksum = "9e7bb73d1bceac06c20b3f755b2c8a2cb13b20b50083084a8cf3700daf397ba4"
dependencies = [
"cookie",
"dpi",
@ -5810,9 +5595,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.7.2"
version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe653a2fbbef19fe898efc774bc52c8742576342a33d3d028c189b57eb1d2439"
checksum = "902b5aa9035e16f342eb64f8bf06ccdc2808e411a2525ed1d07672fa4e780bad"
dependencies = [
"gtk",
"http 1.3.1",
@ -5837,9 +5622,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.6.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330c15cabfe1d9f213478c9e8ec2b0c76dab26bb6f314b8ad1c8a568c1d186e"
checksum = "41743bbbeb96c3a100d234e5a0b60a46d5aa068f266160862c7afdbf828ca02e"
dependencies = [
"anyhow",
"brotli",
@ -6064,16 +5849,6 @@ dependencies = [
"syn 2.0.101",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
@ -6086,9 +5861,9 @@ dependencies = [
[[package]]
name = "tokio-util"
version = "0.7.16"
version = "0.7.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "14307c986784f72ef81c89db7d9e28d6ac26d16213b109ea501696195e6e3ce5"
checksum = "66a539a9ad6d5d281510d5bd368c973d636c02dbf8a67300bfb6b950696ad7df"
dependencies = [
"bytes",
"futures-core",
@ -6178,9 +5953,9 @@ dependencies = [
[[package]]
name = "tower-http"
version = "0.6.6"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2"
checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e"
dependencies = [
"bitflags 2.9.1",
"bytes",
@ -6485,12 +6260,6 @@ version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version-compare"
version = "0.2.0"

View File

@ -1,6 +1,6 @@
[package]
name = "drop-app"
version = "0.3.3"
version = "0.3.1-mac"
description = "The client application for the open-source, self-hosted game distribution platform Drop"
authors = ["Drop OSS"]
edition = "2024"
@ -65,17 +65,11 @@ whoami = "1.6.0"
filetime = "0.2.25"
walkdir = "2.5.0"
known-folders = "1.2.0"
native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"}
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
tauri-plugin-opener = "2.4.0"
bitcode = "0.6.6"
reqwest-websocket = "0.5.0"
futures-lite = "2.6.0"
page_size = "0.6.0"
sysinfo = "0.36.1"
humansize = "2.1.3"
tokio-util = { version = "0.7.16", features = ["io"] }
futures-core = "0.3.31"
bytes = "1.10.1"
# tailscale = { path = "./tailscale" }
[dependencies.dynfmt]
@ -83,7 +77,7 @@ version = "0.1.5"
features = ["curly"]
[dependencies.tauri]
version = "2.7.0"
version = "2.1.1"
features = ["protocol-asset", "tray-icon"]
[dependencies.tokio]
@ -107,17 +101,9 @@ version = "2"
features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
[dependencies.reqwest]
version = "0.12.22"
version = "0.12"
default-features = false
features = [
"json",
"http2",
"blocking",
"rustls-tls",
"native-tls-alpn",
"rustls-tls-native-roots",
"stream",
]
features = ["json", "http2", "blocking", "rustls-tls-webpki-roots"]
[dependencies.serde]
version = "1"

View File

@ -7,7 +7,7 @@ use std::{
use serde_json::Value;
use crate::{
database::{db::borrow_db_mut_checked, scan::scan_install_dirs}, error::download_manager_error::DownloadManagerError,
database::db::borrow_db_mut_checked, error::download_manager_error::DownloadManagerError,
};
use super::{
@ -59,8 +59,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
lock.applications.install_dirs.push(new_dir);
drop(lock);
scan_install_dirs();
Ok(())
}

Some files were not shown because too many files have changed in this diff Show More