mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-13 00:02:41 +10:00
Compare commits
3 Commits
346ee1dddc
...
v0.3.0-rc-
| Author | SHA1 | Date | |
|---|---|---|---|
| 6dedf39420 | |||
| 79373cb0c1 | |||
| f264600619 |
35
.github/workflows/release.yml
vendored
35
.github/workflows/release.yml
vendored
@ -54,47 +54,12 @@ jobs:
|
|||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
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.
|
# 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.admin allow
|
|
||||||
sudo security add-trusted-cert -d -r trustRoot -k build.keychain -p codeSign -u -1 drop.pem
|
|
||||||
sudo security authorizationdb remove com.apple.trust-settings.admin
|
|
||||||
|
|
||||||
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
|
- name: install frontend dependencies
|
||||||
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||||
|
|
||||||
- uses: tauri-apps/tauri-action@v0
|
- uses: tauri-apps/tauri-action@v0
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
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
|
|
||||||
with:
|
with:
|
||||||
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
tagName: v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version.
|
||||||
releaseName: 'Auto-release v__VERSION__'
|
releaseName: 'Auto-release v__VERSION__'
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -27,6 +27,3 @@ dist-ssr
|
|||||||
|
|
||||||
src-tauri/flamegraph.svg
|
src-tauri/flamegraph.svg
|
||||||
src-tauri/perf*
|
src-tauri/perf*
|
||||||
|
|
||||||
/*.AppImage
|
|
||||||
/squashfs-root
|
|
||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,6 +1,9 @@
|
|||||||
|
[submodule "drop-base"]
|
||||||
|
path = drop-base
|
||||||
|
url = https://github.com/drop-oss/drop-base
|
||||||
[submodule "src-tauri/tailscale/libtailscale"]
|
[submodule "src-tauri/tailscale/libtailscale"]
|
||||||
path = src-tauri/tailscale/libtailscale
|
path = src-tauri/tailscale/libtailscale
|
||||||
url = https://github.com/tailscale/libtailscale.git
|
url = https://github.com/tailscale/libtailscale.git
|
||||||
[submodule "libs/drop-base"]
|
[submodule "src-tauri/umu/umu-launcher"]
|
||||||
path = libs/drop-base
|
path = src-tauri/umu/umu-launcher
|
||||||
url = https://github.com/drop-oss/drop-base.git
|
url = https://github.com/Open-Wine-Components/umu-launcher.git
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
<template>
|
<template>
|
||||||
<LoadingIndicator />
|
|
||||||
<NuxtLayout class="select-none w-screen h-screen">
|
<NuxtLayout class="select-none w-screen h-screen">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
<ModalStack />
|
<ModalStack />
|
||||||
@ -10,6 +9,8 @@
|
|||||||
import "~/composables/downloads.js";
|
import "~/composables/downloads.js";
|
||||||
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
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 { useAppState } from "./composables/app-state.js";
|
||||||
import {
|
import {
|
||||||
initialNavigation,
|
initialNavigation,
|
||||||
@ -19,26 +20,18 @@ import {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
|
|
||||||
async function fetchState() {
|
|
||||||
try {
|
try {
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
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) {
|
} catch (e) {
|
||||||
console.error("failed to parse state", e);
|
console.error("failed to parse state", e);
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
await fetchState();
|
|
||||||
|
|
||||||
// This is inefficient but apparently we do it lol
|
|
||||||
router.beforeEach(async () => {
|
router.beforeEach(async () => {
|
||||||
await fetchState();
|
try {
|
||||||
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
|
} catch (e) {
|
||||||
|
console.error("failed to parse state", e);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
setupHooks();
|
setupHooks();
|
||||||
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 6.5 MiB |
48
build.mjs
48
build.mjs
@ -1,48 +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 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@ -1,75 +1,49 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||||
<div class="inline-flex divide-x divide-zinc-900">
|
<div class="inline-flex divide-x divide-zinc-900">
|
||||||
<button
|
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
||||||
type="button"
|
|
||||||
@click="() => buttonActions[props.status.type]()"
|
|
||||||
:class="[
|
|
||||||
styles[props.status.type],
|
styles[props.status.type],
|
||||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
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',
|
'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" />
|
||||||
<component
|
|
||||||
:is="buttonIcons[props.status.type]"
|
|
||||||
class="-mr-0.5 size-5"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
{{ buttonNames[props.status.type] }}
|
{{ buttonNames[props.status.type] }}
|
||||||
</button>
|
</button>
|
||||||
<Menu
|
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
||||||
v-if="showDropdown"
|
|
||||||
as="div"
|
|
||||||
class="relative inline-block text-left grow"
|
|
||||||
>
|
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<MenuButton
|
<MenuButton :class="[
|
||||||
:class="[
|
|
||||||
styles[props.status.type],
|
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',
|
'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',
|
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition
|
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
|
||||||
enter-active-class="transition ease-out duration-100"
|
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
||||||
enter-from-class="transform opacity-0 scale-95"
|
leave-from-class="transform opacity-100 scale-100" leave-to-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
|
<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">
|
<div class="py-1">
|
||||||
<MenuItem v-if="showOptions" v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button
|
<button @click="() => emit('options')" :class="[
|
||||||
@click="() => emit('options')"
|
|
||||||
:class="[
|
|
||||||
active
|
active
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
: 'text-zinc-400',
|
: 'text-zinc-400',
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
Options
|
Options
|
||||||
<Cog6ToothIcon class="size-5" />
|
<Cog6ToothIcon class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button
|
<button @click="() => emit('uninstall')" :class="[
|
||||||
@click="() => emit('uninstall')"
|
|
||||||
:class="[
|
|
||||||
active
|
active
|
||||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||||
: 'text-zinc-400',
|
: 'text-zinc-400',
|
||||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||||
]"
|
]">
|
||||||
>
|
|
||||||
Uninstall
|
Uninstall
|
||||||
<TrashIcon class="size-5" />
|
<TrashIcon class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -87,8 +61,6 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QueueListIcon,
|
QueueListIcon,
|
||||||
ServerIcon,
|
|
||||||
StopIcon,
|
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
|
|
||||||
@ -105,7 +77,7 @@ const emit = defineEmits<{
|
|||||||
(e: "uninstall"): void;
|
(e: "uninstall"): void;
|
||||||
(e: "kill"): void;
|
(e: "kill"): void;
|
||||||
(e: "options"): void;
|
(e: "options"): void;
|
||||||
(e: "resume"): void;
|
(e: "resume"): void
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showDropdown = computed(
|
const showDropdown = computed(
|
||||||
@ -115,10 +87,6 @@ const showDropdown = computed(
|
|||||||
props.status.type === GameStatusEnum.PartiallyInstalled
|
props.status.type === GameStatusEnum.PartiallyInstalled
|
||||||
);
|
);
|
||||||
|
|
||||||
const showOptions = computed(
|
|
||||||
() => props.status.type === GameStatusEnum.Installed
|
|
||||||
);
|
|
||||||
|
|
||||||
const styles: { [key in GameStatusEnum]: string } = {
|
const styles: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]:
|
[GameStatusEnum.Remote]:
|
||||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||||
@ -126,8 +94,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",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.Downloading]:
|
[GameStatusEnum.Downloading]:
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"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]:
|
[GameStatusEnum.SetupRequired]:
|
||||||
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
||||||
[GameStatusEnum.Installed]:
|
[GameStatusEnum.Installed]:
|
||||||
@ -139,45 +105,42 @@ const styles: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Running]:
|
[GameStatusEnum.Running]:
|
||||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.PartiallyInstalled]:
|
[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 } = {
|
const buttonNames: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "Install",
|
[GameStatusEnum.Remote]: "Install",
|
||||||
[GameStatusEnum.Queued]: "Queued",
|
[GameStatusEnum.Queued]: "Queued",
|
||||||
[GameStatusEnum.Downloading]: "Downloading",
|
[GameStatusEnum.Downloading]: "Downloading",
|
||||||
[GameStatusEnum.Validating]: "Validating",
|
|
||||||
[GameStatusEnum.SetupRequired]: "Setup",
|
[GameStatusEnum.SetupRequired]: "Setup",
|
||||||
[GameStatusEnum.Installed]: "Play",
|
[GameStatusEnum.Installed]: "Play",
|
||||||
[GameStatusEnum.Updating]: "Updating",
|
[GameStatusEnum.Updating]: "Updating",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
||||||
[GameStatusEnum.Running]: "Stop",
|
[GameStatusEnum.Running]: "Stop",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "Resume",
|
[GameStatusEnum.PartiallyInstalled]: "Resume"
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
||||||
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
|
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Queued]: QueueListIcon,
|
[GameStatusEnum.Queued]: QueueListIcon,
|
||||||
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
|
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Validating]: ServerIcon,
|
|
||||||
[GameStatusEnum.SetupRequired]: WrenchIcon,
|
[GameStatusEnum.SetupRequired]: WrenchIcon,
|
||||||
[GameStatusEnum.Installed]: PlayIcon,
|
[GameStatusEnum.Installed]: PlayIcon,
|
||||||
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Uninstalling]: TrashIcon,
|
[GameStatusEnum.Uninstalling]: TrashIcon,
|
||||||
[GameStatusEnum.Running]: StopIcon,
|
[GameStatusEnum.Running]: PlayIcon,
|
||||||
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon,
|
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||||
[GameStatusEnum.Remote]: () => emit("install"),
|
[GameStatusEnum.Remote]: () => emit("install"),
|
||||||
[GameStatusEnum.Queued]: () => emit("queue"),
|
[GameStatusEnum.Queued]: () => emit("queue"),
|
||||||
[GameStatusEnum.Downloading]: () => emit("queue"),
|
[GameStatusEnum.Downloading]: () => emit("queue"),
|
||||||
[GameStatusEnum.Validating]: () => emit("queue"),
|
|
||||||
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||||
[GameStatusEnum.Installed]: () => emit("launch"),
|
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||||
[GameStatusEnum.Uninstalling]: () => { },
|
[GameStatusEnum.Uninstalling]: () => { },
|
||||||
[GameStatusEnum.Running]: () => emit("kill"),
|
[GameStatusEnum.Running]: () => emit("kill"),
|
||||||
[GameStatusEnum.PartiallyInstalled]: () => emit("resume"),
|
[GameStatusEnum.PartiallyInstalled]: () => emit("resume")
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -37,7 +37,7 @@
|
|||||||
<component class="h-5" :is="item.icon" />
|
<component class="h-5" :is="item.icon" />
|
||||||
</HeaderWidget>
|
</HeaderWidget>
|
||||||
</li>
|
</li>
|
||||||
<OfflineHeaderWidget v-if="state?.status === AppStatus.Offline" />
|
<OfflineHeaderWidget v-if="state.status === AppStatus.Offline" />
|
||||||
<HeaderUserWidget />
|
<HeaderUserWidget />
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Menu v-if="state?.user" as="div" class="relative inline-block">
|
<Menu v-if="state.user" as="div" class="relative inline-block">
|
||||||
<MenuButton>
|
<MenuButton>
|
||||||
<HeaderWidget>
|
<HeaderWidget>
|
||||||
<div class="inline-flex items-center text-zinc-300 hover:text-white">
|
<div class="inline-flex items-center text-zinc-300 hover:text-white">
|
||||||
@ -23,7 +23,7 @@
|
|||||||
<MenuItems
|
<MenuItems
|
||||||
class="absolute bg-zinc-900 right-0 top-10 z-50 w-56 origin-top-right focus:outline-none shadow-md"
|
class="absolute bg-zinc-900 right-0 top-10 z-50 w-56 origin-top-right focus:outline-none shadow-md"
|
||||||
>
|
>
|
||||||
<div class="flex-col gap-y-2">
|
<PanelWidget class="flex-col gap-y-2">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/id/me"
|
to="/id/me"
|
||||||
class="transition inline-flex items-center w-full py-3 px-4 hover:bg-zinc-800"
|
class="transition inline-flex items-center w-full py-3 px-4 hover:bg-zinc-800"
|
||||||
@ -65,7 +65,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</PanelWidget>
|
||||||
</MenuItems>
|
</MenuItems>
|
||||||
</transition>
|
</transition>
|
||||||
</Menu>
|
</Menu>
|
||||||
@ -87,7 +87,7 @@ router.afterEach(() => {
|
|||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
const profilePictureUrl: string = await useObject(
|
const profilePictureUrl: string = await useObject(
|
||||||
state.value?.user?.profilePictureObjectId ?? ""
|
state.value.user?.profilePictureObjectId ?? ""
|
||||||
);
|
);
|
||||||
const adminUrl: string = await invoke("gen_drop_url", {
|
const adminUrl: string = await invoke("gen_drop_url", {
|
||||||
path: "/admin",
|
path: "/admin",
|
||||||
@ -13,7 +13,11 @@
|
|||||||
<div class="max-w-lg">
|
<div class="max-w-lg">
|
||||||
<slot />
|
<slot />
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<div>
|
<button
|
||||||
|
@click="() => authWrapper_wrapper()"
|
||||||
|
:disabled="loading"
|
||||||
|
class="text-sm text-left font-semibold leading-7 text-blue-600"
|
||||||
|
>
|
||||||
<div v-if="loading" role="status">
|
<div v-if="loading" role="status">
|
||||||
<svg
|
<svg
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
@ -33,19 +37,10 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="inline-flex gap-x-8 items-center" v-else>
|
<span v-else>
|
||||||
<button
|
Sign in with your browser <span aria-hidden="true">→</span>
|
||||||
@click="() => authWrapper_wrapper()"
|
|
||||||
:disabled="loading"
|
|
||||||
class="px-3 py-1 inline-flex items-center gap-x-2 bg-zinc-700 rounded text-sm text-left font-semibold leading-7 text-white"
|
|
||||||
>
|
|
||||||
Sign in with your browser <ArrowTopRightOnSquareIcon class="size-4" />
|
|
||||||
</button>
|
|
||||||
<NuxtLink href="/auth/code" class="text-zinc-100 text-sm hover:text-zinc-300">
|
|
||||||
Use a code →
|
|
||||||
</NuxtLink>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</button>
|
||||||
|
|
||||||
<div class="mt-5" v-if="offerManual">
|
<div class="mt-5" v-if="offerManual">
|
||||||
<h1 class="text-zinc-100 font-semibold">Having trouble?</h1>
|
<h1 class="text-zinc-100 font-semibold">Having trouble?</h1>
|
||||||
@ -126,7 +121,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||||
import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/20/solid";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
@ -1,16 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-3 inline-flex gap-x-2">
|
|
||||||
<div
|
<div
|
||||||
class="relative transition-transform duration-300 hover:scale-105 active:scale-95"
|
class="relative mb-3 transition-transform duration-300 hover:scale-105 active:scale-95"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
|
class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3"
|
||||||
>
|
>
|
||||||
<MagnifyingGlassIcon
|
<MagnifyingGlassIcon class="h-5 w-5 text-zinc-400" aria-hidden="true" />
|
||||||
class="h-5 w-5 text-zinc-400"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
@ -19,13 +15,6 @@
|
|||||||
placeholder="Search library..."
|
placeholder="Search library..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<TransitionGroup name="list" tag="ul" class="flex flex-col gap-y-1.5">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
@ -71,7 +60,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ArrowPathIcon, MagnifyingGlassIcon } from "@heroicons/vue/20/solid";
|
import { MagnifyingGlassIcon } from "@heroicons/vue/20/solid";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { GameStatusEnum, type Game, type GameStatus } from "~/types";
|
import { GameStatusEnum, type Game, type GameStatus } from "~/types";
|
||||||
import { TransitionGroup } from "vue";
|
import { TransitionGroup } from "vue";
|
||||||
@ -81,26 +70,24 @@ import { listen } from "@tauri-apps/api/event";
|
|||||||
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Installed]: "text-green-500",
|
[GameStatusEnum.Installed]: "text-green-500",
|
||||||
[GameStatusEnum.Downloading]: "text-blue-500",
|
[GameStatusEnum.Downloading]: "text-blue-500",
|
||||||
[GameStatusEnum.Validating]: "text-blue-300",
|
|
||||||
[GameStatusEnum.Running]: "text-green-500",
|
[GameStatusEnum.Running]: "text-green-500",
|
||||||
[GameStatusEnum.Remote]: "text-zinc-500",
|
[GameStatusEnum.Remote]: "text-zinc-500",
|
||||||
[GameStatusEnum.Queued]: "text-blue-500",
|
[GameStatusEnum.Queued]: "text-blue-500",
|
||||||
[GameStatusEnum.Updating]: "text-blue-500",
|
[GameStatusEnum.Updating]: "text-blue-500",
|
||||||
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
||||||
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
|
[GameStatusEnum.PartiallyInstalled]: "text-gray-600"
|
||||||
};
|
};
|
||||||
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "Not installed",
|
[GameStatusEnum.Remote]: "Not installed",
|
||||||
[GameStatusEnum.Queued]: "Queued",
|
[GameStatusEnum.Queued]: "Queued",
|
||||||
[GameStatusEnum.Downloading]: "Downloading...",
|
[GameStatusEnum.Downloading]: "Downloading...",
|
||||||
[GameStatusEnum.Validating]: "Validating...",
|
|
||||||
[GameStatusEnum.Installed]: "Installed",
|
[GameStatusEnum.Installed]: "Installed",
|
||||||
[GameStatusEnum.Updating]: "Updating...",
|
[GameStatusEnum.Updating]: "Updating...",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
||||||
[GameStatusEnum.SetupRequired]: "Setup required",
|
[GameStatusEnum.SetupRequired]: "Setup required",
|
||||||
[GameStatusEnum.Running]: "Running",
|
[GameStatusEnum.Running]: "Running",
|
||||||
[GameStatusEnum.PartiallyInstalled]: "Partially installed",
|
[GameStatusEnum.PartiallyInstalled]: "Partially installed"
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -114,20 +101,16 @@ const icons: { [key: string]: string } = {};
|
|||||||
|
|
||||||
const rawGames: Ref<Game[], Game[]> = ref([]);
|
const rawGames: Ref<Game[], Game[]> = ref([]);
|
||||||
|
|
||||||
async function calculateGames(clearAll = false) {
|
async function calculateGames() {
|
||||||
if (clearAll) rawGames.value = [];
|
rawGames.value = await invoke("fetch_library");
|
||||||
// If we update immediately, the navigation gets re-rendered before we
|
for (const game of rawGames.value) {
|
||||||
// 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;
|
if (games[game.id]) continue;
|
||||||
games[game.id] = await useGame(game.id);
|
games[game.id] = await useGame(game.id);
|
||||||
}
|
}
|
||||||
for (const game of newGames) {
|
for (const game of rawGames.value) {
|
||||||
if (icons[game.id]) continue;
|
if (icons[game.id]) continue;
|
||||||
icons[game.id] = await useObject(game.mIconObjectId);
|
icons[game.id] = await useObject(game.mIconObjectId);
|
||||||
}
|
}
|
||||||
rawGames.value = newGames;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await calculateGames();
|
await calculateGames();
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="h-16 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
class="h-10 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
||||||
>
|
>
|
||||||
<div class="px-5 py-3 grow" @mousedown="() => window.startDragging()">
|
<div class="px-5 py-3 grow" @mousedown="() => window.startDragging()">
|
||||||
<Wordmark class="mt-1" />
|
<Wordmark class="mt-1" />
|
||||||
3
composables/app-state.ts
Normal file
3
composables/app-state.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { AppState } from "~/types";
|
||||||
|
|
||||||
|
export const useAppState = () => useState<AppState>("state");
|
||||||
@ -14,6 +14,7 @@ export type SerializedGameStatus = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
||||||
|
console.log(status);
|
||||||
if (status[0]) {
|
if (status[0]) {
|
||||||
return {
|
return {
|
||||||
type: status[0].type,
|
type: status[0].type,
|
||||||
@ -47,6 +48,7 @@ export const useGame = async (gameId: string) => {
|
|||||||
status: SerializedGameStatus;
|
status: SerializedGameStatus;
|
||||||
version?: GameVersion;
|
version?: GameVersion;
|
||||||
} = event.payload as any;
|
} = event.payload as any;
|
||||||
|
console.log(payload.status);
|
||||||
gameStatusRegistry[gameId].value = parseStatus(payload.status);
|
gameStatusRegistry[gameId].value = parseStatus(payload.status);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -18,7 +18,7 @@ export function setupHooks() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
listen("auth/finished", async (event) => {
|
listen("auth/finished", async (event) => {
|
||||||
router.push("/library");
|
router.push("/store");
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -30,31 +30,12 @@ export function setupHooks() {
|
|||||||
description: `Drop encountered an error while downloading your game: "${(
|
description: `Drop encountered an error while downloading your game: "${(
|
||||||
event.payload as unknown as string
|
event.payload as unknown as string
|
||||||
).toString()}"`,
|
).toString()}"`,
|
||||||
buttonText: "Close",
|
buttonText: "Close"
|
||||||
},
|
},
|
||||||
(e, c) => c()
|
(e, c) => c()
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// This is for errors that (we think) aren't our fault
|
|
||||||
listen("launch_external_error", (event) => {
|
|
||||||
createModal(
|
|
||||||
ModalType.Confirmation,
|
|
||||||
{
|
|
||||||
title: "Did something go wrong?",
|
|
||||||
description:
|
|
||||||
"Drop detected that something might've gone wrong with launching your game. Do you want to open the log directory?",
|
|
||||||
buttonText: "Open",
|
|
||||||
},
|
|
||||||
async (e, c) => {
|
|
||||||
if (e == "confirm") {
|
|
||||||
await invoke("open_process_logs", { gameId: event.payload });
|
|
||||||
}
|
|
||||||
c();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
||||||
document.addEventListener("contextmenu", (event) => {
|
document.addEventListener("contextmenu", (event) => {
|
||||||
@ -65,13 +46,7 @@ export function setupHooks() {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialNavigation(state: ReturnType<typeof useAppState>) {
|
export function initialNavigation(state: Ref<AppState>) {
|
||||||
if (!state.value)
|
|
||||||
throw createError({
|
|
||||||
statusCode: 500,
|
|
||||||
statusMessage: "App state not valid",
|
|
||||||
fatal: true,
|
|
||||||
});
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
switch (state.value.status) {
|
switch (state.value.status) {
|
||||||
@ -88,6 +63,6 @@ export function initialNavigation(state: ReturnType<typeof useAppState>) {
|
|||||||
router.push("/error/serverunavailable");
|
router.push("/error/serverunavailable");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
router.push("/library");
|
router.push("/store");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1
drop-base
Submodule
1
drop-base
Submodule
Submodule drop-base added at 26698e5b06
@ -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"
|
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" />
|
<Logo class="h-10 w-auto sm:h-12" />
|
||||||
|
|
||||||
</header>
|
</header>
|
||||||
<main
|
<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"
|
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"
|
||||||
Submodule libs/drop-base deleted from 04125e89be
@ -1,7 +0,0 @@
|
|||||||
<template></template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
const loading = useLoadingIndicator();
|
|
||||||
|
|
||||||
watch(loading.isLoading, console.log);
|
|
||||||
</script>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import type { AppState } from "~/types";
|
|
||||||
|
|
||||||
export const useAppState = () => useState<AppState | undefined>("state");
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "view",
|
|
||||||
"private": true,
|
|
||||||
"version": "0.3.1",
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="min-h-full w-full flex items-center justify-center">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<div class="text-center">
|
|
||||||
<h1 class="text-3xl font-semibold font-display leading-6 text-zinc-100">
|
|
||||||
Device authorization
|
|
||||||
</h1>
|
|
||||||
<div class="mt-4">
|
|
||||||
<p class="text-sm text-zinc-400 max-w-md mx-auto">
|
|
||||||
Open Drop on another one of your devices, and use your account
|
|
||||||
dropdown to "Authorize client", and enter the code below.
|
|
||||||
</p>
|
|
||||||
<div
|
|
||||||
class="mt-8 flex items-center justify-center gap-x-5 text-8xl font-bold text-zinc-100"
|
|
||||||
>
|
|
||||||
<span v-for="letter in code.split('')">{{ letter }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mt-10 flex items-center justify-center gap-x-6">
|
|
||||||
<NuxtLink href="/auth" class="text-sm font-semibold text-blue-600"
|
|
||||||
><span aria-hidden="true">←</span> Use a different method
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
const code = await invoke<string>("auth_initiate_code");
|
|
||||||
|
|
||||||
definePageMeta({
|
|
||||||
layout: "mini",
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@ -1,37 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="grow w-full h-full flex items-center justify-center">
|
|
||||||
<div class="flex flex-col items-center">
|
|
||||||
<BuildingStorefrontIcon
|
|
||||||
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">
|
|
||||||
Store not supported in client
|
|
||||||
</h1>
|
|
||||||
<div class="mt-4">
|
|
||||||
<p class="text-sm text-zinc-400 max-w-lg">
|
|
||||||
Currently, Drop requires you to view the store in your browser.
|
|
||||||
Please click the button below to open it in your default browser.
|
|
||||||
</p>
|
|
||||||
<NuxtLink
|
|
||||||
:href="storeUrl"
|
|
||||||
target="_blank"
|
|
||||||
class="mt-6 transition text-sm/6 font-semibold text-zinc-400 hover:text-zinc-100 inline-flex gap-x-2 items-center duration-200 hover:scale-105"
|
|
||||||
>
|
|
||||||
Open Store <ArrowTopRightOnSquareIcon class="size-4" />
|
|
||||||
</NuxtLink>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
ArrowTopRightOnSquareIcon,
|
|
||||||
BuildingStorefrontIcon,
|
|
||||||
} from "@heroicons/vue/20/solid";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
const storeUrl = await invoke<string>("gen_drop_url", { path: "/store" });
|
|
||||||
</script>
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
{
|
|
||||||
// https://nuxt.com/docs/guide/concepts/typescript
|
|
||||||
"extends": "./.nuxt/tsconfig.json",
|
|
||||||
"exclude": ["src-tauri/**/*"]
|
|
||||||
}
|
|
||||||
8091
main/yarn.lock
8091
main/yarn.lock
File diff suppressed because it is too large
Load Diff
@ -13,9 +13,5 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
extends: [["../libs/drop-base"]],
|
extends: [["./drop-base"]],
|
||||||
|
|
||||||
app: {
|
|
||||||
baseURL: "/main",
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
@ -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"
|
|
||||||
45
package.json
45
package.json
@ -1,22 +1,45 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"version": "0.3.0-rc-7",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node ./build.mjs",
|
"build": "nuxt build",
|
||||||
|
"dev": "nuxt dev",
|
||||||
|
"generate": "nuxt generate",
|
||||||
|
"preview": "nuxt preview",
|
||||||
|
"postinstall": "nuxt prepare",
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tauri-apps/api": "^2.7.0",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@tauri-apps/plugin-deep-link": "^2.4.1",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@tauri-apps/plugin-dialog": "^2.3.2",
|
"@nuxtjs/tailwindcss": "^6.12.2",
|
||||||
"@tauri-apps/plugin-opener": "^2.4.0",
|
"@tauri-apps/api": ">=2.0.0",
|
||||||
"@tauri-apps/plugin-os": "^2.3.0",
|
"@tauri-apps/plugin-deep-link": "~2",
|
||||||
"@tauri-apps/plugin-shell": "^2.3.0",
|
"@tauri-apps/plugin-dialog": "^2.0.1",
|
||||||
"pino": "^9.7.0",
|
"@tauri-apps/plugin-os": "~2",
|
||||||
"pino-pretty": "^13.1.1"
|
"@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": {
|
"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"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -76,7 +76,7 @@
|
|||||||
<TransitionGroup name="slide" tag="div" class="h-full">
|
<TransitionGroup name="slide" tag="div" class="h-full">
|
||||||
<img
|
<img
|
||||||
v-for="(url, index) in mediaUrls"
|
v-for="(url, index) in mediaUrls"
|
||||||
:key="index"
|
:key="url"
|
||||||
:src="url"
|
:src="url"
|
||||||
class="absolute inset-0 w-full h-full object-cover"
|
class="absolute inset-0 w-full h-full object-cover"
|
||||||
v-show="index === currentImageIndex"
|
v-show="index === currentImageIndex"
|
||||||
@ -157,8 +157,8 @@
|
|||||||
<template #default>
|
<template #default>
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||||
<h3 class="text-base font-semibold text-zinc-100">
|
<h3 class="text-base font-semibold text-zinc-100"
|
||||||
Install {{ game.mName }}?
|
>Install {{ game.mName }}?
|
||||||
</h3>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-zinc-400">
|
<p class="text-sm text-zinc-400">
|
||||||
@ -350,7 +350,9 @@
|
|||||||
<template #buttons>
|
<template #buttons>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@click="() => install()"
|
@click="() => install()"
|
||||||
:disabled="!(versionOptions && versionOptions.length > 0)"
|
:disabled="
|
||||||
|
!(versionOptions && versionOptions.length > 0)
|
||||||
|
"
|
||||||
:loading="installLoading"
|
:loading="installLoading"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="ml-2 w-full sm:w-fit"
|
class="ml-2 w-full sm:w-fit"
|
||||||
@ -368,18 +370,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModalTemplate>
|
</ModalTemplate>
|
||||||
|
|
||||||
<!--
|
<GameOptionsModal v-if="status.type === GameStatusEnum.Installed" v-model="configureModalOpen" :game-id="game.id" />
|
||||||
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"
|
|
||||||
:game-id="game.id"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
enter="transition ease-out duration-300"
|
enter="transition ease-out duration-300"
|
||||||
@ -429,7 +420,7 @@
|
|||||||
<img
|
<img
|
||||||
v-for="(url, index) in mediaUrls"
|
v-for="(url, index) in mediaUrls"
|
||||||
v-show="currentImageIndex === index"
|
v-show="currentImageIndex === index"
|
||||||
:key="index"
|
:key="url"
|
||||||
:src="url"
|
:src="url"
|
||||||
class="max-h-[90vh] max-w-[90vw] object-contain"
|
class="max-h-[90vh] max-w-[90vw] object-contain"
|
||||||
:alt="`${game.mName} screenshot ${index + 1}`"
|
:alt="`${game.mName} screenshot ${index + 1}`"
|
||||||
@ -450,6 +441,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
TransitionChild,
|
||||||
|
TransitionRoot,
|
||||||
Listbox,
|
Listbox,
|
||||||
ListboxButton,
|
ListboxButton,
|
||||||
ListboxLabel,
|
ListboxLabel,
|
||||||
@ -487,10 +482,7 @@ const bannerUrl = await useObject(game.value.mBannerObjectId);
|
|||||||
|
|
||||||
// Get all available images
|
// Get all available images
|
||||||
const mediaUrls = await Promise.all(
|
const mediaUrls = await Promise.all(
|
||||||
game.value.mImageCarouselObjectIds.map(async (v) => {
|
game.value.mImageCarouselObjectIds.map((id) => useObject(id))
|
||||||
const src = await useObject(v);
|
|
||||||
return src;
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlDescription = micromark(game.value.mDescription);
|
const htmlDescription = micromark(game.value.mDescription);
|
||||||
@ -504,6 +496,7 @@ const currentImageIndex = ref(0);
|
|||||||
|
|
||||||
const configureModalOpen = ref(false);
|
const configureModalOpen = ref(false);
|
||||||
|
|
||||||
|
|
||||||
async function installFlow() {
|
async function installFlow() {
|
||||||
installFlowOpen.value = true;
|
installFlowOpen.value = true;
|
||||||
versionOptions.value = undefined;
|
versionOptions.value = undefined;
|
||||||
@ -543,9 +536,10 @@ async function install() {
|
|||||||
|
|
||||||
async function resumeDownload() {
|
async function resumeDownload() {
|
||||||
try {
|
try {
|
||||||
await invoke("resume_download", { gameId: game.value.id });
|
await invoke("resume_download", { gameId: game.value.id })
|
||||||
} catch (e) {
|
}
|
||||||
console.error(e);
|
catch(e) {
|
||||||
|
console.error(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,6 +106,8 @@ const systemData = await invoke<{
|
|||||||
dataDir: string;
|
dataDir: string;
|
||||||
}>("fetch_system_data");
|
}>("fetch_system_data");
|
||||||
|
|
||||||
|
console.log(systemData);
|
||||||
|
|
||||||
clientId.value = systemData.clientId;
|
clientId.value = systemData.clientId;
|
||||||
baseUrl.value = systemData.baseUrl;
|
baseUrl.value = systemData.baseUrl;
|
||||||
dataDir.value = systemData.dataDir;
|
dataDir.value = systemData.dataDir;
|
||||||
3
pages/store/index.vue
Normal file
3
pages/store/index.vue
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
<template>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts"></script>
|
||||||
518
src-tauri/Cargo.lock
generated
518
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,9 +1,9 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "drop-app"
|
name = "drop-app"
|
||||||
version = "0.3.1"
|
version = "0.3.0-rc-7"
|
||||||
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
description = "The client application for the open-source, self-hosted game distribution platform Drop"
|
||||||
authors = ["Drop OSS"]
|
authors = ["Drop OSS"]
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@ -25,6 +25,7 @@ tauri-build = { version = "2.0.0", features = [] }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
tauri-plugin-shell = "2.2.1"
|
tauri-plugin-shell = "2.2.1"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
serde-binary = "0.5.0"
|
||||||
rayon = "1.10.0"
|
rayon = "1.10.0"
|
||||||
webbrowser = "1.0.2"
|
webbrowser = "1.0.2"
|
||||||
url = "2.5.2"
|
url = "2.5.2"
|
||||||
@ -66,13 +67,6 @@ filetime = "0.2.25"
|
|||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
known-folders = "1.2.0"
|
known-folders = "1.2.0"
|
||||||
native_model = { version = "0.6.1", features = ["rmp_serde_1_3"] }
|
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"
|
|
||||||
# tailscale = { path = "./tailscale" }
|
# tailscale = { path = "./tailscale" }
|
||||||
|
|
||||||
[dependencies.dynfmt]
|
[dependencies.dynfmt]
|
||||||
@ -80,7 +74,7 @@ version = "0.1.5"
|
|||||||
features = ["curly"]
|
features = ["curly"]
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "2.7.0"
|
version = "2.1.1"
|
||||||
features = ["protocol-asset", "tray-icon"]
|
features = ["protocol-asset", "tray-icon"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
@ -106,7 +100,7 @@ features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
|
|||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.12"
|
version = "0.12"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["json", "http2", "blocking", "rustls-tls", "native-tls-alpn", "rustls-tls-webpki-roots"]
|
features = ["json", "http2", "blocking", "rustls-tls-webpki-roots"]
|
||||||
|
|
||||||
[dependencies.serde]
|
[dependencies.serde]
|
||||||
version = "1"
|
version = "1"
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tauri_build::build();
|
tauri_build::build()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@
|
|||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
"deep-link:default",
|
"deep-link:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"os:default",
|
"os:default"
|
||||||
"opener:default"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -13,8 +13,8 @@ pub fn cleanup_and_exit(app: &AppHandle, state: &tauri::State<'_, std::sync::Mut
|
|||||||
let download_manager = state.lock().unwrap().download_manager.clone();
|
let download_manager = state.lock().unwrap().download_manager.clone();
|
||||||
match download_manager.ensure_terminated() {
|
match download_manager.ensure_terminated() {
|
||||||
Ok(res) => match res {
|
Ok(res) => match res {
|
||||||
Ok(()) => debug!("download manager terminated correctly"),
|
Ok(_) => debug!("download manager terminated correctly"),
|
||||||
Err(()) => error!("download manager failed to terminate correctly"),
|
Err(_) => error!("download manager failed to terminate correctly"),
|
||||||
},
|
},
|
||||||
Err(e) => panic!("{e:?}"),
|
Err(e) => panic!("{e:?}"),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ use std::{
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
use crate::{
|
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::{
|
use super::{
|
||||||
@ -59,8 +59,6 @@ pub fn add_download_dir(new_dir: PathBuf) -> Result<(), DownloadManagerError<()>
|
|||||||
lock.applications.install_dirs.push(new_dir);
|
lock.applications.install_dirs.push(new_dir);
|
||||||
drop(lock);
|
drop(lock);
|
||||||
|
|
||||||
scan_install_dirs();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ use chrono::Utc;
|
|||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
use native_model::{Decode, Encode};
|
use native_model::{Decode, Encode};
|
||||||
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
use rustbreak::{DeSerError, DeSerializer, PathDatabase, RustbreakError};
|
||||||
use serde::{Serialize, de::DeserializeOwned};
|
use serde::{de::DeserializeOwned, Serialize};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::DB;
|
use crate::DB;
|
||||||
@ -67,18 +67,20 @@ impl DatabaseImpls for DatabaseInterface {
|
|||||||
|
|
||||||
let exists = fs::exists(db_path.clone()).unwrap();
|
let exists = fs::exists(db_path.clone()).unwrap();
|
||||||
|
|
||||||
if exists {
|
match exists {
|
||||||
match PathDatabase::load_from_path(db_path.clone()) {
|
true => match PathDatabase::load_from_path(db_path.clone()) {
|
||||||
Ok(db) => db,
|
Ok(db) => db,
|
||||||
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
Err(e) => handle_invalid_database(e, db_path, games_base_dir, cache_dir),
|
||||||
}
|
},
|
||||||
} else {
|
false => {
|
||||||
let default = Database::new(games_base_dir, None, cache_dir);
|
let default = Database::new(games_base_dir, None, cache_dir);
|
||||||
debug!(
|
debug!(
|
||||||
"Creating database at path {}",
|
"Creating database at path {}",
|
||||||
db_path.as_os_str().to_str().unwrap()
|
db_path.as_os_str().to_str().unwrap()
|
||||||
);
|
);
|
||||||
PathDatabase::create_at_path(db_path, default).expect("Database could not be created")
|
PathDatabase::create_at_path(db_path, default)
|
||||||
|
.expect("Database could not be created")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +124,14 @@ fn handle_invalid_database(
|
|||||||
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
pub struct DBRead<'a>(RwLockReadGuard<'a, Database>);
|
||||||
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
pub struct DBWrite<'a>(ManuallyDrop<RwLockWriteGuard<'a, Database>>);
|
||||||
impl<'a> Deref for DBWrite<'a> {
|
impl<'a> Deref for DBWrite<'a> {
|
||||||
type Target = Database;
|
type Target = RwLockWriteGuard<'a, Database>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'a> Deref for DBRead<'a> {
|
||||||
|
type Target = RwLockReadGuard<'a, Database>;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.0
|
&self.0
|
||||||
@ -133,21 +142,14 @@ impl<'a> DerefMut for DBWrite<'a> {
|
|||||||
&mut self.0
|
&mut self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<'a> Deref for DBRead<'a> {
|
impl<'a> Drop for DBWrite<'a> {
|
||||||
type Target = Database;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Drop for DBWrite<'_> {
|
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
ManuallyDrop::drop(&mut self.0);
|
ManuallyDrop::drop(&mut self.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
match DB.save() {
|
match DB.save() {
|
||||||
Ok(()) => {}
|
Ok(_) => {}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("database failed to save with error {e}");
|
error!("database failed to save with error {e}");
|
||||||
panic!("database failed to save with error {e}")
|
panic!("database failed to save with error {e}")
|
||||||
@ -155,7 +157,6 @@ impl Drop for DBWrite<'_> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
pub fn borrow_db_checked<'a>() -> DBRead<'a> {
|
||||||
match DB.borrow_data() {
|
match DB.borrow_data() {
|
||||||
Ok(data) => DBRead(data),
|
Ok(data) => DBRead(data),
|
||||||
|
|||||||
@ -2,4 +2,3 @@ pub mod commands;
|
|||||||
pub mod db;
|
pub mod db;
|
||||||
pub mod debug;
|
pub mod debug;
|
||||||
pub mod models;
|
pub mod models;
|
||||||
pub mod scan;
|
|
||||||
@ -1,14 +1,8 @@
|
|||||||
/**
|
use crate::database::models::data::Database;
|
||||||
* NEXT BREAKING CHANGE
|
|
||||||
*
|
|
||||||
* UPDATE DATABASE TO USE RPMSERDENAMED
|
|
||||||
*
|
|
||||||
* WE CAN'T DELETE ANY FIELDS
|
|
||||||
*/
|
|
||||||
pub mod data {
|
pub mod data {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
|
||||||
use native_model::native_model;
|
use native_model::native_model;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
@ -24,14 +18,16 @@ pub mod data {
|
|||||||
pub type DatabaseApplications = v2::DatabaseApplications;
|
pub type DatabaseApplications = v2::DatabaseApplications;
|
||||||
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
|
pub type DatabaseCompatInfo = v2::DatabaseCompatInfo;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::{collections::HashMap, process::Command};
|
||||||
|
|
||||||
|
use crate::process::process_manager::UMU_LAUNCHER_EXECUTABLE;
|
||||||
|
|
||||||
pub mod v1 {
|
pub mod v1 {
|
||||||
use crate::process::process_manager::Platform;
|
use crate::process::process_manager::Platform;
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
use std::{collections::HashMap, path::PathBuf};
|
use std::{collections::HashMap, path::PathBuf};
|
||||||
|
|
||||||
use super::{Deserialize, Serialize, native_model};
|
use super::*;
|
||||||
|
|
||||||
fn default_template() -> String {
|
fn default_template() -> String {
|
||||||
"{}".to_owned()
|
"{}".to_owned()
|
||||||
@ -119,7 +115,6 @@ pub mod data {
|
|||||||
Downloading { version_name: String },
|
Downloading { version_name: String },
|
||||||
Uninstalling {},
|
Uninstalling {},
|
||||||
Updating { version_name: String },
|
Updating { version_name: String },
|
||||||
Validating { version_name: String },
|
|
||||||
Running {},
|
Running {},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,10 +174,7 @@ pub mod data {
|
|||||||
|
|
||||||
use serde_with::serde_as;
|
use serde_with::serde_as;
|
||||||
|
|
||||||
use super::{
|
use super::*;
|
||||||
ApplicationTransientStatus, DatabaseAuth, Deserialize, DownloadableMetadata,
|
|
||||||
GameVersion, Serialize, Settings, native_model, v1,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
#[native_model(id = 1, version = 2, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
@ -214,7 +206,7 @@ pub mod data {
|
|||||||
applications: value.applications,
|
applications: value.applications,
|
||||||
prev_database: value.prev_database,
|
prev_database: value.prev_database,
|
||||||
cache_dir: value.cache_dir,
|
cache_dir: value.cache_dir,
|
||||||
compat_info: None,
|
compat_info: crate::database::models::Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -291,10 +283,7 @@ pub mod data {
|
|||||||
mod v3 {
|
mod v3 {
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::{
|
use super::*;
|
||||||
DatabaseApplications, DatabaseAuth, DatabaseCompatInfo, Deserialize, Serialize,
|
|
||||||
Settings, native_model, v2,
|
|
||||||
};
|
|
||||||
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
|
#[native_model(id = 1, version = 3, with = native_model::rmp_serde_1_3::RmpSerde)]
|
||||||
#[derive(Serialize, Deserialize, Clone, Default)]
|
#[derive(Serialize, Deserialize, Clone, Default)]
|
||||||
pub struct Database {
|
pub struct Database {
|
||||||
@ -308,7 +297,6 @@ pub mod data {
|
|||||||
pub cache_dir: PathBuf,
|
pub cache_dir: PathBuf,
|
||||||
pub compat_info: Option<DatabaseCompatInfo>,
|
pub compat_info: Option<DatabaseCompatInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<v2::Database> for Database {
|
impl From<v2::Database> for Database {
|
||||||
fn from(value: v2::Database) -> Self {
|
fn from(value: v2::Database) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@ -318,13 +306,22 @@ pub mod data {
|
|||||||
applications: value.applications.into(),
|
applications: value.applications.into(),
|
||||||
prev_database: value.prev_database,
|
prev_database: value.prev_database,
|
||||||
cache_dir: value.cache_dir,
|
cache_dir: value.cache_dir,
|
||||||
compat_info: None,
|
compat_info: Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
impl Database {
|
||||||
|
fn create_new_compat_info() -> Option<DatabaseCompatInfo> {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
return None;
|
||||||
|
|
||||||
|
let has_umu_installed = Command::new(UMU_LAUNCHER_EXECUTABLE).spawn().is_ok();
|
||||||
|
Some(DatabaseCompatInfo {
|
||||||
|
umu_installed: has_umu_installed,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
impl Database {
|
|
||||||
pub fn new<T: Into<PathBuf>>(
|
pub fn new<T: Into<PathBuf>>(
|
||||||
games_base_dir: T,
|
games_base_dir: T,
|
||||||
prev_database: Option<PathBuf>,
|
prev_database: Option<PathBuf>,
|
||||||
@ -339,11 +336,11 @@ pub mod data {
|
|||||||
transient_statuses: HashMap::new(),
|
transient_statuses: HashMap::new(),
|
||||||
},
|
},
|
||||||
prev_database,
|
prev_database,
|
||||||
base_url: String::new(),
|
base_url: "".to_owned(),
|
||||||
auth: None,
|
auth: None,
|
||||||
settings: Settings::default(),
|
settings: Settings::default(),
|
||||||
cache_dir,
|
cache_dir,
|
||||||
compat_info: None,
|
compat_info: Database::create_new_compat_info(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,52 +0,0 @@
|
|||||||
use std::fs;
|
|
||||||
|
|
||||||
use log::warn;
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
database::{
|
|
||||||
db::borrow_db_mut_checked,
|
|
||||||
models::data::v1::{DownloadType, DownloadableMetadata},
|
|
||||||
},
|
|
||||||
games::{
|
|
||||||
downloads::drop_data::{v1::DropData, DROP_DATA_PATH},
|
|
||||||
library::set_partially_installed_db,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn scan_install_dirs() {
|
|
||||||
let mut db_lock = borrow_db_mut_checked();
|
|
||||||
for install_dir in db_lock.applications.install_dirs.clone() {
|
|
||||||
let Ok(files) = fs::read_dir(install_dir) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
for game in files.into_iter().flatten() {
|
|
||||||
let drop_data_file = game.path().join(DROP_DATA_PATH);
|
|
||||||
if !drop_data_file.exists() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let game_id = game.file_name().into_string().unwrap();
|
|
||||||
let Ok(drop_data) = DropData::read(&game.path()) else {
|
|
||||||
warn!(
|
|
||||||
".dropdata exists for {}, but couldn't read it. is it corrupted?",
|
|
||||||
game.file_name().into_string().unwrap()
|
|
||||||
);
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if db_lock.applications.game_statuses.contains_key(&game_id) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let metadata = DownloadableMetadata::new(
|
|
||||||
drop_data.game_id,
|
|
||||||
Some(drop_data.game_version),
|
|
||||||
DownloadType::Game,
|
|
||||||
);
|
|
||||||
set_partially_installed_db(
|
|
||||||
&mut db_lock,
|
|
||||||
&metadata,
|
|
||||||
drop_data.base_path.to_str().unwrap().to_string(),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -4,12 +4,12 @@ use crate::{database::models::data::DownloadableMetadata, AppState};
|
|||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn pause_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.pause_downloads();
|
state.lock().unwrap().download_manager.pause_downloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
pub fn resume_downloads(state: tauri::State<'_, Mutex<AppState>>) {
|
||||||
state.lock().unwrap().download_manager.resume_downloads();
|
state.lock().unwrap().download_manager.resume_downloads()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
@ -22,10 +22,10 @@ pub fn move_download_in_queue(
|
|||||||
.lock()
|
.lock()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.download_manager
|
.download_manager
|
||||||
.rearrange(old_index, new_index);
|
.rearrange(old_index, new_index)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
pub fn cancel_game(state: tauri::State<'_, Mutex<AppState>>, meta: DownloadableMetadata) {
|
||||||
state.lock().unwrap().download_manager.cancel(meta);
|
state.lock().unwrap().download_manager.cancel(meta)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
sync::{
|
sync::{
|
||||||
|
mpsc::{channel, Receiver, Sender},
|
||||||
Arc, Mutex,
|
Arc, Mutex,
|
||||||
mpsc::{Receiver, Sender, channel},
|
|
||||||
},
|
},
|
||||||
thread::{JoinHandle, spawn},
|
thread::{spawn, JoinHandle},
|
||||||
};
|
};
|
||||||
|
|
||||||
use log::{debug, error, info, warn};
|
use log::{debug, error, info, warn};
|
||||||
@ -176,7 +176,7 @@ impl DownloadManagerBuilder {
|
|||||||
DownloadManagerSignal::Cancel(meta) => {
|
DownloadManagerSignal::Cancel(meta) => {
|
||||||
self.manage_cancel_signal(&meta);
|
self.manage_cancel_signal(&meta);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
fn manage_queue_signal(&mut self, download_agent: DownloadAgent) {
|
||||||
@ -242,26 +242,20 @@ impl DownloadManagerBuilder {
|
|||||||
let app_handle = self.app_handle.clone();
|
let app_handle = self.app_handle.clone();
|
||||||
|
|
||||||
*download_thread_lock = Some(spawn(move || {
|
*download_thread_lock = Some(spawn(move || {
|
||||||
loop {
|
match download_agent.download(&app_handle) {
|
||||||
let download_result = match download_agent.download(&app_handle) {
|
|
||||||
// Ok(true) is for completed and exited properly
|
// Ok(true) is for completed and exited properly
|
||||||
Ok(v) => v,
|
Ok(true) => {
|
||||||
Err(e) => {
|
debug!("download {:?} has completed", download_agent.metadata());
|
||||||
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
match download_agent.validate() {
|
||||||
download_agent.on_error(&app_handle, &e);
|
Ok(true) => {
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
download_agent.on_complete(&app_handle);
|
||||||
return;
|
sender
|
||||||
|
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
};
|
Ok(false) => {
|
||||||
|
download_agent.on_incomplete(&app_handle);
|
||||||
// If the download gets cancelled
|
|
||||||
// immediately return, on_cancelled gets called for us earlier
|
|
||||||
if !download_result {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let validate_result = match download_agent.validate(&app_handle) {
|
|
||||||
Ok(v) => v,
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!(
|
error!(
|
||||||
"download {:?} has validation error {}",
|
"download {:?} has validation error {}",
|
||||||
@ -270,19 +264,21 @@ impl DownloadManagerBuilder {
|
|||||||
);
|
);
|
||||||
download_agent.on_error(&app_handle, &e);
|
download_agent.on_error(&app_handle, &e);
|
||||||
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
}
|
||||||
if validate_result {
|
// Ok(false) is for incomplete but exited properly
|
||||||
download_agent.on_complete(&app_handle);
|
Ok(false) => {
|
||||||
sender
|
debug!("Donwload agent finished incomplete");
|
||||||
.send(DownloadManagerSignal::Completed(download_agent.metadata()))
|
download_agent.on_incomplete(&app_handle);
|
||||||
.unwrap();
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("download {:?} has error {}", download_agent.metadata(), &e);
|
||||||
|
download_agent.on_error(&app_handle, &e);
|
||||||
|
sender.send(DownloadManagerSignal::Error(e)).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
sender.send(DownloadManagerSignal::UpdateUIQueue).unwrap();
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
self.set_status(DownloadManagerStatus::Downloading);
|
self.set_status(DownloadManagerStatus::Downloading);
|
||||||
@ -299,12 +295,11 @@ impl DownloadManagerBuilder {
|
|||||||
}
|
}
|
||||||
fn manage_completed_signal(&mut self, meta: DownloadableMetadata) {
|
fn manage_completed_signal(&mut self, meta: DownloadableMetadata) {
|
||||||
debug!("got signal Completed");
|
debug!("got signal Completed");
|
||||||
if let Some(interface) = &self.current_download_agent
|
if let Some(interface) = &self.current_download_agent {
|
||||||
&& interface.metadata() == meta
|
if interface.metadata() == meta {
|
||||||
{
|
|
||||||
self.remove_and_cleanup_front_download(&meta);
|
self.remove_and_cleanup_front_download(&meta);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
self.push_ui_queue_update();
|
self.push_ui_queue_update();
|
||||||
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
self.sender.send(DownloadManagerSignal::Go).unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,13 +23,13 @@ use super::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
pub enum DownloadManagerSignal {
|
pub enum DownloadManagerSignal {
|
||||||
/// Resumes (or starts) the `DownloadManager`
|
/// Resumes (or starts) the DownloadManager
|
||||||
Go,
|
Go,
|
||||||
/// Pauses the `DownloadManager`
|
/// Pauses the DownloadManager
|
||||||
Stop,
|
Stop,
|
||||||
/// Called when a `DownloadAgent` has fully completed a download.
|
/// Called when a DownloadAgent has fully completed a download.
|
||||||
Completed(DownloadableMetadata),
|
Completed(DownloadableMetadata),
|
||||||
/// Generates and appends a `DownloadAgent`
|
/// Generates and appends a DownloadAgent
|
||||||
/// to the registry and queue
|
/// to the registry and queue
|
||||||
Queue(DownloadAgent),
|
Queue(DownloadAgent),
|
||||||
/// Tells the Manager to stop the current
|
/// Tells the Manager to stop the current
|
||||||
@ -70,14 +70,14 @@ pub enum DownloadStatus {
|
|||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accessible front-end for the `DownloadManager`
|
/// Accessible front-end for the DownloadManager
|
||||||
///
|
///
|
||||||
/// The system works entirely through signals, both internally and externally,
|
/// The system works entirely through signals, both internally and externally,
|
||||||
/// all of which are accessible through the `DownloadManagerSignal` type, but
|
/// all of which are accessible through the DownloadManagerSignal type, but
|
||||||
/// should not be used directly. Rather, signals are abstracted through this
|
/// should not be used directly. Rather, signals are abstracted through this
|
||||||
/// interface.
|
/// interface.
|
||||||
///
|
///
|
||||||
/// The actual download queue may be accessed through the .`edit()` function,
|
/// The actual download queue may be accessed through the .edit() function,
|
||||||
/// which provides raw access to the underlying queue.
|
/// which provides raw access to the underlying queue.
|
||||||
/// THIS EDITING IS BLOCKING!!!
|
/// THIS EDITING IS BLOCKING!!!
|
||||||
pub struct DownloadManager {
|
pub struct DownloadManager {
|
||||||
@ -139,7 +139,7 @@ impl DownloadManager {
|
|||||||
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
pub fn rearrange(&self, current_index: usize, new_index: usize) {
|
||||||
if current_index == new_index {
|
if current_index == new_index {
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let needs_pause = current_index == 0 || new_index == 0;
|
let needs_pause = current_index == 0 || new_index == 0;
|
||||||
if needs_pause {
|
if needs_pause {
|
||||||
@ -148,7 +148,9 @@ impl DownloadManager {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!("moving download at index {current_index} to index {new_index}");
|
debug!(
|
||||||
|
"moving download at index {current_index} to index {new_index}"
|
||||||
|
);
|
||||||
|
|
||||||
let mut queue = self.edit();
|
let mut queue = self.edit();
|
||||||
let to_move = queue.remove(current_index).unwrap();
|
let to_move = queue.remove(current_index).unwrap();
|
||||||
@ -183,7 +185,7 @@ impl DownloadManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes in the locked value from .`edit()` and attempts to
|
/// Takes in the locked value from .edit() and attempts to
|
||||||
/// get the index of whatever id is passed in
|
/// get the index of whatever id is passed in
|
||||||
fn get_index_from_id(
|
fn get_index_from_id(
|
||||||
queue: &mut MutexGuard<'_, VecDeque<DownloadableMetadata>>,
|
queue: &mut MutexGuard<'_, VecDeque<DownloadableMetadata>>,
|
||||||
|
|||||||
@ -14,14 +14,14 @@ use super::{
|
|||||||
|
|
||||||
pub trait Downloadable: Send + Sync {
|
pub trait Downloadable: Send + Sync {
|
||||||
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
fn download(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
||||||
fn validate(&self, app_handle: &AppHandle) -> Result<bool, ApplicationDownloadError>;
|
|
||||||
|
|
||||||
fn progress(&self) -> Arc<ProgressObject>;
|
fn progress(&self) -> Arc<ProgressObject>;
|
||||||
fn control_flag(&self) -> DownloadThreadControl;
|
fn control_flag(&self) -> DownloadThreadControl;
|
||||||
|
fn validate(&self) -> Result<bool, ApplicationDownloadError>;
|
||||||
fn status(&self) -> DownloadStatus;
|
fn status(&self) -> DownloadStatus;
|
||||||
fn metadata(&self) -> DownloadableMetadata;
|
fn metadata(&self) -> DownloadableMetadata;
|
||||||
fn on_initialised(&self, app_handle: &AppHandle);
|
fn on_initialised(&self, app_handle: &AppHandle);
|
||||||
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
fn on_error(&self, app_handle: &AppHandle, error: &ApplicationDownloadError);
|
||||||
fn on_complete(&self, app_handle: &AppHandle);
|
fn on_complete(&self, app_handle: &AppHandle);
|
||||||
|
fn on_incomplete(&self, app_handle: &AppHandle);
|
||||||
fn on_cancelled(&self, app_handle: &AppHandle);
|
fn on_cancelled(&self, app_handle: &AppHandle);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
pub mod commands;
|
pub mod commands;
|
||||||
pub mod download_manager_builder;
|
|
||||||
pub mod download_manager_frontend;
|
pub mod download_manager_frontend;
|
||||||
|
pub mod download_manager_builder;
|
||||||
pub mod downloadable;
|
pub mod downloadable;
|
||||||
pub mod util;
|
pub mod util;
|
||||||
|
|||||||
@ -22,7 +22,10 @@ impl From<DownloadThreadControlFlag> for bool {
|
|||||||
/// false => Stop
|
/// false => Stop
|
||||||
impl From<bool> for DownloadThreadControlFlag {
|
impl From<bool> for DownloadThreadControlFlag {
|
||||||
fn from(value: bool) -> Self {
|
fn from(value: bool) -> Self {
|
||||||
if value { DownloadThreadControlFlag::Go } else { DownloadThreadControlFlag::Stop }
|
match value {
|
||||||
|
true => DownloadThreadControlFlag::Go,
|
||||||
|
false => DownloadThreadControlFlag::Stop,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,9 +41,9 @@ impl DownloadThreadControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn get(&self) -> DownloadThreadControlFlag {
|
pub fn get(&self) -> DownloadThreadControlFlag {
|
||||||
self.inner.load(Ordering::Acquire).into()
|
self.inner.load(Ordering::Relaxed).into()
|
||||||
}
|
}
|
||||||
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
pub fn set(&self, flag: DownloadThreadControlFlag) {
|
||||||
self.inner.store(flag.into(), Ordering::Release);
|
self.inner.store(flag.into(), Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user