Compare commits

..

6 Commits

Author SHA1 Message Date
31f7d5bcfa Merge branch 'develop' into bigpicturemode 2025-10-14 17:23:52 +11:00
87bbe1da49 156 refactor into workspaces (#157)
* chore: Major refactoring

Still needs a massive go-over because there shouldn't be anything referencing tauri in any of the workspaces except the original one. Process manager has been refactored as an example

Signed-off-by: quexeky <git@quexeky.dev>

* fix: Remote tauri dependency from process

Signed-off-by: quexeky <git@quexeky.dev>

* refactor: Improvements to src-tauri

Signed-off-by: quexeky <git@quexeky.dev>

* refactor: Builds, but some logic still left to move back

Signed-off-by: quexeky <git@quexeky.dev>

* refactor: Finish refactor

Signed-off-by: quexeky <git@quexeky.dev>

* chore: Run cargo clippy && cargo fmt

Signed-off-by: quexeky <git@quexeky.dev>

* refactor: Move everything into src-tauri

Signed-off-by: quexeky <git@quexeky.dev>

---------

Signed-off-by: quexeky <git@quexeky.dev>
2025-10-14 17:12:51 +11:00
ab9e06f6c4 feat: improvements to jump calculator 2025-09-24 07:38:37 +10:00
dbf9c8e8e5 feat: prototype big picture mode 2025-09-23 18:05:35 +10:00
864640d6ae feat: finish big picture navigation 2025-09-23 16:37:25 +10:00
e29d5c8ead partial: mutationobserver 2025-09-23 15:38:30 +10:00
150 changed files with 16880 additions and 9251 deletions

8303
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +0,0 @@
[workspace]
members = [
"client",
"database",
"src-tauri",
"process",
"remote",
"utils",
"cloud_saves",
"download_manager",
"games", "drop-consts",
]
resolver = "3"

View File

@ -1,7 +0,0 @@
[package]
name = "drop-consts"
version = "0.1.0"
edition = "2024"
[dependencies]
dirs = "6.0.0"

View File

@ -1,23 +0,0 @@
use std::{cell::LazyCell, path::PathBuf};
#[cfg(not(debug_assertions))]
pub const DATA_ROOT_PREFIX: &str = "drop";
#[cfg(debug_assertions)]
pub const DATA_ROOT_PREFIX: &str = "drop-debug";
pub const DATA_ROOT_DIR: LazyCell<PathBuf> = LazyCell::new(|| {
dirs::data_dir()
.expect("Failed to get data dir")
.join(DATA_ROOT_PREFIX)
});
pub const DROP_DATA_PATH: &str = ".dropdata";
// Downloads
pub const MAX_PACKET_LENGTH: usize = 4096 * 4;
pub const BUMP_SIZE: usize = 4096 * 16;
pub const RETRY_COUNT: usize = 3;
pub const TARGET_BUCKET_SIZE: usize = 63 * 1000 * 1000;
pub const MAX_FILES_PER_BUCKET: usize = (1024 / 4) - 1;

View File

@ -1,5 +1,5 @@
<template>
<NuxtLoadingIndicator color="#2563eb" />
<NuxtLoadingIndicator color="#2563eb" />
<NuxtLayout class="select-none w-screen h-screen">
<NuxtPage />
<ModalStack />
@ -7,14 +7,7 @@
</template>
<script setup lang="ts">
import "~/composables/downloads.js";
import { invoke } from "@tauri-apps/api/core";
import { useAppState } from "./composables/app-state.js";
import {
initialNavigation,
setupHooks,
} from "./composables/state-navigation.js";
const router = useRouter();

View File

@ -2,9 +2,7 @@
<div class="h-16 bg-zinc-950 flex flex-row justify-between">
<div class="flex flex-row grow items-center pl-5 pr-2 py-3">
<div class="inline-flex items-center gap-x-10">
<NuxtLink to="/store">
<Wordmark class="h-8 mb-0.5" />
</NuxtLink>
<Wordmark class="h-8 mb-0.5" />
<nav class="inline-flex items-center mt-0.5">
<ol class="inline-flex items-center gap-x-6">
<NuxtLink
@ -42,7 +40,7 @@
</ol>
</div>
</div>
<WindowControl />
<WindowControl />
</div>
</template>

View File

@ -76,7 +76,6 @@ import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
import { ChevronDownIcon } from "@heroicons/vue/16/solid";
import type { NavigationItem } from "../types";
import HeaderWidget from "./HeaderWidget.vue";
import { useAppState } from "~/composables/app-state";
import { invoke } from "@tauri-apps/api/core";
const open = ref(false);

View File

@ -73,7 +73,7 @@
alt=""
/>
</div>
<div class="inline-flex items-center gap-x-2">
<div class="flex flex-col gap-x-2">
<p
class="text-sm whitespace-nowrap font-display font-semibold"
>

View File

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

View File

@ -9,13 +9,17 @@ export default defineNuxtConfig({
},
},
css: ["~/assets/main.scss"],
ssr: false,
extends: [["../libs/drop-base"]],
extends: ["../shared", "../libs/drop-base"],
app: {
baseURL: "/main",
}
},
devtools: {
enabled: false,
},
});

