Error handling (#121)

This commit is contained in:
Geometrically 2023-05-22 18:11:31 -07:00 committed by GitHub
parent 6014172046
commit 1b47eb71e1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 271 additions and 198 deletions

View File

@ -15,7 +15,7 @@
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.2.0", "@tauri-apps/api": "^1.2.0",
"ofetch": "^1.0.1", "ofetch": "^1.0.1",
"omorphia": "^0.4.16", "omorphia": "^0.4.17",
"pinia": "^2.0.33", "pinia": "^2.0.33",
"vite-svg-loader": "^4.0.0", "vite-svg-loader": "^4.0.0",
"vue": "^3.2.45", "vue": "^3.2.45",

View File

@ -13,15 +13,15 @@ pub mod profile_create;
pub mod settings; pub mod settings;
pub mod tags; pub mod tags;
pub type Result<T> = std::result::Result<T, TheseusGuiError>; pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
// Main returnable Theseus GUI error // // Main returnable Theseus GUI error
// Needs to be Serializable to be returned to the JavaScript side // // Needs to be Serializable to be returned to the JavaScript side
#[derive(Error, Debug, Serialize)] // #[derive(Error, Debug, Serialize)]
pub enum TheseusGuiError { // pub enum TheseusGuiError {
#[error(transparent)] // #[error(transparent)]
Serializable(TheseusSerializableError), // Serializable(),
} // }
// Serializable error intermediary, so TheseusGuiError can be Serializable (eg: so that we can return theseus::Errors in Tauri directly) // Serializable error intermediary, so TheseusGuiError can be Serializable (eg: so that we can return theseus::Errors in Tauri directly)
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -34,14 +34,14 @@ pub enum TheseusSerializableError {
} }
// Generic implementation of From<T> for ErrorTypeA // Generic implementation of From<T> for ErrorTypeA
impl<T> From<T> for TheseusGuiError // impl<T> From<T> for TheseusGuiError
where // where
TheseusSerializableError: From<T>, // TheseusSerializableError: From<T>,
{ // {
fn from(error: T) -> Self { // fn from(error: T) -> Self {
TheseusGuiError::Serializable(TheseusSerializableError::from(error)) // TheseusGuiError::Serializable(TheseusSerializableError::from(error))
} // }
} // }
// Lists active progress bars // Lists active progress bars
// Create a new HashMap with the same keys // Create a new HashMap with the same keys

View File

@ -1,8 +1,16 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref, watch } from 'vue'
import { RouterView, RouterLink } from 'vue-router' import { RouterView, RouterLink } from 'vue-router'
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, Button } from 'omorphia' import {
import { useLoading, useTheming } from '@/store/state' HomeIcon,
SearchIcon,
LibraryIcon,
PlusIcon,
SettingsIcon,
Button,
Notifications,
} from 'omorphia'
import { handleError, useLoading, useTheming } from '@/store/state'
import AccountsCard from '@/components/ui/AccountsCard.vue' import AccountsCard from '@/components/ui/AccountsCard.vue'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import { get } from '@/helpers/settings' import { get } from '@/helpers/settings'
@ -10,14 +18,24 @@ import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
import RunningAppBar from '@/components/ui/RunningAppBar.vue' import RunningAppBar from '@/components/ui/RunningAppBar.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue' import SplashScreen from '@/components/ui/SplashScreen.vue'
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator' import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
import { useNotifications } from '@/store/notifications.js'
import { warning_listener } from '@/helpers/events.js'
const themeStore = useTheming() const themeStore = useTheming()
const isLoading = ref(true) const isLoading = ref(true)
onMounted(async () => { onMounted(async () => {
const { settings, collapsed_navigation } = await get() const { settings, collapsed_navigation } = await get().catch(handleError)
themeStore.setThemeState(settings) themeStore.setThemeState(settings)
themeStore.collapsedNavigation = collapsed_navigation themeStore.collapsedNavigation = collapsed_navigation
await warning_listener((e) =>
notificationsWrapper.value.addNotification({
title: 'Warning',
text: e.message,
type: 'warn',
})
)
}) })
defineExpose({ defineExpose({
@ -28,6 +46,13 @@ defineExpose({
}, },
}) })
const loading = useLoading() const loading = useLoading()
const notifications = useNotifications()
const notificationsWrapper = ref(null)
watch(notificationsWrapper, () => {
notifications.setNotifs(notificationsWrapper.value)
})
</script> </script>
<template> <template>
@ -123,6 +148,7 @@ const loading = useLoading()
offset-height="var(--appbar-height)" offset-height="var(--appbar-height)"
offset-width="var(--sidebar-width)" offset-width="var(--sidebar-width)"
/> />
<Notifications ref="notificationsWrapper" />
<RouterView v-slot="{ Component }"> <RouterView v-slot="{ Component }">
<template v-if="Component"> <template v-if="Component">
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()"> <Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="m6 14 1.45-2.9A2 2 0 0 1 9.24 10H20a2 2 0 0 1 1.94 2.5l-1.55 6a2 2 0 0 1-1.94 1.5H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H18a2 2 0 0 1 2 2v2"></path>
</svg>

Before

Width:  |  Height:  |  Size: 389 B

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M11 20H4a2 2 0 0 1-2-2V5c0-1.1.9-2 2-2h3.93a2 2 0 0 1 1.66.9l.82 1.2a2 2 0 0 0 1.66.9H20a2 2 0 0 1 2 2v4"></path>
<circle cx="17" cy="17" r="3"></circle>
<path d="m21 21-1.5-1.5"></path>
</svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 12-8.5 8.5c-.83.83-2.17.83-3 0 0 0 0 0 0 0a2.12 2.12 0 0 1 0-3L12 9"></path><path d="M17.64 15 22 10.64"></path><path d="m20.91 11.7-1.25-1.25c-.6-.6-.93-1.4-.93-2.25v-.86L16.01 4.6a5.56 5.56 0 0 0-3.94-1.64H9l.92.82A6.18 6.18 0 0 1 12 8.4v1.56l2 2h2.47l2.26 1.91"></path></svg>

Before

Width:  |  Height:  |  Size: 473 B

View File

@ -1,8 +0,0 @@
export { default as PlayIcon } from './play.svg'
export { default as OpenFolderIcon } from './folder-open.svg'
export { default as BrowseIcon } from './folder-search.svg'
export { default as LoginIcon } from './log-in.svg'
export { default as StopIcon } from './stop-circle.svg'
export { default as TerminalIcon } from './terminal-square.svg'
export { default as UsersIcon } from './users.svg'
export { default as HammerIcon } from './hammer.svg'

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-log-in"><path d="M15 3h4a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2h-4"></path><polyline points="10 17 15 12 10 7"></polyline><line x1="15" x2="3" y1="12" y2="12"></line></svg>

