Skip to content
Merged
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
423 changes: 337 additions & 86 deletions apps/app-frontend/src/App.vue

Large diffs are not rendered by default.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/app-frontend/src/assets/stylesheets/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@
);
}

html.light-mode {
color-scheme: light;
}

body {
position: fixed;
width: 100%;
Expand Down
9 changes: 2 additions & 7 deletions apps/app-frontend/src/components/ui/AccountsCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
<div class="flex gap-2 w-full min-w-0">
<Avatar
size="36px"
disable-conditional-icon-padding
:src="
selectedAccount
? avatarUrl
Expand All @@ -35,7 +34,7 @@
</div>
</div>
</template>
<div class="bg-button-bg pt-1 pb-2 border border-solid border-surface-5">
<div class="bg-button-bg pt-1 pb-2 border-0 border-t border-solid border-surface-5">
<template v-if="accounts.length > 0">
<div v-for="account in accounts" :key="account.profile.id" class="flex gap-1 items-center">
<button
Expand All @@ -47,11 +46,7 @@
class="w-5 h-5 text-brand shrink-0"
/>
<RadioButtonIcon v-else class="w-5 h-5 text-secondary shrink-0" />
<Avatar
:src="getAccountAvatarUrl(account)"
size="24px"
disable-conditional-icon-padding
/>
<Avatar :src="getAccountAvatarUrl(account)" size="24px" />
<p
class="m-0 truncate min-w-0"
:class="
Expand Down
20 changes: 20 additions & 0 deletions apps/app-frontend/src/components/ui/ErrorModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
CheckIcon,
CopyIcon,
DropdownIcon,
FolderOpenIcon,
HammerIcon,
LogInIcon,
UpdatedIcon,
Expand All @@ -13,8 +14,10 @@ import {
Button,
ButtonLink,
Collapsible,
defineMessages,
IconButton,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
import { computed, ref } from 'vue'

Expand All @@ -25,8 +28,17 @@ import { trackEvent } from '@/helpers/analytics'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { install_existing_instance } from '@/helpers/install'
import { cancel_directory_change } from '@/helpers/settings.ts'
import { showAppDbBackupsFolder } from '@/helpers/utils.js'

const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl()

const messages = defineMessages({
openBackupsFolder: {
id: 'app.error.state-init.open-backups-folder',
defaultMessage: 'Open backups folder',
},
})

const errorModal = ref()
const error = ref()
Expand Down Expand Up @@ -123,6 +135,10 @@ function retryDirectoryChange() {
window.location.reload()
}

async function openDbBackupsFolder() {
await showAppDbBackupsFolder().catch(handleError)
}

const loadingRepair = ref(false)
async function repairInstance() {
loadingRepair.value = true
Expand Down Expand Up @@ -310,6 +326,10 @@ async function copyToClipboard(text) {
class="m-0 p-0 rounded-none bg-transparent text-sm font-mono break-words overflow-auto"
>
{{ debugInfo }}
<button class="btn" @click="openDbBackupsFolder">
<FolderOpenIcon aria-hidden="true" />
{{ formatMessage(messages.openBackupsFolder) }}
</button>
</div>
<IconButton
v-tooltip="'Copy debug info'"
Expand Down
2 changes: 2 additions & 0 deletions apps/app-frontend/src/components/ui/Instance.vue
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ onMounted(() => {
:src="getInstanceIconUrl(instance.icon_path)"
:tint-by="instance.id"
:alt="formatMessage(messages.instanceIcon)"
pad-transparent-corners
/>
<div class="h-full flex items-center font-bold text-contrast leading-normal">
<span class="line-clamp-2">{{ instance.name }}</span>
Expand Down Expand Up @@ -244,6 +245,7 @@ onMounted(() => {
:tint-by="instance.id"
:alt="formatMessage(messages.instanceIcon)"
:class="`transition-all ${modLoading || installing ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
pad-transparent-corners
/>
<div class="absolute inset-0 flex items-center justify-center">
<IconButton
Expand Down
7 changes: 6 additions & 1 deletion apps/app-frontend/src/components/ui/InstanceIndicator.vue
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ const instanceLink = computed(() => {
<div class="flex justify-between items-center border-0 border-b border-solid border-divider pb-4">
<router-link :to="instanceLink" tabindex="-1" class="flex flex-col gap-4 text-primary">
<span class="flex items-center gap-2">
<Avatar :src="getInstanceIconUrl(instance.icon_path)" :alt="instance.name" size="48px" />
<Avatar
:src="getInstanceIconUrl(instance.icon_path)"
:alt="instance.name"
size="48px"
pad-transparent-corners
/>
<span class="flex flex-col gap-2">
<span class="font-extrabold bold text-contrast">
{{ instance.name }}
Expand Down
102 changes: 99 additions & 3 deletions apps/app-frontend/src/components/ui/QuickInstanceSwitcher.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
<script setup>
import { SpinnerIcon } from '@modrinth/assets'
import { Avatar, defineMessages, injectNotificationManager, useVIntl } from '@modrinth/ui'
import { EyeIcon, FolderOpenIcon, PlayIcon, SpinnerIcon, StopCircleIcon } from '@modrinth/assets'
import {
Avatar,
commonMessages,
ContextMenu,
defineMessages,
injectNotificationManager,
useVIntl,
} from '@modrinth/ui'
import { useQueryClient } from '@tanstack/vue-query'
import dayjs from 'dayjs'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'

import NavButton from '@/components/ui/NavButton.vue'
import { useAppEvent } from '@/composables/use-app-event'
import { getInstanceIconUrl, list } from '@/helpers/instance'
import { handleSevereError } from '@/composables/use-error.js'
import { trackEvent } from '@/helpers/analytics'
import { getInstanceIconUrl, kill, list, run } from '@/helpers/instance'
import { get_all } from '@/helpers/process'
import { showInstanceInFolder } from '@/helpers/utils'
import { instanceKeys } from '@/pages/instance/query-options'

const ITEM_SIZE = 52
Expand All @@ -16,6 +28,9 @@ const STORAGE_KEY = 'modrinth-quick-instance-count'

const { handleError } = injectNotificationManager()
const queryClient = useQueryClient()
const router = useRouter()
const instanceOptions = ref()
const runningInstances = ref([])

const { formatMessage } = useVIntl()

Expand Down Expand Up @@ -153,8 +168,11 @@ useAppEvent('instance', async (event) => {
}
})

useAppEvent('process', checkProcesses)

onMounted(() => {
window.addEventListener('resize', updateMaxAuto)
checkProcesses()
})

onUnmounted(() => {
Expand All @@ -172,6 +190,18 @@ const messages = defineMessages({
id: 'app.quick-instance-switcher.drag-show-tooltip',
defaultMessage: 'Drag to show recent instances',
},
viewInstance: {
id: 'app.quick-instance-switcher.view-instance',
defaultMessage: 'View instance',
},
instanceActions: {
id: 'app.quick-instance-switcher.actions.label',
defaultMessage: 'Instance actions',
},
instanceLocked: {
id: 'app.quick-instance-switcher.instance-locked',
defaultMessage: 'This instance has been locked',
},
})

const dividerTooltip = computed(() => {
Expand All @@ -180,6 +210,69 @@ const dividerTooltip = computed(() => {
}
return formatMessage(visibleCount.value === 0 ? messages.dragShowTooltip : messages.dragTooltip)
})

async function checkProcesses() {
const processes = (await get_all().catch(handleError)) ?? []
runningInstances.value = processes.map((process) => process.instance_id)
}

async function playInstance(instance) {
if (instance.quarantined || instance.install_stage !== 'installed') return
await run(instance.id)
.catch((err) => handleSevereError(err, { instanceId: instance.id }))
.finally(() => {
trackEvent('InstanceStart', {
loader: instance.loader,
game_version: instance.game_version,
source: 'QuickInstanceSwitcher',
})
})
}

async function stopInstance(instance) {
await kill(instance.id).catch(handleError)
trackEvent('InstanceStop', {
loader: instance.loader,
game_version: instance.game_version,
source: 'QuickInstanceSwitcher',
})
}

function openContextMenu(event, instance) {
const playing = runningInstances.value.includes(instance.id)
instanceOptions.value?.open(event, [
playing
? {
id: 'stop',
label: formatMessage(commonMessages.stopButton),
icon: StopCircleIcon,
tone: 'red',
action: () => stopInstance(instance),
}
: {
id: 'play',
label: formatMessage(commonMessages.playButton),
icon: PlayIcon,
tone: 'brand',
disabled: instance.quarantined || instance.install_stage !== 'installed',
tooltip: instance.quarantined ? formatMessage(messages.instanceLocked) : undefined,
action: () => playInstance(instance),
},
{ type: 'divider' },
{
id: 'open-instance',
label: formatMessage(messages.viewInstance),
icon: EyeIcon,
action: () => router.push(`/instance/${encodeURIComponent(instance.id)}`),
},
{
id: 'open-folder',
label: formatMessage(commonMessages.openFolderButton),
icon: FolderOpenIcon,
action: () => showInstanceInFolder(instance.id),
},
])
}
</script>

<template>
Expand All @@ -197,13 +290,15 @@ const dividerTooltip = computed(() => {
:key="instance.id"
v-tooltip.right="instance.name"
class="quick-instance-item"
@contextmenu.prevent.stop="(event) => openContextMenu(event, instance)"
>
<NavButton :to="`/instance/${encodeURIComponent(instance.id)}`" class="relative">
<Avatar
:src="getInstanceIconUrl(instance.icon_path)"
size="28px"
:tint-by="instance.id"
:class="`transition-all ${instance.install_stage !== 'installed' ? `brightness-[0.25] scale-[0.85]` : `group-hover:brightness-75`}`"
pad-transparent-corners
/>
<div
v-if="instance.install_stage !== 'installed'"
Expand All @@ -214,6 +309,7 @@ const dividerTooltip = computed(() => {
</NavButton>
</div>
</TransitionGroup>
<ContextMenu ref="instanceOptions" :label="formatMessage(messages.instanceActions)" />
<div
v-tooltip.right="dividerTooltip"
class="flex items-center justify-center py-2 select-none"
Expand Down
30 changes: 24 additions & 6 deletions apps/app-frontend/src/components/ui/SplashScreen.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<Transition name="splash-fade" @after-leave="onAfterLeave">
<div v-if="!doneLoading" class="splash-screen dark">
<div v-if="!doneLoading" class="splash-screen" :class="`${theme.active}-mode`">
<div class="app-logo-wrapper" data-tauri-drag-region>
<svg
class="app-logo"
Expand Down Expand Up @@ -83,6 +83,9 @@ import { ref, watch } from 'vue'

import ProgressBar from '@/components/ui/ProgressBar.vue'
import { useAppEvent } from '@/composables/use-app-event'
import { useTheme } from '@/composables/use-theme.ts'

const theme = useTheme()

const doneLoading = ref(false)
const loadingProgress = ref(0)
Expand Down Expand Up @@ -145,6 +148,12 @@ useAppEvent('loading', (e) => {
position: fixed;
inset: 0;
z-index: 10000;

--splash-cube-image: url('@/assets/loading/cube.png');

&.light-mode {
--splash-cube-image: url('@/assets/loading/cube-light.webp');
}
}

.splash-fade-leave-active {
Expand All @@ -166,6 +175,7 @@ useAppEvent('loading', (e) => {
align-items: center;

gap: 1rem;
color: var(--color-contrast);

z-index: 9998;
}
Expand All @@ -184,8 +194,8 @@ useAppEvent('loading', (e) => {
height: 100vh;
width: 100vw;
background:
linear-gradient(180deg, rgba(66, 131, 92, 0.275) 0%, rgba(17, 35, 43, 0.5) 97.29%),
linear-gradient(0deg, rgba(22, 24, 28, 0.64), rgba(22, 24, 28, 0.64));
linear-gradient(180deg, var(--splash-tint-top) 0%, var(--splash-tint-bottom) 97.29%),
linear-gradient(0deg, var(--splash-overlay), var(--splash-overlay));
z-index: 9997;
}

Expand All @@ -198,11 +208,19 @@ useAppEvent('loading', (e) => {

width: 180vw;
height: 180vh;
opacity: 0.8;
background: #16181c url('@/assets/loading/cube.png') center no-repeat;
background-size: contain;
background-color: var(--color-bg);

z-index: 9996;

&::after {
content: '';
position: absolute;
inset: 0;
background: var(--splash-cube-image) center no-repeat;
background-size: contain;
opacity: var(--splash-cube-opacity);
mix-blend-mode: var(--splash-cube-blend);
}
}

.base-bg {
Expand Down
Loading
Loading