Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion libs/drop-base
9 changes: 5 additions & 4 deletions main/app.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<template>
<NuxtLoadingIndicator color="#2563eb" />
<NuxtLoadingIndicator color="#2563eb" />
<NuxtLayout class="select-none w-screen h-screen">
<NuxtPage />
<ModalStack />
Expand All @@ -15,6 +15,8 @@ import {
initialNavigation,
setupHooks,
} from "./composables/state-navigation.js";
import { listen } from "@tauri-apps/api/event";
import type { AppState } from "./types.js";

const router = useRouter();

Expand All @@ -36,9 +38,8 @@ async function fetchState() {
}
await fetchState();

// This is inefficient but apparently we do it lol
router.beforeEach(async () => {
await fetchState();
listen("update_state", (event) => {
state.value = event.payload as AppState;
});

setupHooks();
Expand Down
106 changes: 106 additions & 0 deletions main/components/DependencyRequiredModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
<template>
<ModalTemplate :model-value="true">
<template #default
><div class="flex items-start gap-x-3">
<img :src="useObject(game.mIconObjectId)" class="size-12" />
<div class="mt-3 text-center sm:mt-0 sm:text-left">
<h3 class="text-base font-semibold text-zinc-100">
Missing required dependency "{{ game.mName }}"
</h3>
<div class="mt-2">
<p class="text-sm text-zinc-400">
To launch this game, you need to have "{{ game.mName }}" ({{
version.displayName ?? version.versionPath
}}) installed.
</p>
</div>
</div>
</div>
<InstallDirectorySelector
:install-dirs="installDirs"
v-model="installDir"
/>

<div v-if="installError" class="mt-1 rounded-md bg-red-600/10 p-4">
<div class="flex">
<div class="flex-shrink-0">
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-600">
{{ installError }}
</h3>
</div>
</div>
</div>
</template>
<template #buttons>
<LoadingButton
@click="() => install()"
:loading="installLoading"
:disabled="installLoading"
type="submit"
class="ml-2 w-full sm:w-fit"
>
Install
</LoadingButton>
<button
type="button"
class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 hover:bg-zinc-900 sm:mt-0 sm:w-auto"
@click="cancel"
ref="cancelButtonRef"
>
Cancel
</button>
</template>
</ModalTemplate>
</template>

<script setup lang="ts">
import { invoke } from "@tauri-apps/api/core";
import { XCircleIcon } from "@heroicons/vue/24/solid";

const model = defineModel<{ gameId: string; versionId: string }>({
required: true,
});

const { game, status } = await useGame(model.value.gameId);

const versionOptions = await invoke<Array<VersionOption>>(
"fetch_game_version_options",
{
gameId: game.id,
}
);
const version = versionOptions.find(
(v) => v.versionId === model.value.versionId
)!;

const installDirs = await invoke<string[]>("fetch_download_dir_stats");
const installDir = ref(0);

function cancel() {
// @ts-expect-error
model.value = undefined;
}

const installError = ref<string | undefined>();
const installLoading = ref(false);

async function install() {
try {
installLoading.value = true;
await invoke("download_game", {
gameId: game.id,
versionId: model.value.versionId,
installDir: installDir.value,
targetPlatform: version.platform,
});
cancel();
} catch (error) {
installError.value = (error as string).toString();
}

installLoading.value = false;
}
</script>
87 changes: 87 additions & 0 deletions main/components/InstallDirectorySelector.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<template>
<Listbox as="div" v-model="installDir">
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
>Install to</ListboxLabel
>
<div class="relative mt-2">
<ListboxButton
class="relative w-full cursor-default rounded-md bg-zinc-800 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-600 sm:text-sm/6"
>
<span class="block truncate">{{ installDirs[installDir] }}</span>
<span
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
>
<ChevronUpDownIcon class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>

<transition
leave-active-class="transition ease-in duration-100"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
>
<ListboxOption
as="template"
v-for="(dir, dirIdx) in installDirs"
:key="dir"
:value="dirIdx"
v-slot="{ active, selected }"
>
<li
:class="[
active ? 'bg-blue-600 text-white' : 'text-zinc-300',
'relative cursor-default select-none py-2 pl-3 pr-9',
]"
>
<span
:class="[
selected ? 'font-semibold text-zinc-100' : 'font-normal',
'block truncate',
]"
>{{ dir }}</span
>

<span
v-if="selected"
:class="[
active ? 'text-white' : 'text-blue-600',
'absolute inset-y-0 right-0 flex items-center pr-4',
]"
>
<CheckIcon class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
<div class="text-zinc-400 text-sm mt-2">
Add more install directories in
<PageWidget to="/settings/downloads">
<WrenchIcon class="size-3" />
Settings
</PageWidget>
</div>
</Listbox>
</template>

<script setup lang="ts">
import {
Listbox,
ListboxButton,
ListboxLabel,
ListboxOption,
ListboxOptions,
} from "@headlessui/vue";
import {
CheckIcon,
ChevronUpDownIcon,
WrenchIcon,
} from "@heroicons/vue/20/solid";