View File

@ -116,7 +116,7 @@ platformInfo.value = currentPlatform;
async function openDataDir() {
if (!dataDir.value) return;
try {
await invoke("open_fs", { path: dataDir.value });
await open(dataDir.value);
} catch (error) {
console.error("Failed to open data dir:", error);
}
@ -126,7 +126,7 @@ async function openLogFile() {
if (!dataDir.value) return;
try {
const logPath = `${dataDir.value}/drop.log`;
await invoke("open_fs", { path: logPath });
await open(logPath);
} catch (error) {
console.error("Failed to open log file:", error);
}

View File

@ -7,6 +7,7 @@ export default {
"./plugins/**/*.{js,ts}",
"./app.vue",
"./error.vue",
"../shared/components/**/*.vue"
],
theme: {
extend: {

50
shared/app.vue Normal file
View File

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

84
shared/assets/main.scss Normal file
View File

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

View File

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

91
shared/error.vue Normal file
View File

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

25
shared/nuxt.config.ts Normal file
View File

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

37
shared/package.json Normal file
View File

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

20
shared/tailwind.config.js Normal file
View File

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

5
shared/tsconfig.json Normal file
View File

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

96
shared/types.ts Normal file
View File

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

8091
shared/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

2209
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -80,14 +80,13 @@ bytes = "1.10.1"
# Workspaces
client = { version = "0.1.0", path = "../client" }
database = { path = "../database" }
process = { path = "../process" }
remote = { version = "0.1.0", path = "../remote" }
utils = { path = "../utils" }
games = { version = "0.1.0", path = "../games" }
download_manager = { version = "0.1.0", path = "../download_manager" }
drop-consts = { version = "0.1.0", path = "../drop-consts" }
client = { version = "0.1.0", path = "./client" }
database = { path = "./database" }
process = { path = "./process" }
remote = { version = "0.1.0", path = "./remote" }
utils = { path = "./utils" }
games = { version = "0.1.0", path = "./games" }
download_manager = { version = "0.1.0", path = "./download_manager" }
[dependencies.dynfmt]
version = "0.1.5"
@ -138,3 +137,18 @@ features = ["derive", "rc"]
lto = true
codegen-units = 1
panic = 'abort'
[workspace]
members = [
"client",
"database",
"process",
"remote",
"utils",
"cloud_saves",
"download_manager",
"games",
]
resolver = "3"

View File

@ -1,12 +1,15 @@
use std::{
cell::LazyCell, ffi::OsStr, path::PathBuf, process::{Command, Stdio}
ffi::OsStr,
path::PathBuf,
process::{Command, Stdio},
sync::LazyLock,
};
use log::info;
pub const COMPAT_INFO: LazyCell<Option<CompatInfo>> = LazyCell::new(create_new_compat_info);
pub static COMPAT_INFO: LazyLock<Option<CompatInfo>> = LazyLock::new(create_new_compat_info);
pub const UMU_LAUNCHER_EXECUTABLE: LazyCell<Option<PathBuf>> = LazyCell::new(|| {
pub static UMU_LAUNCHER_EXECUTABLE: LazyLock<Option<PathBuf>> = LazyLock::new(|| {
let x = get_umu_executable();
info!("{:?}", &x);
x

View File

@ -6,7 +6,6 @@ edition = "2024"
[dependencies]
database = { version = "0.1.0", path = "../database" }
dirs = "6.0.0"
drop-consts = { version = "0.1.0", path = "../drop-consts" }
log = "0.4.28"
regex = "1.11.3"
rustix = "1.1.2"

View File

@ -2,8 +2,7 @@ use std::{collections::HashMap, path::PathBuf, str::FromStr};
#[cfg(target_os = "linux")]
use database::platform::Platform;
use database::GameVersion;
use drop_consts::DATA_ROOT_DIR;
use database::{GameVersion, db::DATA_ROOT_DIR};
use log::warn;
use crate::error::BackupError;

View File

@ -6,7 +6,6 @@ edition = "2024"
[dependencies]
chrono = "0.4.42"
dirs = "6.0.0"
drop-consts = { version = "0.1.0", path = "../drop-consts" }
log = "0.4.28"
native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"}
rustbreak = "2.0.0"

View File

@ -1,5 +1,6 @@
use std::{
sync::LazyLock,
path::PathBuf,
sync::{Arc, LazyLock},
};
use rustbreak::{DeSerError, DeSerializer};
@ -9,6 +10,19 @@ use crate::interface::{DatabaseImpls, DatabaseInterface};
pub static DB: LazyLock<DatabaseInterface> = LazyLock::new(DatabaseInterface::set_up_database);
#[cfg(not(debug_assertions))]
static DATA_ROOT_PREFIX: &str = "drop";
#[cfg(debug_assertions)]
static DATA_ROOT_PREFIX: &str = "drop-debug";
pub static DATA_ROOT_DIR: LazyLock<Arc<PathBuf>> = LazyLock::new(|| {
Arc::new(
dirs::data_dir()
.expect("Failed to get data dir")
.join(DATA_ROOT_PREFIX),
)
});
// Custom JSON serializer to support everything we need
#[derive(Debug, Default, Clone)]
pub struct DropDatabaseSerializer;
@ -25,7 +39,7 @@ impl<T: native_model::Model + Serialize + DeserializeOwned> DeSerializer<T>
s.read_to_end(&mut buf)
.map_err(|e| rustbreak::error::DeSerError::Other(e.into()))?;
let (val, _version) =
native_model::decode(buf).map_err(|e| rustbreak::error::DeSerError::Internal(e.to_string()))?;
native_model::decode(buf).map_err(|e| DeSerError::Internal(e.to_string()))?;
Ok(val)
}
}

View File

@ -7,13 +7,12 @@ use std::{
};
use chrono::Utc;
use drop_consts::DATA_ROOT_DIR;
use log::{debug, error, info, warn};
use rustbreak::{PathDatabase, RustbreakError};
use url::Url;
use crate::{
db::{DB, DropDatabaseSerializer},
db::{DATA_ROOT_DIR, DB, DropDatabaseSerializer},
models::data::Database,
};

View File

@ -24,4 +24,3 @@ throttle_my_fn = "0.2.6"
utils = { version = "0.1.0", path = "../utils" }
native_model = { version = "0.6.4", features = ["rmp_serde_1_3"], git = "https://github.com/Drop-OSS/native_model.git"}
serde_json = "1.0.145"
drop-consts = { version = "0.1.0", path = "../drop-consts" }

View File

@ -9,7 +9,6 @@ use download_manager::util::download_thread_control_flag::{
DownloadThreadControl, DownloadThreadControlFlag,
};
use download_manager::util::progress_object::{ProgressHandle, ProgressObject};
use drop_consts::{MAX_FILES_PER_BUCKET, RETRY_COUNT, TARGET_BUCKET_SIZE};
use log::{debug, error, info, warn};
use rayon::ThreadPoolBuilder;
use remote::auth::generate_authorization_header;
@ -40,6 +39,11 @@ use crate::state::GameStatusManager;
use super::download_logic::download_game_bucket;
use super::drop_data::DropData;
static RETRY_COUNT: usize = 3;
const TARGET_BUCKET_SIZE: usize = 63 * 1000 * 1000;
const MAX_FILES_PER_BUCKET: usize = (1024 / 4) - 1;
pub struct GameDownloadAgent {
pub id: String,
pub version: String,

View File

@ -15,7 +15,6 @@ use download_manager::util::download_thread_control_flag::{
DownloadThreadControl, DownloadThreadControlFlag,
};
use download_manager::util::progress_object::ProgressHandle;
use drop_consts::{BUMP_SIZE, MAX_PACKET_LENGTH};
use log::{debug, info, warn};
use md5::{Context, Digest};
use remote::auth::generate_authorization_header;
@ -26,6 +25,9 @@ use reqwest::blocking::Response;
use crate::downloads::manifest::{ChunkBody, DownloadBucket, DownloadContext, DownloadDrop};
static MAX_PACKET_LENGTH: usize = 4096 * 4;
static BUMP_SIZE: usize = 4096 * 16;
pub struct DropWriter<W: Write> {
hasher: Context,
destination: BufWriter<W>,

View File

@ -5,13 +5,14 @@ use std::{
path::{Path, PathBuf},
};
use drop_consts::DROP_DATA_PATH;
use log::error;
use native_model::{Decode, Encode};
use utils::lock;
pub type DropData = v1::DropData;
pub static DROP_DATA_PATH: &str = ".dropdata";
pub mod v1 {
use std::{collections::HashMap, path::PathBuf, sync::Mutex};

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