mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-13 00:02:41 +10:00
Compare commits
66 Commits
cloud-save
...
v0.3.2-dl
| Author | SHA1 | Date | |
|---|---|---|---|
| 124d51bced | |||
| 3b830e2a44 | |||
| 75a4b73ee1 | |||
| 339d707092 | |||
| 776dc8fe7a | |||
| dbe8c8df4d | |||
| 35f49b8811 | |||
| cc5339a389 | |||
| 6104bfda72 | |||
| be688cb18f | |||
| 13cc69f10e | |||
| 574782f445 | |||
| b5a8543194 | |||
| d0e4aea5ce | |||
| 739e6166c5 | |||
| 682c6e9c0b | |||
| 46e1f16cdd | |||
| d19f9bbc31 | |||
| 2913fdf35b | |||
| f9fdf151ea | |||
| 495d93705e | |||
| c477dd4872 | |||
| f560a62c8f | |||
| 2874b9776b | |||
| afcd4e916f | |||
| 885fa42ecc | |||
| 6d295bd47f | |||
| c3ee09af85 | |||
| 0ce55e12c5 | |||
| 86bce1c68d | |||
| 924e4e334c | |||
| 065eb2356a | |||
| 689e9ad890 | |||
| 7c35ed73aa | |||
| 8f261a5dac | |||
| 67b6f2aa2e | |||
| 11e2b3fe8a | |||
| eba224f998 | |||
| d045385a5d | |||
| d878806ade | |||
| b71081006e | |||
| c9e1ed78eb | |||
| 446aa70b0b | |||
| 1d0b81078a | |||
| 5251a56c3c | |||
| eeca8a7a98 | |||
| 365cdaf311 | |||
| 2957773179 | |||
| 15e5fe4dc0 | |||
| 2dc0a78354 | |||
| 51c480f245 | |||
| 95d223e2b2 | |||
| 790e8c2afe | |||
| 02edb2cbc1 | |||
| 4b4c0734ec | |||
| e75e0044fb | |||
| 65561abdab | |||
| fed3e08dce | |||
| b0b1e397b1 | |||
| 7b443818d1 | |||
| fa4a881cc0 | |||
| 4f16a6e6b2 | |||
| 47d9e9949b | |||
| a643d6102b | |||
| a71ff160c2 | |||
| a53a566792 |
23
.github/workflows/clippy.yml
vendored
Normal file
23
.github/workflows/clippy.yml
vendored
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
on: push
|
||||||
|
name: Clippy check
|
||||||
|
jobs:
|
||||||
|
clippy_check:
|
||||||
|
runs-on: ubuntu-24.04
|
||||||
|
permissions:
|
||||||
|
checks: write
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v1
|
||||||
|
- name: install dependencies (ubuntu only)
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get install -y libglib2.0-dev libgtk-3-dev libwebkit2gtk-4.1-dev
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly
|
||||||
|
components: clippy
|
||||||
|
override: true
|
||||||
|
- uses: actions-rs/clippy-check@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
args: --manifest-path ./src-tauri/Cargo.toml
|
||||||
104
.github/workflows/release.yml
vendored
Normal file
104
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
name: 'publish'
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch: {}
|
||||||
|
release:
|
||||||
|
types: [published]
|
||||||
|
# This can be used to automatically publish nightlies at UTC nighttime
|
||||||
|
# schedule:
|
||||||
|
# - cron: "0 2 * * *" # run at 2 AM UTC
|
||||||
|
|
||||||
|
# This workflow will trigger on each push to the `release` branch to create or update a GitHub release, build your app, and upload the artifacts to the release.
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish-tauri:
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- platform: 'macos-latest' # for Arm based macs (M1 and above).
|
||||||
|
args: '--target aarch64-apple-darwin'
|
||||||
|
- platform: 'macos-latest' # for Intel based macs.
|
||||||
|
args: '--target x86_64-apple-darwin'
|
||||||
|
- platform: 'ubuntu-22.04' # for Tauri v1 you could replace this with ubuntu-20.04.
|
||||||
|
args: ''
|
||||||
|
- platform: 'ubuntu-22.04-arm'
|
||||||
|
args: '--target aarch64-unknown-linux-gnu'
|
||||||
|
- platform: 'windows-latest'
|
||||||
|
args: ''
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.platform }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
submodules: true
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- name: setup node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: lts/*
|
||||||
|
|
||||||
|
- name: install Rust nightly
|
||||||
|
uses: dtolnay/rust-toolchain@nightly
|
||||||
|
with:
|
||||||
|
# Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds.
|
||||||
|
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
|
||||||
|
|
||||||
|
- name: install dependencies (ubuntu only)
|
||||||
|
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
|
||||||
|
# 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
|
||||||
|
run: yarn install # change this to npm, pnpm or bun depending on which one you use.
|
||||||
|
|
||||||
|
- uses: tauri-apps/tauri-action@v0
|
||||||
|
env:
|
||||||
|
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:
|
||||||
|
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 }}
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@ -26,4 +26,7 @@ dist-ssr
|
|||||||
.output
|
.output
|
||||||
|
|
||||||
src-tauri/flamegraph.svg
|
src-tauri/flamegraph.svg
|
||||||
src-tauri/perf*
|
src-tauri/perf*
|
||||||
|
|
||||||
|
/*.AppImage
|
||||||
|
/squashfs-root
|
||||||
9
.gitmodules
vendored
9
.gitmodules
vendored
@ -1,3 +1,6 @@
|
|||||||
[submodule "drop-base"]
|
[submodule "src-tauri/tailscale/libtailscale"]
|
||||||
path = drop-base
|
path = src-tauri/tailscale/libtailscale
|
||||||
url = https://github.com/drop-oss/drop-base
|
url = https://github.com/tailscale/libtailscale.git
|
||||||
|
[submodule "libs/drop-base"]
|
||||||
|
path = libs/drop-base
|
||||||
|
url = https://github.com/drop-oss/drop-base.git
|
||||||
|
|||||||
@ -4,7 +4,7 @@ Drop app is the companion app for [Drop](https://github.com/Drop-OSS/drop). It u
|
|||||||
|
|
||||||
## Running
|
## Running
|
||||||
Before setting up the drop app, be sure that you have a server set up.
|
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 Wiki](https://wiki.droposs.org/guides/quickstart.html)
|
The instructions for this can be found on the [Drop Docs](https://docs.droposs.org/docs/guides/quickstart)
|
||||||
|
|
||||||
## Current features
|
## Current features
|
||||||
Currently supported are the following features:
|
Currently supported are the following features:
|
||||||
|
|||||||
48
build.mjs
Normal file
48
build.mjs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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,5 +0,0 @@
|
|||||||
<template>
|
|
||||||
<button class="transition h-10 w-10 text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100 p-2">
|
|
||||||
<slot />
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
import type { AppState } from "~/types";
|
|
||||||
|
|
||||||
export const useAppState = () => useState<AppState>("state");
|
|
||||||
Submodule drop-base deleted from 26698e5b06
1
libs/drop-base
Submodule
1
libs/drop-base
Submodule
Submodule libs/drop-base added at 04125e89be
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<LoadingIndicator />
|
||||||
<NuxtLayout class="select-none w-screen h-screen">
|
<NuxtLayout class="select-none w-screen h-screen">
|
||||||
<NuxtPage />
|
<NuxtPage />
|
||||||
<ModalStack />
|
<ModalStack />
|
||||||
@ -9,8 +10,6 @@
|
|||||||
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,
|
||||||
@ -20,18 +19,26 @@ import {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
const state = useAppState();
|
const state = useAppState();
|
||||||
try {
|
|
||||||
state.value = JSON.parse(await invoke("fetch_state"));
|
|
||||||
} catch (e) {
|
|
||||||
console.error("failed to parse state", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
router.beforeEach(async () => {
|
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 () => {
|
||||||
|
await fetchState();
|
||||||
});
|
});
|
||||||
|
|
||||||
setupHooks();
|
setupHooks();
|
||||||
|
Before Width: | Height: | Size: 6.5 MiB After Width: | Height: | Size: 6.5 MiB |
@ -46,7 +46,7 @@
|
|||||||
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-slot="{ active }">
|
<MenuItem v-if="showOptions" v-slot="{ active }">
|
||||||
<button
|
<button
|
||||||
@click="() => emit('options')"
|
@click="() => emit('options')"
|
||||||
:class="[
|
:class="[
|
||||||
@ -87,6 +87,8 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QueueListIcon,
|
QueueListIcon,
|
||||||
|
ServerIcon,
|
||||||
|
StopIcon,
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
|
|
||||||
@ -103,12 +105,18 @@ const emit = defineEmits<{
|
|||||||
(e: "uninstall"): void;
|
(e: "uninstall"): void;
|
||||||
(e: "kill"): void;
|
(e: "kill"): void;
|
||||||
(e: "options"): void;
|
(e: "options"): void;
|
||||||
|
(e: "resume"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showDropdown = computed(
|
const showDropdown = computed(
|
||||||
() =>
|
() =>
|
||||||
props.status.type === GameStatusEnum.Installed ||
|
props.status.type === GameStatusEnum.Installed ||
|
||||||
props.status.type === GameStatusEnum.SetupRequired
|
props.status.type === GameStatusEnum.SetupRequired ||
|
||||||
|
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 } = {
|
||||||
@ -118,6 +126,8 @@ 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]:
|
||||||
@ -128,38 +138,46 @@ 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.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]:
|
||||||
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-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",
|
||||||
};
|
};
|
||||||
|
|
||||||
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]: PlayIcon,
|
[GameStatusEnum.Running]: StopIcon,
|
||||||
|
[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"),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -37,12 +37,12 @@
|
|||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
<WindowControl class="h-16 w-16 p-4" />
|
<WindowControl />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
5
main/components/HeaderButton.vue
Normal file
5
main/components/HeaderButton.vue
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<template>
|
||||||
|
<button class="transition h-full aspect-square text-zinc-300 hover:bg-zinc-800 hover:text-zinc-100 p-[1.1rem]">
|
||||||
|
<slot />
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
@ -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"
|
||||||
>
|
>
|
||||||
<PanelWidget class="flex-col gap-y-2">
|
<div 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>
|
||||||
</PanelWidget>
|
</div>
|
||||||
</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?.profilePicture ?? ""
|
state.value?.user?.profilePictureObjectId ?? ""
|
||||||
);
|
);
|
||||||
const adminUrl: string = await invoke("gen_drop_url", {
|
const adminUrl: string = await invoke("gen_drop_url", {
|
||||||
path: "/admin",
|
path: "/admin",
|
||||||
@ -99,11 +99,6 @@ function navigate(close: () => any, to: NavigationItem) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const navigation: NavigationItem[] = [
|
const navigation: NavigationItem[] = [
|
||||||
{
|
|
||||||
label: "Account settings",
|
|
||||||
route: "/account",
|
|
||||||
prefix: "",
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: "App settings",
|
label: "App settings",
|
||||||
route: "/settings",
|
route: "/settings",
|
||||||
@ -13,11 +13,7 @@
|
|||||||
<div class="max-w-lg">
|
<div class="max-w-lg">
|
||||||
<slot />
|
<slot />
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<button
|
<div>
|
||||||
@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"
|
||||||
@ -37,10 +33,19 @@
|
|||||||
</svg>
|
</svg>
|
||||||
<span class="sr-only">Loading...</span>
|
<span class="sr-only">Loading...</span>
|
||||||
</div>
|
</div>
|
||||||
<span v-else>
|
<span class="inline-flex gap-x-8 items-center" v-else>
|
||||||
Sign in with your browser <span aria-hidden="true">→</span>
|
<button
|
||||||
|
@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>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
@ -121,6 +126,7 @@
|
|||||||
|
|
||||||
<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);
|
||||||
@ -136,15 +142,16 @@ async function auth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function authWrapper_wrapper() {
|
function authWrapper_wrapper() {
|
||||||
|
error.value = undefined;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
auth().catch((e) => {
|
auth().catch((e) => {
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
error.value = e;
|
error.value = e;
|
||||||
if(offerManualTimeout) clearTimeout(offerManualTimeout);
|
if (offerManualTimeout) clearTimeout(offerManualTimeout);
|
||||||
});
|
});
|
||||||
offerManualTimeout = setTimeout(() => {
|
offerManualTimeout = setTimeout(() => {
|
||||||
offerManual.value = true;
|
offerManual.value = true;
|
||||||
}, 10000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function continueManual() {
|
async function continueManual() {
|
||||||
@ -1,19 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div class="mb-3 inline-flex gap-x-2">
|
||||||
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="relative transition-transform duration-300 hover:scale-105 active:scale-95"
|
||||||
>
|
>
|
||||||
<MagnifyingGlassIcon class="h-5 w-5 text-zinc-400" aria-hidden="true" />
|
<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>
|
</div>
|
||||||
<input
|
<button
|
||||||
type="text"
|
@click="() => calculateGames(true)"
|
||||||
v-model="searchQuery"
|
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"
|
||||||
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..."
|
<ArrowPathIcon class="size-4" />
|
||||||
/>
|
</button>
|
||||||
</div>
|
</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">
|
||||||
@ -60,7 +71,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MagnifyingGlassIcon } from "@heroicons/vue/20/solid";
|
import { ArrowPathIcon, 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";
|
||||||
@ -70,22 +81,26 @@ 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",
|
||||||
};
|
};
|
||||||
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",
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -99,16 +114,20 @@ const icons: { [key: string]: string } = {};
|
|||||||
|
|
||||||
const rawGames: Ref<Game[], Game[]> = ref([]);
|
const rawGames: Ref<Game[], Game[]> = ref([]);
|
||||||
|
|
||||||
async function calculateGames() {
|
async function calculateGames(clearAll = false) {
|
||||||
rawGames.value = await invoke("fetch_library");
|
if (clearAll) rawGames.value = [];
|
||||||
for (const game of 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;
|
if (games[game.id]) continue;
|
||||||
games[game.id] = await useGame(game.id);
|
games[game.id] = await useGame(game.id);
|
||||||
}
|
}
|
||||||
for (const game of rawGames.value) {
|
for (const game of newGames) {
|
||||||
if (icons[game.id]) continue;
|
if (icons[game.id]) continue;
|
||||||
icons[game.id] = await useObject(game.mIconId);
|
icons[game.id] = await useObject(game.mIconObjectId);
|
||||||
}
|
}
|
||||||
|
rawGames.value = newGames;
|
||||||
}
|
}
|
||||||
|
|
||||||
await calculateGames();
|
await calculateGames();
|
||||||
7
main/components/LoadingIndicator.vue
Normal file
7
main/components/LoadingIndicator.vue
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
const loading = useLoadingIndicator();
|
||||||
|
|
||||||
|
watch(loading.isLoading, console.log);
|
||||||
|
</script>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="h-10 cursor-pointer flex flex-row items-center justify-between bg-zinc-950"
|
class="h-16 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" />
|
||||||
@ -1,4 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<HeaderButton v-if="showMinimise" @click="() => minimise()">
|
||||||
|
<MinusIcon />
|
||||||
|
</HeaderButton>
|
||||||
<HeaderButton @click="() => close()">
|
<HeaderButton @click="() => close()">
|
||||||
<XMarkIcon />
|
<XMarkIcon />
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
@ -8,11 +11,14 @@
|
|||||||
import { MinusIcon, XMarkIcon } from "@heroicons/vue/16/solid";
|
import { MinusIcon, XMarkIcon } from "@heroicons/vue/16/solid";
|
||||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||||
|
|
||||||
async function close(){
|
const window = getCurrentWindow();
|
||||||
console.log(window);
|
const showMinimise = await window.isMinimizable();
|
||||||
const result = await window.close();
|
|
||||||
console.log(`closed window: ${result}`);
|
async function close() {
|
||||||
|
await window.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
const window = getCurrentWindow();
|
async function minimise() {
|
||||||
|
await window.minimize();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
3
main/composables/app-state.ts
Normal file
3
main/composables/app-state.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { AppState } from "~/types";
|
||||||
|
|
||||||
|
export const useAppState = () => useState<AppState | undefined>("state");
|
||||||
@ -14,7 +14,6 @@ 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,
|
||||||
@ -48,7 +47,6 @@ 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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1,9 +1,11 @@
|
|||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import { data } from "autoprefixer";
|
import { data } from "autoprefixer";
|
||||||
import { AppStatus, type AppState } from "~/types";
|
import { AppStatus, type AppState } from "~/types";
|
||||||
|
|
||||||
export function setupHooks() {
|
export function setupHooks() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const state = useAppState();
|
||||||
|
|
||||||
listen("auth/processing", (event) => {
|
listen("auth/processing", (event) => {
|
||||||
router.push("/auth/processing");
|
router.push("/auth/processing");
|
||||||
@ -15,8 +17,9 @@ export function setupHooks() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
listen("auth/finished", (event) => {
|
listen("auth/finished", async (event) => {
|
||||||
router.push("/store");
|
router.push("/library");
|
||||||
|
state.value = JSON.parse(await invoke("fetch_state"));
|
||||||
});
|
});
|
||||||
|
|
||||||
listen("download_error", (event) => {
|
listen("download_error", (event) => {
|
||||||
@ -27,12 +30,31 @@ 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) => {
|
||||||
@ -43,7 +65,13 @@ export function setupHooks() {
|
|||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialNavigation(state: Ref<AppState>) {
|
export function initialNavigation(state: ReturnType<typeof useAppState>) {
|
||||||
|
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) {
|
||||||
@ -60,6 +88,6 @@ export function initialNavigation(state: Ref<AppState>) {
|
|||||||
router.push("/error/serverunavailable");
|
router.push("/error/serverunavailable");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
router.push("/store");
|
router.push("/library");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -7,6 +7,7 @@
|
|||||||
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"
|
||||||
@ -13,5 +13,9 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
extends: [["./drop-base"]],
|
extends: [["../libs/drop-base"]],
|
||||||
|
|
||||||
|
app: {
|
||||||
|
baseURL: "/main",
|
||||||
|
}
|
||||||
});
|
});
|
||||||
37
main/package.json
Normal file
37
main/package.json
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"name": "view",
|
||||||
|
"private": true,
|
||||||
|
"version": "0.3.2-dl",
|
||||||
|
"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"
|
||||||
|
}
|
||||||
37
main/pages/auth/code.vue
Normal file
37
main/pages/auth/code.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<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>
|
||||||
@ -32,6 +32,7 @@
|
|||||||
@uninstall="() => uninstall()"
|
@uninstall="() => uninstall()"
|
||||||
@kill="() => kill()"
|
@kill="() => kill()"
|
||||||
@options="() => (configureModalOpen = true)"
|
@options="() => (configureModalOpen = true)"
|
||||||
|
@resume="() => resumeDownload()"
|
||||||
:status="status"
|
:status="status"
|
||||||
/>
|
/>
|
||||||
<a
|
<a
|
||||||
@ -75,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="url"
|
:key="index"
|
||||||
: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"
|
||||||
@ -156,9 +157,9 @@
|
|||||||
<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">
|
||||||
<DialogTitle as="h3" class="text-base font-semibold text-zinc-100"
|
<h3 class="text-base font-semibold text-zinc-100">
|
||||||
>Install {{ game.mName }}?
|
Install {{ game.mName }}?
|
||||||
</DialogTitle>
|
</h3>
|
||||||
<div class="mt-2">
|
<div class="mt-2">
|
||||||
<p class="text-sm text-zinc-400">
|
<p class="text-sm text-zinc-400">
|
||||||
Drop will add {{ game.mName }} to the queue to be downloaded.
|
Drop will add {{ game.mName }} to the queue to be downloaded.
|
||||||
@ -349,9 +350,7 @@
|
|||||||
<template #buttons>
|
<template #buttons>
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
@click="() => install()"
|
@click="() => install()"
|
||||||
:disabled="
|
:disabled="!(versionOptions && versionOptions.length > 0)"
|
||||||
!(versionOptions && versionOptions.length > 0 && !installDir)
|
|
||||||
"
|
|
||||||
:loading="installLoading"
|
:loading="installLoading"
|
||||||
type="submit"
|
type="submit"
|
||||||
class="ml-2 w-full sm:w-fit"
|
class="ml-2 w-full sm:w-fit"
|
||||||
@ -369,7 +368,18 @@
|
|||||||
</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"
|
||||||
@ -419,7 +429,7 @@
|
|||||||
<img
|
<img
|
||||||
v-for="(url, index) in mediaUrls"
|
v-for="(url, index) in mediaUrls"
|
||||||
v-show="currentImageIndex === index"
|
v-show="currentImageIndex === index"
|
||||||
:key="url"
|
:key="index"
|
||||||
: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}`"
|
||||||
@ -440,10 +450,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import {
|
||||||
Dialog,
|
|
||||||
DialogTitle,
|
|
||||||
TransitionChild,
|
|
||||||
TransitionRoot,
|
|
||||||
Listbox,
|
Listbox,
|
||||||
ListboxButton,
|
ListboxButton,
|
||||||
ListboxLabel,
|
ListboxLabel,
|
||||||
@ -477,11 +483,14 @@ const remoteUrl: string = await invoke("gen_drop_url", {
|
|||||||
path: `/store/${game.value.id}`,
|
path: `/store/${game.value.id}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
const bannerUrl = await useObject(game.value.mBannerId);
|
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.mImageCarousel.map((id) => useObject(id))
|
game.value.mImageCarouselObjectIds.map(async (v) => {
|
||||||
|
const src = await useObject(v);
|
||||||
|
return src;
|
||||||
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
const htmlDescription = micromark(game.value.mDescription);
|
const htmlDescription = micromark(game.value.mDescription);
|
||||||
@ -532,6 +541,14 @@ async function install() {
|
|||||||
installLoading.value = false;
|
installLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function resumeDownload() {
|
||||||
|
try {
|
||||||
|
await invoke("resume_download", { gameId: game.value.id });
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function launch() {
|
async function launch() {
|
||||||
try {
|
try {
|
||||||
await invoke("launch_game", { id: game.value.id });
|
await invoke("launch_game", { id: game.value.id });
|
||||||
@ -91,7 +91,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ServerIcon, XMarkIcon } from "@heroicons/vue/20/solid";
|
import { ServerIcon, XMarkIcon } from "@heroicons/vue/20/solid";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import type { DownloadableMetadata, Game, GameStatus } from "~/types";
|
import { GameStatusEnum, type DownloadableMetadata, type Game, type GameStatus } from "~/types";
|
||||||
|
|
||||||
|
// const actionNames = {
|
||||||
|
// [GameStatusEnum.Downloading]: "downloading",
|
||||||
|
// [GameStatusEnum.Verifying]: "verifying",
|
||||||
|
// }
|
||||||
|
|
||||||
const windowWidth = ref(window.innerWidth);
|
const windowWidth = ref(window.innerWidth);
|
||||||
window.addEventListener("resize", (event) => {
|
window.addEventListener("resize", (event) => {
|
||||||
@ -158,7 +163,7 @@ function loadGamesForQueue(v: typeof queue.value) {
|
|||||||
if (games.value[id]) return;
|
if (games.value[id]) return;
|
||||||
(async () => {
|
(async () => {
|
||||||
const gameData = await useGame(id);
|
const gameData = await useGame(id);
|
||||||
const cover = await useObject(gameData.game.mCoverId);
|
const cover = await useObject(gameData.game.mCoverObjectId);
|
||||||
games.value[id] = { ...gameData, cover };
|
games.value[id] = { ...gameData, cover };
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -45,6 +45,7 @@ import type { Component } from "vue";
|
|||||||
import type { NavigationItem } from "~/types";
|
import type { NavigationItem } from "~/types";
|
||||||
import { platform } from '@tauri-apps/plugin-os';
|
import { platform } from '@tauri-apps/plugin-os';
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { UserIcon } from "@heroicons/vue/20/solid";
|
||||||
|
|
||||||
const systemData = await invoke<{
|
const systemData = await invoke<{
|
||||||
clientId: string;
|
clientId: string;
|
||||||
@ -101,6 +102,12 @@ const navigation = computed(() => [
|
|||||||
prefix: "/settings/downloads",
|
prefix: "/settings/downloads",
|
||||||
icon: ArrowDownTrayIcon,
|
icon: ArrowDownTrayIcon,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: "Account",
|
||||||
|
route: "/settings/account",
|
||||||
|
prefix: "/settings/account",
|
||||||
|
icon: UserIcon
|
||||||
|
},
|
||||||
...(isDebugMode.value ? [{
|
...(isDebugMode.value ? [{
|
||||||
label: "Debug Info",
|
label: "Debug Info",
|
||||||
route: "/settings/debug",
|
route: "/settings/debug",
|
||||||
64
main/pages/settings/account.vue
Normal file
64
main/pages/settings/account.vue
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border-b border-zinc-700 py-5">
|
||||||
|
<h3 class="text-base font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
General
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 flex flex-col gap-4">
|
||||||
|
<div class="flex flex-row items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium leading-6 text-zinc-100">Sign out</h3>
|
||||||
|
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
||||||
|
Sign out of your Drop account on this device
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
@click="signOut"
|
||||||
|
type="button"
|
||||||
|
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="error" class="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" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-600">
|
||||||
|
{{ error }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
import { listen } from "@tauri-apps/api/event";
|
||||||
|
import { useRouter } from "#imports";
|
||||||
|
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const error = ref<string | null>(null);
|
||||||
|
|
||||||
|
// Listen for auth events
|
||||||
|
onMounted(async () => {
|
||||||
|
await listen("auth/signedout", () => {
|
||||||
|
router.push("/auth/signedout");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function signOut() {
|
||||||
|
try {
|
||||||
|
error.value = null;
|
||||||
|
await invoke("sign_out");
|
||||||
|
} catch (e) {
|
||||||
|
error.value = `Failed to sign out: ${e}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -106,8 +106,6 @@ 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;
|
||||||
@ -1,8 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div class="border-b border-zinc-700 py-5">
|
||||||
<div class="border-b border-zinc-600 py-2 px-1">
|
<h3 class="text-base font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
Downloads
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="mt-5">
|
||||||
|
<div class="border-b border-zinc-600">
|
||||||
<div class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap">
|
<div class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap">
|
||||||
<div class="ml-4 mt-2">
|
<div class="ml-4 mt-2 pb-4">
|
||||||
<h3 class="text-base font-display font-semibold text-zinc-100">
|
<h3 class="text-base font-display font-semibold text-zinc-100">
|
||||||
Install directories
|
Install directories
|
||||||
</h3>
|
</h3>
|
||||||
59
main/pages/settings/index.vue
Normal file
59
main/pages/settings/index.vue
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<template>
|
||||||
|
<div class="border-b border-zinc-700 py-5">
|
||||||
|
<h3 class="text-base font-semibold font-display leading-6 text-zinc-100">
|
||||||
|
General
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-5 space-y-8">
|
||||||
|
<div class="flex flex-row items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h3 class="text-sm font-medium leading-6 text-zinc-100">
|
||||||
|
Start with system
|
||||||
|
</h3>
|
||||||
|
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
||||||
|
Drop will automatically start when you log into your computer
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
v-model="autostartEnabled"
|
||||||
|
:class="[
|
||||||
|
autostartEnabled ? 'bg-blue-600' : 'bg-zinc-700',
|
||||||
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
:class="[
|
||||||
|
autostartEnabled ? 'translate-x-5' : 'translate-x-0',
|
||||||
|
'pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Switch } from "@headlessui/vue";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
defineProps<{}>();
|
||||||
|
|
||||||
|
const autostartEnabled = ref<boolean>(false);
|
||||||
|
|
||||||
|
// Load initial state
|
||||||
|
invoke("get_autostart_enabled").then((enabled) => {
|
||||||
|
autostartEnabled.value = enabled as boolean;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Watch for changes and update autostart
|
||||||
|
watch(autostartEnabled, async (newValue: boolean) => {
|
||||||
|
try {
|
||||||
|
await invoke("toggle_autostart", { enabled: newValue });
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to toggle autostart:", error);
|
||||||
|
// Revert the toggle if it failed
|
||||||
|
autostartEnabled.value = !newValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
37
main/pages/store/index.vue
Normal file
37
main/pages/store/index.vue
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
<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>
|
||||||
5
main/tsconfig.json
Normal file
5
main/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
// https://nuxt.com/docs/guide/concepts/typescript
|
||||||
|
"extends": "./.nuxt/tsconfig.json",
|
||||||
|
"exclude": ["src-tauri/**/*"]
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ export type User = {
|
|||||||
username: string;
|
username: string;
|
||||||
admin: boolean;
|
admin: boolean;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
profilePicture: string;
|
profilePictureObjectId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type AppState = {
|
export type AppState = {
|
||||||
@ -30,11 +30,11 @@ export type Game = {
|
|||||||
mName: string;
|
mName: string;
|
||||||
mShortDescription: string;
|
mShortDescription: string;
|
||||||
mDescription: string;
|
mDescription: string;
|
||||||
mIconId: string;
|
mIconObjectId: string;
|
||||||
mBannerId: string;
|
mBannerObjectId: string;
|
||||||
mCoverId: string;
|
mCoverObjectId: string;
|
||||||
mImageLibrary: string[];
|
mImageLibraryObjectIds: string[];
|
||||||
mImageCarousel: string[];
|
mImageCarouselObjectIds: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GameVersion = {
|
export type GameVersion = {
|
||||||
@ -54,16 +54,19 @@ export enum GameStatusEnum {
|
|||||||
Remote = "Remote",
|
Remote = "Remote",
|
||||||
Queued = "Queued",
|
Queued = "Queued",
|
||||||
Downloading = "Downloading",
|
Downloading = "Downloading",
|
||||||
|
Validating = "Validating",
|
||||||
Installed = "Installed",
|
Installed = "Installed",
|
||||||
Updating = "Updating",
|
Updating = "Updating",
|
||||||
Uninstalling = "Uninstalling",
|
Uninstalling = "Uninstalling",
|
||||||
SetupRequired = "SetupRequired",
|
SetupRequired = "SetupRequired",
|
||||||
Running = "Running",
|
Running = "Running",
|
||||||
|
PartiallyInstalled = "PartiallyInstalled",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameStatus = {
|
export type GameStatus = {
|
||||||
type: GameStatusEnum;
|
type: GameStatusEnum;
|
||||||
version_name?: string;
|
version_name?: string;
|
||||||
|
install_dir?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum DownloadableType {
|
export enum DownloadableType {
|
||||||
8091
main/yarn.lock
Normal file
8091
main/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
22
optimize-appimage.sh
Executable file
22
optimize-appimage.sh
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
## 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"
|
||||||
43
package.json
43
package.json
@ -1,43 +1,22 @@
|
|||||||
{
|
{
|
||||||
"name": "drop-app",
|
"name": "drop-app",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.0-beta",
|
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "nuxt build",
|
"build": "node ./build.mjs",
|
||||||
"dev": "nuxt dev",
|
|
||||||
"generate": "nuxt generate",
|
|
||||||
"preview": "nuxt preview",
|
|
||||||
"postinstall": "nuxt prepare",
|
|
||||||
"tauri": "tauri"
|
"tauri": "tauri"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@tauri-apps/api": "^2.7.0",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@tauri-apps/plugin-deep-link": "^2.4.1",
|
||||||
"@nuxtjs/tailwindcss": "^6.12.2",
|
"@tauri-apps/plugin-dialog": "^2.3.2",
|
||||||
"@tauri-apps/api": ">=2.0.0",
|
"@tauri-apps/plugin-opener": "^2.4.0",
|
||||||
"@tauri-apps/plugin-deep-link": "~2",
|
"@tauri-apps/plugin-os": "^2.3.0",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.1",
|
"@tauri-apps/plugin-shell": "^2.3.0",
|
||||||
"@tauri-apps/plugin-os": "~2",
|
"pino": "^9.7.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.2.1",
|
"pino-pretty": "^13.1.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": {
|
||||||
"@tailwindcss/forms": "^0.5.9",
|
"@tauri-apps/cli": "^2.7.1"
|
||||||
"@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"
|
|
||||||
},
|
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,72 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="mx-auto max-w-7xl px-8">
|
|
||||||
<div class="border-b border-zinc-700 py-5">
|
|
||||||
<h3 class="text-base font-semibold font-display leading-6 text-zinc-100">
|
|
||||||
Account
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mt-5">
|
|
||||||
<div class="divide-y divide-zinc-700">
|
|
||||||
<div class="py-6">
|
|
||||||
<div class="flex flex-col gap-4">
|
|
||||||
<div class="flex flex-row items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-sm font-medium leading-6 text-zinc-100">Sign out</h3>
|
|
||||||
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
|
||||||
Sign out of your Drop account on this device
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
@click="signOut"
|
|
||||||
type="button"
|
|
||||||
class="rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
|
|
||||||
>
|
|
||||||
Sign out
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="error" class="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" />
|
|
||||||
</div>
|
|
||||||
<div class="ml-3">
|
|
||||||
<h3 class="text-sm font-medium text-red-600">
|
|
||||||
{{ error }}
|
|
||||||
</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
import { listen } from '@tauri-apps/api/event'
|
|
||||||
import { useRouter } from '#imports'
|
|
||||||
import { XCircleIcon } from "@heroicons/vue/16/solid";
|
|
||||||
|
|
||||||
const router = useRouter()
|
|
||||||
const error = ref<string | null>(null)
|
|
||||||
|
|
||||||
// Listen for auth events
|
|
||||||
onMounted(async () => {
|
|
||||||
await listen('auth/signedout', () => {
|
|
||||||
router.push('/auth/signedout')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
async function signOut() {
|
|
||||||
try {
|
|
||||||
error.value = null
|
|
||||||
await invoke('sign_out')
|
|
||||||
} catch (e) {
|
|
||||||
error.value = `Failed to sign out: ${e}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="divide-y divide-zinc-700">
|
|
||||||
<div class="py-6">
|
|
||||||
<h2 class="text-base font-semibold font-display leading-7 text-zinc-100">General</h2>
|
|
||||||
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
|
||||||
Configure basic application settings
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="mt-10 space-y-8">
|
|
||||||
<div class="flex flex-row items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<h3 class="text-sm font-medium leading-6 text-zinc-100">Start with system</h3>
|
|
||||||
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
|
||||||
Drop will automatically start when you log into your computer
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
v-model="autostartEnabled"
|
|
||||||
:class="[
|
|
||||||
autostartEnabled ? 'bg-blue-600' : 'bg-zinc-700',
|
|
||||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out'
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
:class="[
|
|
||||||
autostartEnabled ? 'translate-x-5' : 'translate-x-0',
|
|
||||||
'pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out'
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
</Switch>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { Switch } from '@headlessui/vue'
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
defineProps<{}>()
|
|
||||||
|
|
||||||
const autostartEnabled = ref<boolean>(false)
|
|
||||||
|
|
||||||
// Load initial state
|
|
||||||
invoke('get_autostart_enabled').then((enabled) => {
|
|
||||||
autostartEnabled.value = enabled as boolean
|
|
||||||
})
|
|
||||||
|
|
||||||
// Watch for changes and update autostart
|
|
||||||
watch(autostartEnabled, async (newValue: boolean) => {
|
|
||||||
try {
|
|
||||||
await invoke('toggle_autostart', { enabled: newValue })
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to toggle autostart:', error)
|
|
||||||
// Revert the toggle if it failed
|
|
||||||
autostartEnabled.value = !newValue
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
<template>
|
|
||||||
<iframe src="server://drop.local/store" class="w-full h-full" />
|
|
||||||
</template>
|
|
||||||
<script setup lang="ts"></script>
|
|
||||||
2668
src-tauri/Cargo.lock
generated
2668
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.2.0-beta-prerelease-1"
|
version = "0.3.2-dl"
|
||||||
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 = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
# 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
|
||||||
|
|
||||||
@ -16,8 +16,6 @@ tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] }
|
|||||||
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
|
||||||
name = "drop_app_lib"
|
name = "drop_app_lib"
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||||
|
|
||||||
[build]
|
|
||||||
rustflags = ["-C", "target-feature=+aes,+sse2"]
|
rustflags = ["-C", "target-feature=+aes,+sse2"]
|
||||||
|
|
||||||
|
|
||||||
@ -27,7 +25,6 @@ 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"
|
||||||
@ -50,7 +47,6 @@ throttle_my_fn = "0.2.6"
|
|||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
atomic-instant-full = "0.1.0"
|
atomic-instant-full = "0.1.0"
|
||||||
cacache = "13.1.0"
|
cacache = "13.1.0"
|
||||||
bincode = "1.3.3"
|
|
||||||
http-serde = "2.1.1"
|
http-serde = "2.1.1"
|
||||||
reqwest-middleware = "0.4.0"
|
reqwest-middleware = "0.4.0"
|
||||||
reqwest-middleware-cache = "0.1.1"
|
reqwest-middleware-cache = "0.1.1"
|
||||||
@ -69,14 +65,23 @@ whoami = "1.6.0"
|
|||||||
filetime = "0.2.25"
|
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"] }
|
||||||
|
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" }
|
||||||
|
|
||||||
[dependencies.dynfmt]
|
[dependencies.dynfmt]
|
||||||
version = "0.1.5"
|
version = "0.1.5"
|
||||||
features = ["curly"]
|
features = ["curly"]
|
||||||
|
|
||||||
[dependencies.tauri]
|
[dependencies.tauri]
|
||||||
version = "2.1.1"
|
version = "2.7.0"
|
||||||
features = ["tray-icon"]
|
features = ["protocol-asset", "tray-icon"]
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.40.0"
|
version = "1.40.0"
|
||||||
@ -96,11 +101,12 @@ features = ["v4", "fast-rng", "macro-diagnostics"]
|
|||||||
|
|
||||||
[dependencies.rustbreak]
|
[dependencies.rustbreak]
|
||||||
version = "2"
|
version = "2"
|
||||||
features = [] # You can also use "yaml_enc" or "bin_enc"
|
features = ["other_errors"] # You can also use "yaml_enc" or "bin_enc"
|
||||||
|
|
||||||
[dependencies.reqwest]
|
[dependencies.reqwest]
|
||||||
version = "0.12"
|
version = "0.12"
|
||||||
features = ["json", "blocking"]
|
default-features = false
|
||||||
|
features = ["json", "http2", "blocking", "rustls-tls", "native-tls-alpn", "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,6 +14,7 @@
|
|||||||
"core:window:allow-close",
|
"core:window:allow-close",
|
||||||
"deep-link:default",
|
"deep-link:default",
|
||||||
"dialog:default",
|
"dialog:default",
|
||||||
"os:default"
|
"os:default",
|
||||||
|
"opener:default"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked, save_db};
|
use crate::database::db::{borrow_db_checked, borrow_db_mut_checked};
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use tauri::AppHandle;
|
use tauri::AppHandle;
|
||||||
use tauri_plugin_autostart::ManagerExt;
|
use tauri_plugin_autostart::ManagerExt;
|
||||||
@ -17,7 +17,6 @@ pub fn toggle_autostart_logic(app: AppHandle, enabled: bool) -> Result<(), Strin
|
|||||||
let mut db_handle = borrow_db_mut_checked();
|
let mut db_handle = borrow_db_mut_checked();
|
||||||
db_handle.settings.autostart = enabled;
|
db_handle.settings.autostart = enabled;
|
||||||
drop(db_handle);
|
drop(db_handle);
|
||||||
save_db();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -13,10 +13,10 @@ 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:?}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
3
src-tauri/src/client/mod.rs
Normal file
3
src-tauri/src/client/mod.rs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
pub mod autostart;
|
||||||
|
pub mod cleanup;
|
||||||
|
pub mod commands;
|
||||||
@ -1,7 +1,9 @@
|
|||||||
use std::{
|
use std::{
|
||||||
fs::{self, create_dir_all, File},
|
fs::{self, create_dir_all, File},
|
||||||
io::{self, ErrorKind, Read, Write},
|
io::{self, ErrorKind, Read, Write},
|
||||||
path::{Path, PathBuf}, thread::sleep, time::Duration,
|
path::{Path, PathBuf},
|
||||||
|
thread::sleep,
|
||||||
|
time::Duration,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@ -60,7 +62,6 @@ pub fn resolve(meta: &mut CloudSaveMetadata) -> File {
|
|||||||
file.id = Some(id);
|
file.id = Some(id);
|
||||||
}
|
}
|
||||||
let binding = serde_json::to_string(meta).unwrap();
|
let binding = serde_json::to_string(meta).unwrap();
|
||||||
println!("Binding: {}", &binding);
|
|
||||||
let serialized = binding.as_bytes();
|
let serialized = binding.as_bytes();
|
||||||
let mut file = tempfile().unwrap();
|
let mut file = tempfile().unwrap();
|
||||||
file.write(serialized).unwrap();
|
file.write(serialized).unwrap();
|
||||||
@ -117,8 +118,10 @@ pub fn extract(file: PathBuf) -> Result<(), BackupError> {
|
|||||||
let new_path = parse_path(file.path.into(), handler, &manifest.game_version)?;
|
let new_path = parse_path(file.path.into(), handler, &manifest.game_version)?;
|
||||||
create_dir_all(&new_path.parent().unwrap()).unwrap();
|
create_dir_all(&new_path.parent().unwrap()).unwrap();
|
||||||
|
|
||||||
println!("Current path {:?} copying to {:?}", ¤t_path, &new_path);
|
println!(
|
||||||
|
"Current path {:?} copying to {:?}",
|
||||||
|
¤t_path, &new_path
|
||||||
|
);
|
||||||
|
|
||||||
copy_item(current_path, new_path).unwrap();
|
copy_item(current_path, new_path).unwrap();
|
||||||
}
|
}
|
||||||
@ -168,7 +171,11 @@ fn copy_dir_recursive(src: &Path, dest: &Path) -> io::Result<()> {
|
|||||||
let metadata = entry.metadata()?;
|
let metadata = entry.metadata()?;
|
||||||
|
|
||||||
if metadata.is_file() {
|
if metadata.is_file() {
|
||||||
debug!("Writing file {} to {}", entry_path.display(), dest_entry_path.display());
|
debug!(
|
||||||
|
"Writing file {} to {}",
|
||||||
|
entry_path.display(),
|
||||||
|
dest_entry_path.display()
|
||||||
|
);
|
||||||
fs::copy(&entry_path, &dest_entry_path)?;
|
fs::copy(&entry_path, &dest_entry_path)?;
|
||||||
} else if metadata.is_dir() {
|
} else if metadata.is_dir() {
|
||||||
copy_dir_recursive(&entry_path, &dest_entry_path)?;
|
copy_dir_recursive(&entry_path, &dest_entry_path)?;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user