Before

Width:  |  Height:  |  Size: 366 B

View File

@ -1,4 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<polygon points="5 3 19 12 5 21 5 3"></polygon>
</svg>

Before

Width:  |  Height:  |  Size: 239 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-stop-circle"><circle cx="12" cy="12" r="10"></circle><rect width="6" height="6" x="9" y="9"></rect></svg>

Before

Width:  |  Height:  |  Size: 307 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-terminal-square"><path d="m7 11 2-2-2-2"></path><path d="M11 13h4"></path><rect width="18" height="18" x="3" y="3" rx="2" ry="2"></rect></svg>

Before

Width:  |  Height:  |  Size: 344 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-users"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M22 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>

Before

Width:  |  Height:  |  Size: 398 B

View File

@ -1,6 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle>
<line x1="15" y1="9" x2="9" y2="15"></line>
<line x1="9" y1="9" x2="15" y2="15"></line>
</svg>

Before

Width:  |  Height:  |  Size: 324 B

View File

@ -31,7 +31,7 @@
<div v-else class="logged-out account"> <div v-else class="logged-out account">
<h4>Not signed in</h4> <h4>Not signed in</h4>
<Button icon-only color="primary" @click="login()"> <Button icon-only color="primary" @click="login()">
<LoginIcon /> <LogInIcon />
</Button> </Button>
</div> </div>
<div v-if="displayAccounts.length > 0" class="account-group"> <div v-if="displayAccounts.length > 0" class="account-group">
@ -54,8 +54,7 @@
</template> </template>
<script setup> <script setup>
import { Avatar, Button, Card, PlusIcon, XIcon } from 'omorphia' import { Avatar, Button, Card, PlusIcon, XIcon, UsersIcon, LogInIcon } from 'omorphia'
import { LoginIcon, UsersIcon } from '@/assets/icons'
import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue' import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue'
import { import {
users, users,
@ -65,6 +64,7 @@ import {
} from '@/helpers/auth' } from '@/helpers/auth'
import { get, set } from '@/helpers/settings' import { get, set } from '@/helpers/settings'
import { WebviewWindow } from '@tauri-apps/api/window' import { WebviewWindow } from '@tauri-apps/api/window'
import { handleError } from '@/store/state.js'
defineProps({ defineProps({
expanded: { expanded: {
@ -73,7 +73,7 @@ defineProps({
}, },
}) })
const settings = ref(await get()) const settings = ref(await get().catch(handleError))
const appendProfiles = (accounts) => { const appendProfiles = (accounts) => {
return accounts.map((account) => { return accounts.map((account) => {
@ -84,7 +84,7 @@ const appendProfiles = (accounts) => {
}) })
} }
const accounts = ref(await users().then(appendProfiles)) const accounts = ref(await users().then(appendProfiles).catch(handleError))
const displayAccounts = computed(() => const displayAccounts = computed(() =>
accounts.value.filter((account) => settings.value.default_user !== account.id) accounts.value.filter((account) => settings.value.default_user !== account.id)
@ -95,7 +95,7 @@ const selectedAccount = ref(
) )
const refreshValues = async () => { const refreshValues = async () => {
accounts.value = await users().then(appendProfiles) accounts.value = await users().then(appendProfiles).catch(handleError)
selectedAccount.value = accounts.value.find( selectedAccount.value = accounts.value.find(
(account) => account.id === settings.value.default_user (account) => account.id === settings.value.default_user
) )
@ -108,11 +108,11 @@ let button = ref(null)
const setAccount = async (account) => { const setAccount = async (account) => {
settings.value.default_user = account.id settings.value.default_user = account.id
selectedAccount.value = account selectedAccount.value = account
await set(settings.value) await set(settings.value).catch(handleError)
} }
const login = async () => { const login = async () => {
const url = await authenticate_begin_flow() const url = await authenticate_begin_flow().catch(handleError)
const window = new WebviewWindow('loginWindow', { const window = new WebviewWindow('loginWindow', {
url: url, url: url,
@ -126,14 +126,14 @@ const login = async () => {
console.log('webview error', e) console.log('webview error', e)
}) })
const loggedIn = await authenticate_await_completion() const loggedIn = await authenticate_await_completion().catch(handleError)
await setAccount(loggedIn) await setAccount(loggedIn)
await refreshValues() await refreshValues()
await window.close() await window.close()
} }
const logout = async (id) => { const logout = async (id) => {
await remove_user(id) await remove_user(id).catch(handleError)
await refreshValues() await refreshValues()
if (!selectedAccount.value && accounts.value.length > 0) { if (!selectedAccount.value && accounts.value.length > 0) {
await setAccount(accounts.value[0]) await setAccount(accounts.value[0])

View File

@ -54,6 +54,7 @@
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia' import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
import { add_project_from_version as installMod } from '@/helpers/profile' import { add_project_from_version as installMod } from '@/helpers/profile'
import { defineExpose, ref } from 'vue' import { defineExpose, ref } from 'vue'
import { handleError } from '@/store/state.js'
const instance = ref(null) const instance = ref(null)
const projectTitle = ref(null) const projectTitle = ref(null)
@ -77,7 +78,7 @@ defineExpose({
const install = async () => { const install = async () => {
installing.value = true installing.value = true
await installMod(instance.value.path, selectedVersion.value.id) await installMod(instance.value.path, selectedVersion.value.id).catch(handleError)
installing.value = false installing.value = false
markInstalled() markInstalled()
incompatibleModal.value.hide() incompatibleModal.value.hide()

View File

@ -1,9 +1,7 @@
<script setup> <script setup>
import { onUnmounted, ref, useSlots } from 'vue' import { onUnmounted, ref, useSlots } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ofetch } from 'ofetch' import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo } from 'omorphia'
import { PlayIcon } from '@/assets/icons'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue' import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
import { install as pack_install } from '@/helpers/pack' import { install as pack_install } from '@/helpers/pack'
@ -14,6 +12,8 @@ import {
get_uuids_by_profile_path, get_uuids_by_profile_path,
} from '@/helpers/process' } from '@/helpers/process'
import { process_listener } from '@/helpers/events' import { process_listener } from '@/helpers/events'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/state.js'
const props = defineProps({ const props = defineProps({
instance: { instance: {
@ -46,7 +46,7 @@ const seeInstance = async () => {
} }
const checkProcess = async () => { const checkProcess = async () => {
const runningPaths = await get_all_running_profile_paths() const runningPaths = await get_all_running_profile_paths().catch(handleError)
if (runningPaths.includes(props.instance.path)) { if (runningPaths.includes(props.instance.path)) {
playing.value = true playing.value = true
@ -60,12 +60,13 @@ const checkProcess = async () => {
const install = async (e) => { const install = async (e) => {
e.stopPropagation() e.stopPropagation()
modLoading.value = true modLoading.value = true
const versions = await ofetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${props.instance.project_id}/version` `https://api.modrinth.com/v2/project/${props.instance.project_id}/version`,
'project versions'
) )
if (props.instance.project_type === 'modpack') { if (props.instance.project_type === 'modpack') {
const packs = Object.values(await list(true)) const packs = Object.values(await list(true).catch(handleError))
if ( if (
packs.length === 0 || packs.length === 0 ||
@ -75,7 +76,9 @@ const install = async (e) => {
) { ) {
try { try {
modLoading.value = true modLoading.value = true
await pack_install(versions[0].id, props.instance.title, props.instance.icon_url) await pack_install(versions[0].id, props.instance.title, props.instance.icon_url).catch(
handleError
)
modLoading.value = false modLoading.value = false
} catch (err) { } catch (err) {
console.error(err) console.error(err)
@ -91,7 +94,7 @@ const install = async (e) => {
const play = async (e) => { const play = async (e) => {
e.stopPropagation() e.stopPropagation()
modLoading.value = true modLoading.value = true
uuid.value = await run(props.instance.path) uuid.value = await run(props.instance.path).catch(handleError)
modLoading.value = false modLoading.value = false
playing.value = true playing.value = true
} }
@ -105,10 +108,10 @@ const stop = async (e) => {
// from-then-back to this page, we will get all uuids by the instance path. // from-then-back to this page, we will get all uuids by the instance path.
// For-each uuid, kill the process. // For-each uuid, kill the process.
if (!uuid.value) { if (!uuid.value) {
const uuids = await get_uuids_by_profile_path(props.instance.path) const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
uuid.value = uuids[0] uuid.value = uuids[0]
uuids.forEach(async (u) => await kill_by_uuid(u)) uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
} else await kill_by_uuid(uuid.value) // If we still have the uuid, just kill it } else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
} catch (err) { } catch (err) {
// Theseus currently throws: // Theseus currently throws:
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error // "Error launching Minecraft: Minecraft exited with non-zero code 1" error

View File

@ -86,11 +86,9 @@ import { computed, ref, shallowRef } from 'vue'
import { get_game_versions, get_loaders } from '@/helpers/tags' import { get_game_versions, get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile' import { create } from '@/helpers/profile'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { useRouter } from 'vue-router'
import { tauri } from '@tauri-apps/api' import { tauri } from '@tauri-apps/api'
import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata' import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata'
import { handleError } from '@/store/notifications.js'
const router = useRouter()
const profile_name = ref('') const profile_name = ref('')
const game_version = ref('') const game_version = ref('')
@ -127,17 +125,18 @@ defineExpose({
const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] = const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] =
await Promise.all([ await Promise.all([
get_fabric_versions().then(shallowRef), get_fabric_versions().then(shallowRef).catch(handleError),
get_forge_versions().then(shallowRef), get_forge_versions().then(shallowRef).catch(handleError),
get_quilt_versions().then(shallowRef), get_quilt_versions().then(shallowRef).catch(handleError),
get_game_versions().then(shallowRef), get_game_versions().then(shallowRef).catch(handleError),
get_loaders() get_loaders()
.then((value) => .then((value) =>
value value
.filter((item) => item.supported_project_types.includes('modpack')) .filter((item) => item.supported_project_types.includes('modpack'))
.map((item) => item.name.toLowerCase()) .map((item) => item.name.toLowerCase())
) )
.then(ref), .then(ref)
.catch(handleError),
]) ])
loaders.value.push('vanilla') loaders.value.push('vanilla')
@ -172,15 +171,14 @@ const create_instance = async () => {
const loader_version_value = const loader_version_value =
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
const id = await create( await create(
profile_name.value, profile_name.value,
game_version.value, game_version.value,
loader.value, loader.value,
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable', loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
icon.value icon.value
) ).catch(handleError)
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
modal.value.hide() modal.value.hide()
creating.value = false creating.value = false
} catch (e) { } catch (e) {

View File

@ -16,11 +16,10 @@ import { add_project_from_version as installMod, check_installed, list } from '@
import { tauri } from '@tauri-apps/api' import { tauri } from '@tauri-apps/api'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import { useRouter } from 'vue-router'
import { create } from '@/helpers/profile' import { create } from '@/helpers/profile'
import { installVersionDependencies } from '@/helpers/utils' import { installVersionDependencies } from '@/helpers/utils'
import { handleError } from '@/store/notifications.js'
const router = useRouter()
const versions = ref([]) const versions = ref([])
const project = ref('') const project = ref('')
const installModal = ref(null) const installModal = ref(null)
@ -55,7 +54,7 @@ async function install(instance) {
) )
}) })
await installMod(instance.path, version.id) await installMod(instance.path, version.id).catch(handleError)
await installVersionDependencies(instance, version) await installVersionDependencies(instance, version)
instance.installedMod = true instance.installedMod = true
@ -63,7 +62,7 @@ async function install(instance) {
} }
async function getData() { async function getData() {
const projects = await list(true).then(Object.values) const projects = await list(true).then(Object.values).catch(handleError)
const filtered = projects const filtered = projects
.filter((profile) => { .filter((profile) => {
@ -83,7 +82,7 @@ async function getData() {
for (let profile of filtered) { for (let profile of filtered) {
profile.installing = false profile.installing = false
profile.installedMod = await check_installed(profile.path, project.value) profile.installedMod = await check_installed(profile.path, project.value).catch(handleError)
} }
return filtered return filtered
@ -130,11 +129,10 @@ const createInstance = async () => {
: 'vanilla', : 'vanilla',
'latest', 'latest',
icon.value icon.value
) ).catch(handleError)
await installMod(id, versions.value[0].id) await installMod(id, versions.value[0].id).catch(handleError)
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
installModal.value.hide() installModal.value.hide()
creatingInstance.value = false creatingInstance.value = false
} }

View File

@ -43,6 +43,7 @@ import {
find_jre_8_jres, find_jre_8_jres,
get_all_jre, get_all_jre,
} from '@/helpers/jre.js' } from '@/helpers/jre.js'
import { handleError } from '@/store/notifications.js'
const chosenInstallOptions = ref([]) const chosenInstallOptions = ref([])
const detectJavaModal = ref(null) const detectJavaModal = ref(null)
@ -52,14 +53,14 @@ defineExpose({
show: async (version, currentSelectedJava) => { show: async (version, currentSelectedJava) => {
if (version <= 8 && !!version) { if (version <= 8 && !!version) {
console.log(version) console.log(version)
chosenInstallOptions.value = await find_jre_8_jres() chosenInstallOptions.value = await find_jre_8_jres().catch(handleError)
} else if (version >= 18) { } else if (version >= 18) {
chosenInstallOptions.value = await find_jre_18plus_jres() chosenInstallOptions.value = await find_jre_18plus_jres().catch(handleError)
} else if (version) { } else if (version) {
chosenInstallOptions.value = await find_jre_17_jres() chosenInstallOptions.value = await find_jre_17_jres().catch(handleError)
} else { } else {
console.log('get all') console.log('get all')
chosenInstallOptions.value = await get_all_jre() chosenInstallOptions.value = await get_all_jre().catch(handleError)
} }
currentSelected.value = currentSelectedJava currentSelected.value = currentSelectedJava

View File

@ -3,7 +3,7 @@
<div class="toggle-setting"> <div class="toggle-setting">
<input <input
:disabled="props.disabled" :disabled="props.disabled"
:value="props.modelValue.path" :value="props.modelValue ? props.modelValue.path : ''"
type="text" type="text"
class="installation-input" class="installation-input"
:placeholder="placeholder ?? '/path/to/java'" :placeholder="placeholder ?? '/path/to/java'"
@ -25,7 +25,7 @@
Auto Detect Auto Detect
</Button> </Button>
<Button :disabled="props.disabled" @click="handleJavaFileInput()"> <Button :disabled="props.disabled" @click="handleJavaFileInput()">
<BrowseIcon /> <FolderSearchIcon />
Browse Browse
</Button> </Button>
<Button :disabled="props.disabled" @click="testJava"> <Button :disabled="props.disabled" @click="testJava">
@ -43,8 +43,15 @@
</template> </template>
<script setup> <script setup>
import { Button, SearchIcon, PlayIcon, CheckIcon, XIcon, AnimatedLogo } from 'omorphia' import {
import { BrowseIcon } from '@/assets/icons' Button,
SearchIcon,
PlayIcon,
CheckIcon,
XIcon,
AnimatedLogo,
FolderSearchIcon,
} from 'omorphia'
import { get_jre } from '@/helpers/jre.js' import { get_jre } from '@/helpers/jre.js'
import { ref } from 'vue' import { ref } from 'vue'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
@ -78,7 +85,7 @@ const testingJava = ref(false)
const testingJavaSuccess = ref(null) const testingJavaSuccess = ref(null)
async function testJava() { async function testJava() {
testingJava.value = true testingJava.value = true
let result = await get_jre(props.modelValue.path) let result = await get_jre(props.modelValue ? props.modelValue.path : '')
testingJava.value = false testingJava.value = false
testingJavaSuccess.value = !!result testingJavaSuccess.value = !!result

View File

@ -5,10 +5,10 @@
{{ currentProcesses[0].metadata.name }} {{ currentProcesses[0].metadata.name }}
</span> </span>
<Button icon-only class="icon-button stop" @click="stop()"> <Button icon-only class="icon-button stop" @click="stop()">
<StopIcon /> <StopCircleIcon />
</Button> </Button>
<Button icon-only class="icon-button" @click="goToTerminal()"> <Button icon-only class="icon-button" @click="goToTerminal()">
<TerminalIcon /> <TerminalSquareIcon />
</Button> </Button>
<Button <Button
v-if="currentLoadingBars.length > 0" v-if="currentLoadingBars.length > 0"
@ -47,8 +47,7 @@
</template> </template>
<script setup> <script setup>
import { Button, DownloadIcon, Card } from 'omorphia' import { Button, DownloadIcon, Card, StopCircleIcon, TerminalSquareIcon } from 'omorphia'
import { StopIcon, TerminalIcon } from '@/assets/icons'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import { import {
get_all_running_profiles as getRunningProfiles, get_all_running_profiles as getRunningProfiles,
@ -59,20 +58,21 @@ import { loading_listener, process_listener } from '@/helpers/events'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { progress_bars_list } from '@/helpers/state.js' import { progress_bars_list } from '@/helpers/state.js'
import ProgressBar from '@/components/ui/ProgressBar.vue' import ProgressBar from '@/components/ui/ProgressBar.vue'
import { handleError } from '@/store/notifications.js'
const router = useRouter() const router = useRouter()
const card = ref(null) const card = ref(null)
const infoButton = ref(null) const infoButton = ref(null)
const showCard = ref(false) const showCard = ref(false)
const currentProcesses = ref(await getRunningProfiles()) const currentProcesses = ref(await getRunningProfiles().catch(handleError))
await process_listener(async () => { await process_listener(async () => {
await refresh() await refresh()
}) })
const refresh = async () => { const refresh = async () => {
currentProcesses.value = await getRunningProfiles() currentProcesses.value = await getRunningProfiles().catch(handleError)
} }
const stop = async () => { const stop = async () => {
@ -89,7 +89,7 @@ const goToTerminal = () => {
router.push(`/instance/${encodeURIComponent(currentProcesses.value[0].path)}/logs`) router.push(`/instance/${encodeURIComponent(currentProcesses.value[0].path)}/logs`)
} }
const currentLoadingBars = ref(Object.values(await progress_bars_list())) const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
await loading_listener(async () => { await loading_listener(async () => {
await refreshInfo() await refreshInfo()
@ -97,7 +97,7 @@ await loading_listener(async () => {
const refreshInfo = async () => { const refreshInfo = async () => {
const currentLoadingBarCount = currentLoadingBars.value.length const currentLoadingBarCount = currentLoadingBars.value.length
currentLoadingBars.value = Object.values(await progress_bars_list()) currentLoadingBars.value = Object.values(await progress_bars_list().catch(handleError))
if (currentLoadingBars.value.length === 0) { if (currentLoadingBars.value.length === 0) {
showCard.value = false showCard.value = false
} else if (currentLoadingBarCount < currentLoadingBars.value.length) { } else if (currentLoadingBarCount < currentLoadingBars.value.length) {
@ -106,7 +106,6 @@ const refreshInfo = async () => {
} }
const handleClickOutside = (event) => { const handleClickOutside = (event) => {
console.log('clicked outside from appbar')
if ( if (
card.value && card.value &&
infoButton.value.$el !== event.target && infoButton.value.$el !== event.target &&

View File

@ -77,12 +77,10 @@ import { ref } from 'vue'
import { add_project_from_version as installMod, list } from '@/helpers/profile.js' import { add_project_from_version as installMod, list } from '@/helpers/profile.js'
import { install as packInstall } from '@/helpers/pack.js' import { install as packInstall } from '@/helpers/pack.js'
import { installVersionDependencies } from '@/helpers/utils.js' import { installVersionDependencies } from '@/helpers/utils.js'
import { ofetch } from 'ofetch' import { useFetch } from '@/helpers/fetch.js'
import { useRouter } from 'vue-router' import { handleError } from '@/store/notifications.js'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const router = useRouter()
const props = defineProps({ const props = defineProps({
backgroundImage: { backgroundImage: {
type: String, type: String,
@ -130,8 +128,9 @@ const installed = ref(
async function install() { async function install() {
installing.value = true installing.value = true
const versions = await ofetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${props.project.project_id}/version` `https://api.modrinth.com/v2/project/${props.project.project_id}/version`,
'project versions'
) )
let queuedVersionData let queuedVersionData
@ -146,15 +145,16 @@ async function install() {
} }
if (props.project.project_type === 'modpack') { if (props.project.project_type === 'modpack') {
const packs = Object.values(await list()) const packs = Object.values(await list().catch(handleError))
if ( if (
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === props.project.project_id) .find((pack) => pack.linked_data?.project_id === props.project.project_id)
) { ) {
let id = await packInstall(queuedVersionData.id, props.project.title, props.project.icon_url) await packInstall(queuedVersionData.id, props.project.title, props.project.icon_url).catch(
await router.push({ path: `/instance/${encodeURIComponent(id)}` }) handleError
)
} else { } else {
props.confirmModal.show(queuedVersionData.id) props.confirmModal.show(queuedVersionData.id)
} }
@ -170,7 +170,7 @@ async function install() {
installing.value = false installing.value = false
return return
} else { } else {
await installMod(props.instance.path, queuedVersionData.id) await installMod(props.instance.path, queuedVersionData.id).catch(handleError)
installVersionDependencies(props.instance, queuedVersionData) installVersionDependencies(props.instance, queuedVersionData)
} }
} else { } else {

View File

@ -0,0 +1,10 @@
import { ofetch } from 'ofetch'
import { handleError } from '@/store/state.js'
export const useFetch = async (url, item) => {
try {
return await ofetch(url)
} catch (err) {
handleError({ message: `Error fetching ${item}` })
}
}

View File

@ -1,5 +1,6 @@
import { add_project_from_version as installMod, check_installed } from '@/helpers/profile' import { add_project_from_version as installMod, check_installed } from '@/helpers/profile'
import { ofetch } from 'ofetch' import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
export const releaseColor = (releaseType) => { export const releaseColor = (releaseType) => {
switch (releaseType) { switch (releaseType) {
@ -17,19 +18,20 @@ export const releaseColor = (releaseType) => {
export const installVersionDependencies = async (profile, version) => { export const installVersionDependencies = async (profile, version) => {
for (const dep of version.dependencies) { for (const dep of version.dependencies) {
if (dep.version_id) { if (dep.version_id) {
if (await check_installed(profile.path, dep.project_id)) continue if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
await installMod(profile.path, dep.version_id) await installMod(profile.path, dep.version_id)
} else { } else {
if (await check_installed(profile.path, dep.project_id)) continue if (await check_installed(profile.path, dep.project_id).catch(handleError)) continue
const depVersions = await ofetch( const depVersions = await useFetch(
`https://api.modrinth.com/v2/project/${dep.project_id}/version` `https://api.modrinth.com/v2/project/${dep.project_id}/version`,
'dependency versions'
) )
const latest = depVersions.find( const latest = depVersions.find(
(v) => (v) =>
v.game_versions.includes(profile.metadata.game_version) && v.game_versions.includes(profile.metadata.game_version) &&
v.loaders.includes(profile.metadata.loader) v.loaders.includes(profile.metadata.loader)
) )
await installMod(profile.path, latest.id) await installMod(profile.path, latest.id).catch(handleError)
} }
} }
} }

View File

@ -1,6 +1,5 @@
<script setup> <script setup>
import { computed, onMounted, ref, watch } from 'vue' import { computed, onMounted, ref, watch } from 'vue'
import { ofetch } from 'ofetch'
import { import {
Pagination, Pagination,
Checkbox, Checkbox,
@ -18,7 +17,7 @@ import {
Promotion, Promotion,
} from 'omorphia' } from 'omorphia'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
import { useSearch } from '@/store/state' import { handleError, useSearch } from '@/store/state'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags' import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
@ -28,6 +27,7 @@ import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue' import SplashScreen from '@/components/ui/SplashScreen.vue'
import Instance from '@/components/ui/Instance.vue' import Instance from '@/components/ui/Instance.vue'
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue' import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
import { useFetch } from '@/helpers/fetch.js'
const route = useRoute() const route = useRoute()
@ -64,9 +64,9 @@ if (searchStore.projectType === 'modpack') {
onMounted(async () => { onMounted(async () => {
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([ ;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
get_categories(), get_categories().catch(handleError),
get_loaders(), get_loaders().catch(handleError),
get_game_versions(), get_game_versions().catch(handleError),
]) ])
breadcrumbs.setContext({ name: 'Browse', link: route.path }) breadcrumbs.setContext({ name: 'Browse', link: route.path })
if (searchStore.projectType === 'modpack') { if (searchStore.projectType === 'modpack') {
@ -94,7 +94,10 @@ const sortedCategories = computed(() => {
const getSearchResults = async () => { const getSearchResults = async () => {
const queryString = searchStore.getQueryString() const queryString = searchStore.getQueryString()
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`) const response = await useFetch(
`https://api.modrinth.com/v2/search${queryString}`,
'search results'
)
searchStore.setSearchResults(response) searchStore.setSearchResults(response)
} }

View File

@ -1,11 +1,12 @@
<script setup> <script setup>
import { ref, onUnmounted, shallowRef } from 'vue' import { ref, onUnmounted, shallowRef } from 'vue'
import { ofetch } from 'ofetch'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import RowDisplay from '@/components/RowDisplay.vue' import RowDisplay from '@/components/RowDisplay.vue'
import { list } from '@/helpers/profile.js' import { list } from '@/helpers/profile.js'
import { profile_listener } from '@/helpers/events' import { profile_listener } from '@/helpers/events'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
const featuredModpacks = ref({}) const featuredModpacks = ref({})
const featuredMods = ref({}) const featuredMods = ref({})
@ -20,7 +21,7 @@ const recentInstances = shallowRef([])
const getInstances = async () => { const getInstances = async () => {
filter.value = '' filter.value = ''
const profiles = await list(true) const profiles = await list(true).catch(handleError)
recentInstances.value = Object.values(profiles) recentInstances.value = Object.values(profiles)
const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id) const excludeIds = recentInstances.value.map((i) => i.metadata?.linked_data?.project_id)
@ -31,14 +32,16 @@ const getInstances = async () => {
} }
const getFeaturedModpacks = async () => { const getFeaturedModpacks = async () => {
const response = await ofetch( const response = await useFetch(
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}` `https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
'featured modpacks'
) )
featuredModpacks.value = response.hits featuredModpacks.value = response.hits
} }
const getFeaturedMods = async () => { const getFeaturedMods = async () => {
const response = await ofetch( const response = await useFetch(
`https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows&filters=${filter.value}` `https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows&filters=${filter.value}`,
'featured mods'
) )
featuredMods.value = response.hits featuredMods.value = response.hits
} }

View File

@ -5,17 +5,18 @@ import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { profile_listener } from '@/helpers/events.js' import { profile_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
const route = useRoute() const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
breadcrumbs.setRootContext({ name: 'Library', link: route.path }) breadcrumbs.setRootContext({ name: 'Library', link: route.path })
const profiles = await list(true) const profiles = await list(true).catch(handleError)
const instances = shallowRef(Object.values(profiles)) const instances = shallowRef(Object.values(profiles))
const unlisten = await profile_listener(async () => { const unlisten = await profile_listener(async () => {
const profiles = await list(true) const profiles = await list(true).catch(handleError)
instances.value = Object.values(profiles) instances.value = Object.values(profiles)
}) })
onUnmounted(() => unlisten()) onUnmounted(() => unlisten())

View File

@ -1,14 +1,14 @@
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { Card, Slider, DropdownSelect, Toggle } from 'omorphia' import { Card, Slider, DropdownSelect, Toggle } from 'omorphia'
import { useTheming } from '@/store/state' import { handleError, useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings' import { get, set } from '@/helpers/settings'
import { get_max_memory } from '@/helpers/jre' import { get_max_memory } from '@/helpers/jre'
import JavaSelector from '@/components/ui/JavaSelector.vue' import JavaSelector from '@/components/ui/JavaSelector.vue'
const themeStore = useTheming() const themeStore = useTheming()
const fetchSettings = await get() const fetchSettings = await get().catch(handleError)
if (!fetchSettings.java_globals?.JAVA_8) if (!fetchSettings.java_globals?.JAVA_8)
fetchSettings.java_globals.JAVA_8 = { path: '', version: '' } fetchSettings.java_globals.JAVA_8 = { path: '', version: '' }
@ -19,7 +19,7 @@ fetchSettings.javaArgs = fetchSettings.custom_java_args.join(' ')
fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ') fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ')
const settings = ref(fetchSettings) const settings = ref(fetchSettings)
const maxMemory = ref((await get_max_memory()) / 1024) const maxMemory = ref((await get_max_memory().catch(handleError)) / 1024)
watch(settings.value, async (oldSettings, newSettings) => { watch(settings.value, async (oldSettings, newSettings) => {
if (newSettings.java_globals?.JAVA_8?.path === '') { if (newSettings.java_globals?.JAVA_8?.path === '') {

View File

@ -50,7 +50,7 @@
</Button> </Button>
<!--TODO: https://github.com/tauri-apps/tauri/issues/4062 --> <!--TODO: https://github.com/tauri-apps/tauri/issues/4062 -->
<Button class="instance-button" icon-only @click="open({ defaultPath: instance.path })"> <Button class="instance-button" icon-only @click="open({ defaultPath: instance.path })">
<OpenFolderIcon /> <FolderOpenIcon />
</Button> </Button>
</span> </span>
</Card> </Card>
@ -82,8 +82,18 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import { BoxIcon, SettingsIcon, FileIcon, XIcon, Button, Avatar, Card, Promotion } from 'omorphia' import {
import { PlayIcon, OpenFolderIcon } from '@/assets/icons' BoxIcon,
SettingsIcon,
FileIcon,
XIcon,
Button,
Avatar,
Card,
Promotion,
PlayIcon,
FolderOpenIcon,
} from 'omorphia'
import { get, run } from '@/helpers/profile' import { get, run } from '@/helpers/profile'
import { import {
get_all_running_profile_paths, get_all_running_profile_paths,
@ -95,13 +105,13 @@ import { useRoute } from 'vue-router'
import { ref, onUnmounted } from 'vue' import { ref, onUnmounted } from 'vue'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { useBreadcrumbs, useLoading, useSearch } from '@/store/state' import { handleError, useBreadcrumbs, useLoading, useSearch } from '@/store/state'
const route = useRoute() const route = useRoute()
const searchStore = useSearch() const searchStore = useSearch()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
const instance = ref(await get(route.params.id)) const instance = ref(await get(route.params.id).catch(handleError))
searchStore.instanceContext = instance.value searchStore.instanceContext = instance.value
breadcrumbs.setName('Instance', instance.value.metadata.name) breadcrumbs.setName('Instance', instance.value.metadata.name)
@ -118,13 +128,13 @@ const loading = ref(false)
const startInstance = async () => { const startInstance = async () => {
loading.value = true loading.value = true
uuid.value = await run(route.params.id) uuid.value = await run(route.params.id).catch(handleError)
loading.value = false loading.value = false
playing.value = true playing.value = true
} }
const checkProcess = async () => { const checkProcess = async () => {
const runningPaths = await get_all_running_profile_paths() const runningPaths = await get_all_running_profile_paths().catch(handleError)
if (runningPaths.includes(instance.value.path)) { if (runningPaths.includes(instance.value.path)) {
playing.value = true playing.value = true
return return
@ -141,10 +151,10 @@ const stopInstance = async () => {
try { try {
if (!uuid.value) { if (!uuid.value) {
const uuids = await get_uuids_by_profile_path(instance.value.path) const uuids = await get_uuids_by_profile_path(instance.value.path).catch(handleError)
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
uuids.forEach(async (u) => await kill_by_uuid(u)) uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
} else await kill_by_uuid(uuid.value) } else await kill_by_uuid(uuid.value).catch(handleError)
} catch (err) { } catch (err) {
// Theseus currently throws: // Theseus currently throws:
// "Error launching Minecraft: Minecraft exited with non-zero code 1" error // "Error launching Minecraft: Minecraft exited with non-zero code 1" error
@ -155,7 +165,7 @@ const stopInstance = async () => {
const unlistenProfiles = await profile_listener(async (event) => { const unlistenProfiles = await profile_listener(async (event) => {
if (event.path === route.params.id) { if (event.path === route.params.id) {
instance.value = await get(route.params.id) instance.value = await get(route.params.id).catch(handleError)
} }
}) })

View File

@ -55,6 +55,7 @@ import calendar from 'dayjs/plugin/calendar'
import { get_stdout_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js' import { get_stdout_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { process_listener } from '@/helpers/events.js' import { process_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
dayjs.extend(calendar) dayjs.extend(calendar)
@ -68,19 +69,19 @@ const props = defineProps({
}) })
async function getLiveLog() { async function getLiveLog() {
const uuids = await get_uuids_by_profile_path(route.params.id) const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
let returnValue let returnValue
if (uuids.length === 0) { if (uuids.length === 0) {
returnValue = 'No live game detected. \nStart your game to proceed' returnValue = 'No live game detected. \nStart your game to proceed'
} else { } else {
returnValue = await get_stdout_by_uuid(uuids[0]) returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
} }
return { name: 'Live Log', stdout: returnValue, live: true } return { name: 'Live Log', stdout: returnValue, live: true }
} }
async function getLogs() { async function getLogs() {
return (await get_logs(props.instance.uuid, true)).reverse().map((log) => { return (await get_logs(props.instance.uuid, true).catch(handleError)).reverse().map((log) => {
log.name = dayjs( log.name = dayjs(
log.datetime_string.slice(0, 8) + 'T' + log.datetime_string.slice(9) log.datetime_string.slice(0, 8) + 'T' + log.datetime_string.slice(9)
).calendar() ).calendar()
@ -116,7 +117,7 @@ watch(selectedLogIndex, async (newIndex) => {
logs.value[newIndex].stdout = await get_stdout_by_datetime( logs.value[newIndex].stdout = await get_stdout_by_datetime(
props.instance.uuid, props.instance.uuid,
logs.value[newIndex].datetime_string logs.value[newIndex].datetime_string
) ).catch(handleError)
} }
}) })
@ -124,7 +125,10 @@ const deleteLog = async () => {
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) { if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
let deleteIndex = selectedLogIndex.value let deleteIndex = selectedLogIndex.value
selectedLogIndex.value = deleteIndex - 1 selectedLogIndex.value = deleteIndex - 1
await delete_logs_by_datetime(props.instance.uuid, logs.value[deleteIndex].datetime_string) await delete_logs_by_datetime(
props.instance.uuid,
logs.value[deleteIndex].datetime_string
).catch(handleError)
await setLogs() await setLogs()
} }
} }

View File

@ -92,6 +92,7 @@ import {
update_all, update_all,
update_project, update_project,
} from '@/helpers/profile.js' } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
const router = useRouter() const router = useRouter()
@ -200,7 +201,7 @@ async function updateAll() {
} }
} }
const paths = await update_all(props.instance.path) const paths = await update_all(props.instance.path).catch(handleError)
for (const [oldVal, newVal] of Object.entries(paths)) { for (const [oldVal, newVal] of Object.entries(paths)) {
const index = projects.value.findIndex((x) => x.path === oldVal) const index = projects.value.findIndex((x) => x.path === oldVal)
@ -219,7 +220,7 @@ async function updateAll() {
async function updateProject(mod) { async function updateProject(mod) {
mod.updating = true mod.updating = true
mod.path = await update_project(props.instance.path, mod.path) mod.path = await update_project(props.instance.path, mod.path).catch(handleError)
mod.updating = false mod.updating = false
mod.outdated = false mod.outdated = false
@ -228,11 +229,11 @@ async function updateProject(mod) {
} }
async function toggleDisableMod(mod) { async function toggleDisableMod(mod) {
mod.path = await toggle_disable_project(props.instance.path, mod.path) mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
} }
async function removeMod(mod) { async function removeMod(mod) {
await remove_project(props.instance.path, mod.path) await remove_project(props.instance.path, mod.path).catch(handleError)
projects.value = projects.value.filter((x) => mod.path !== x.path) projects.value = projects.value.filter((x) => mod.path !== x.path)
} }
</script> </script>

View File

@ -217,8 +217,8 @@ import {
DropdownSelect, DropdownSelect,
XIcon, XIcon,
SaveIcon, SaveIcon,
HammerIcon,
} from 'omorphia' } from 'omorphia'
import { HammerIcon } from '@/assets/icons'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { edit, edit_icon, get_optimal_jre_key, install, remove } from '@/helpers/profile.js' import { edit, edit_icon, get_optimal_jre_key, install, remove } from '@/helpers/profile.js'
import { computed, readonly, ref, shallowRef, watch } from 'vue' import { computed, readonly, ref, shallowRef, watch } from 'vue'
@ -229,6 +229,7 @@ import { convertFileSrc } from '@tauri-apps/api/tauri'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/api/dialog'
import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata.js' import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js' import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js'
const router = useRouter() const router = useRouter()
@ -244,7 +245,7 @@ const icon = ref(props.instance.metadata.icon)
async function resetIcon() { async function resetIcon() {
icon.value = null icon.value = null
await edit_icon(props.instance.path, null) await edit_icon(props.instance.path, null).catch(handleError)
} }
async function setIcon() { async function setIcon() {
@ -261,15 +262,15 @@ async function setIcon() {
if (!value) return if (!value) return
icon.value = value icon.value = value
await edit_icon(props.instance.path, icon.value) await edit_icon(props.instance.path, icon.value).catch(handleError)
} }
const globalSettings = await get() const globalSettings = await get().catch(handleError)
const javaSettings = props.instance.java ?? {} const javaSettings = props.instance.java ?? {}
const overrideJavaInstall = ref(!!javaSettings.override_version) const overrideJavaInstall = ref(!!javaSettings.override_version)
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path)) const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
const javaInstall = ref(optimalJava ?? javaSettings.override_version ?? { path: '', version: '' }) const javaInstall = ref(optimalJava ?? javaSettings.override_version ?? { path: '', version: '' })
const overrideJavaArgs = ref(!!javaSettings.extra_arguments) const overrideJavaArgs = ref(!!javaSettings.extra_arguments)
@ -282,7 +283,7 @@ const envVars = ref(
const overrideMemorySettings = ref(!!props.instance.memory) const overrideMemorySettings = ref(!!props.instance.memory)
const memory = ref(props.instance.memory ?? globalSettings.memory) const memory = ref(props.instance.memory ?? globalSettings.memory)
const maxMemory = (await get_max_memory()) / 1024 const maxMemory = (await get_max_memory().catch(handleError)) / 1024
const overrideWindowSettings = ref(!!props.instance.resolution) const overrideWindowSettings = ref(!!props.instance.resolution)
const resolution = ref(props.instance.resolution ?? globalSettings.game_resolution) const resolution = ref(props.instance.resolution ?? globalSettings.game_resolution)
@ -356,14 +357,14 @@ const repairing = ref(false)
async function repairProfile() { async function repairProfile() {
repairing.value = true repairing.value = true
await install(props.instance.path) await install(props.instance.path).catch(handleError)
repairing.value = false repairing.value = false
} }
const removing = ref(false) const removing = ref(false)
async function removeProfile() { async function removeProfile() {
removing.value = true removing.value = true
await remove(props.instance.path) await remove(props.instance.path).catch(handleError)
removing.value = false removing.value = false
await router.push({ path: '/' }) await router.push({ path: '/' })
@ -374,17 +375,18 @@ const showSnapshots = ref(false)
const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] = const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] =
await Promise.all([ await Promise.all([
get_fabric_versions().then(shallowRef), get_fabric_versions().then(shallowRef).catch(handleError),
get_forge_versions().then(shallowRef), get_forge_versions().then(shallowRef).catch(handleError),
get_quilt_versions().then(shallowRef), get_quilt_versions().then(shallowRef).catch(handleError),
get_game_versions().then(shallowRef), get_game_versions().then(shallowRef).catch(handleError),
get_loaders() get_loaders()
.then((value) => .then((value) =>
value value
.filter((item) => item.supported_project_types.includes('modpack')) .filter((item) => item.supported_project_types.includes('modpack'))
.map((item) => item.name.toLowerCase()) .map((item) => item.name.toLowerCase())
) )
.then(ref), .then(ref)
.catch(handleError),
]) ])
loaders.value.push('vanilla') loaders.value.push('vanilla')
@ -449,7 +451,7 @@ async function saveGvLoaderEdits() {
if (loader.value !== 'vanilla') { if (loader.value !== 'vanilla') {
editProfile.metadata.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value] editProfile.metadata.loader_version = selectableLoaderVersions.value[loaderVersionIndex.value]
} }
await edit(props.instance.path, editProfile) await edit(props.instance.path, editProfile).catch(handleError)
await repairProfile() await repairProfile()
editing.value = false editing.value = false

View File

@ -240,7 +240,6 @@ import { install as packInstall } from '@/helpers/pack'
import { list, add_project_from_version as installMod, check_installed } from '@/helpers/profile' import { list, add_project_from_version as installMod, check_installed } from '@/helpers/profile'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { ofetch } from 'ofetch'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import { ref, shallowRef, watch } from 'vue' import { ref, shallowRef, watch } from 'vue'
import { installVersionDependencies } from '@/helpers/utils' import { installVersionDependencies } from '@/helpers/utils'
@ -250,6 +249,8 @@ import Instance from '@/components/ui/Instance.vue'
import { useSearch } from '@/store/search' import { useSearch } from '@/store/search'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue' import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
const searchStore = useSearch() const searchStore = useSearch()
@ -264,15 +265,23 @@ const instance = ref(searchStore.instanceContext)
const installing = ref(false) const installing = ref(false)
const [data, versions, members, dependencies, categories, loaders] = await Promise.all([ const [data, versions, members, dependencies, categories, loaders] = await Promise.all([
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}`).then(shallowRef), useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project').then(shallowRef),
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`).then(shallowRef), useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project').then(
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`).then(shallowRef), shallowRef
ofetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`).then(shallowRef), ),
get_loaders().then(ref), useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`, 'project').then(
get_categories().then(ref), shallowRef
),
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project').then(
shallowRef
),
get_loaders().then(ref).catch(handleError),
get_categories().then(ref).catch(handleError),
]) ])
const installed = ref(instance.value && (await check_installed(instance.value.path, data.value.id))) const installed = ref(
instance.value && (await check_installed(instance.value.path, data.value.id).catch(handleError))
)
breadcrumbs.setName('Project', data.value.title) breadcrumbs.setName('Project', data.value.title)
@ -306,14 +315,16 @@ async function install(version) {
} }
if (data.value.project_type === 'modpack') { if (data.value.project_type === 'modpack') {
const packs = Object.values(await list(true)) const packs = Object.values(await list(true).catch(handleError))
if ( if (
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === data.value.id) .find((pack) => pack.linked_data?.project_id === data.value.id)
) { ) {
await packInstall(queuedVersionData.id, data.value.title, data.value.icon_url) await packInstall(queuedVersionData.id, data.value.title, data.value.icon_url).catch(
handleError
)
} else { } else {
confirmModal.value.show(queuedVersionData.id, data.value.title, data.value.icon_url) confirmModal.value.show(queuedVersionData.id, data.value.title, data.value.icon_url)
} }
@ -340,7 +351,7 @@ async function install(version) {
return return
} else { } else {
queuedVersionData = selectedVersion queuedVersionData = selectedVersion
await installMod(instance.value.path, selectedVersion.id) await installMod(instance.value.path, selectedVersion.id).catch(handleError)
installVersionDependencies(instance.value, queuedVersionData) installVersionDependencies(instance.value, queuedVersionData)
} }
} else { } else {
@ -354,7 +365,7 @@ async function install(version) {
: true) : true)
) )
if (compatible) { if (compatible) {
await installMod(instance.value.path, queuedVersionData.id) await installMod(instance.value.path, queuedVersionData.id).catch(handleError)
await installVersionDependencies(instance.value, queuedVersionData) await installVersionDependencies(instance.value, queuedVersionData)
} else { } else {
incompatibilityWarning.value.show( incompatibilityWarning.value.show(

View File

@ -0,0 +1,25 @@
import { defineStore } from 'pinia'
export const useNotifications = defineStore('notificationsStore', {
state: () => ({
notificationsWrapper: null,
}),
actions: {
setNotifs(notifs) {
this.notificationsWrapper = notifs
},
addNotification(notif) {
this.notificationsWrapper.addNotification(notif)
},
},
})
export const handleError = (err) => {
const notifs = useNotifications()
notifs.addNotification({
title: 'An error occurred',
text: err.message ?? err,
type: 'error',
})
console.error(err)
}

View File

@ -2,5 +2,6 @@ import { useSearch } from './search'
import { useTheming } from './theme' import { useTheming } from './theme'
import { useBreadcrumbs } from './breadcrumbs' import { useBreadcrumbs } from './breadcrumbs'
import { useLoading } from './loading' import { useLoading } from './loading'
import { useNotifications, handleError } from './notifications'
export { useSearch, useTheming, useBreadcrumbs, useLoading } export { useSearch, useTheming, useBreadcrumbs, useLoading, useNotifications, handleError }

View File

@ -1149,10 +1149,10 @@ ofetch@^1.0.1:
node-fetch-native "^1.0.2" node-fetch-native "^1.0.2"
ufo "^1.1.0" ufo "^1.1.0"
omorphia@^0.4.16: omorphia@^0.4.17:
version "0.4.16" version "0.4.17"
resolved "https://registry.yarnpkg.com/omorphia/-/omorphia-0.4.16.tgz#933a1e28c926cc10da1b0f4f1444c7a563f5a7b4" resolved "https://registry.yarnpkg.com/omorphia/-/omorphia-0.4.17.tgz#b1a661cee835e8238df3a5f33dca7253adcf4037"
integrity sha512-bYAsyDuLgJtuL+/srxYf0wEILLH34LDb8st5L9+MEt1YqlWw5Bk8aQX8O2sOZNWji1V4Zr+NRGIRRXz7iiz+4w== integrity sha512-XcMYz2LaHDcV8AoSRj/RbBfASonZhK14WJC5YvRIyr+wemb7o56MG8X9fGHNmgK1e/vHt6DpGQNITUYfmo48Cg==
dependencies: dependencies:
dayjs "^1.11.7" dayjs "^1.11.7"
floating-vue "^2.0.0-beta.20" floating-vue "^2.0.0-beta.20"