const installDir = defineModel<number>({ required: true });
const { installDirs } = defineProps<{ installDirs: string[] }>();
</script>
22 changes: 18 additions & 4 deletions main/components/LibrarySearch.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,7 @@
/>
</div>
<div class="truncate inline-flex items-center gap-x-2">
<p
class="text-sm whitespace-nowrap font-display font-semibold"
>
<p class="text-sm whitespace-nowrap font-display font-semibold">
{{ item.label }}
</p>
<p
Expand Down Expand Up @@ -143,7 +141,7 @@ const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
[GameStatusEnum.Installed]: "text-green-500",
[GameStatusEnum.Downloading]: "text-zinc-400",
[GameStatusEnum.Validating]: "text-blue-300",
[GameStatusEnum.Running]: "text-green-500",
[GameStatusEnum.Running]: "text-blue-500",
[GameStatusEnum.Remote]: "text-zinc-700",
[GameStatusEnum.Queued]: "text-zinc-400",
[GameStatusEnum.Updating]: "text-zinc-400",
Expand Down Expand Up @@ -177,6 +175,22 @@ const icons: { [key: string]: string } = {};
const collections: Ref<Collection[]> = ref([]);

async function calculateGames(clearAll = false, forceRefresh = false) {
try {
await calculateGamesLogic(clearAll, forceRefresh);
} catch (e) {
createModal(
ModalType.Notification,
{
title: "Failed to fetch library",
description: `Drop encountered an error while fetching your library: ${e}`,
},
(_, c) => c()
);
}
loading.value = false;
}

async function calculateGamesLogic(clearAll = false, forceRefresh = false) {
if (clearAll) {
collections.value = [];
loading.value = true;
Expand Down
10 changes: 8 additions & 2 deletions main/components/OfflineHeaderWidget.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
<script setup lang="ts">
import { ArrowDownTrayIcon, CloudIcon } from "@heroicons/vue/20/solid";
import { invoke } from "@tauri-apps/api/core";

async function checkOffline() {
const isOffline = await invoke("check_online");
}
</script>

<template>
<div
<button
@click="checkOffline"
class="transition inline-flex items-center rounded-sm px-4 py-1.5 bg-zinc-900 text-sm text-zinc-400 gap-x-2"
>
<div class="relative">
Expand All @@ -13,5 +19,5 @@ import { ArrowDownTrayIcon, CloudIcon } from "@heroicons/vue/20/solid";
/>
</div>
Offline
</div>
</button>
</template>
16 changes: 15 additions & 1 deletion main/composables/downloads.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,18 @@ listen("update_stats", (event) => {
stats.value = event.payload as StatsState;
});

export const useDownloadHistory = () => useState<Array<number>>('history', () => []);
export const useDownloadHistory = () => useState<Array<number>>('history', () => []);

export function formatKilobytes(bytes: number): string {
const units = ["K", "M", "G", "T", "P"];
let value = bytes;
let unitIndex = 0;
const scalar = 1000;

while (value >= scalar && unitIndex < units.length - 1) {
value /= scalar;
unitIndex++;
}

return `${value.toFixed(1)} ${units[unitIndex]}`;
}
27 changes: 23 additions & 4 deletions main/composables/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,18 @@ export const useGame = async (gameId: string) => {
gameStatusRegistry[gameId] = ref(parseStatus(data.status));

listen(`update_game/${gameId}`, (event) => {
console.log(event);
const payload: {
status: SerializedGameStatus;
version?: GameVersion;
} = event.payload as any;
gameStatusRegistry[gameId].value = parseStatus(payload.status);

/**
* I am not super happy about this.
*
*
* This will mean that we will still have a version assigned if we have a game installed then uninstall it.
* It is necessary because a flag to check if we should overwrite seems excessive, and this function gets called
* on transient state updates.
* on transient state updates.
*/
if (payload.version) {
gameRegistry[gameId].version = payload.version;
Expand All @@ -72,3 +71,23 @@ export const useGame = async (gameId: string) => {
export type FrontendGameConfiguration = {
launchString: string;
};

export type LaunchResult =
| { result: "Success" }
| { result: "InstallRequired"; data: [string, string] };

export type VersionOption = {
versionId: string;
displayName?: string;
versionPath: string;
platform: string;
size: number;
requiredContent: Array<{
gameId: string;
versionId: string;
name: string;
iconObjectId: string;
shortDescription: string;
size: number;
}>;
};
2 changes: 1 addition & 1 deletion main/composables/use-object.ts
Original file line number Diff line number Diff line change
@@ -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");
};
2 changes: 1 addition & 1 deletion main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@headlessui/vue": "^1.7.23",
"@heroicons/vue": "^2.1.5",
"@nuxtjs/tailwindcss": "^6.12.2",
"@tauri-apps/api": "^2.7.0",
"@tauri-apps/api": "^2.9.1",
"@tauri-apps/plugin-os": "^2.3.2",
"@tauri-apps/plugin-shell": "^2.3.3",
"koa": "^2.16.1",
Expand Down
Loading
Loading