mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 09:34:50 +00:00
* refactor: remove unused legacy onboarded flag * sqlx prepare * feat: add onboarding checklist app db and add vue provider * feat: add welcome page * feat: add onboarding checklist component * feat: move checklist visibility source of truth in app db * feat: split up import modpack button * feat-small: style tweak * feat: remove discover section from homepage and add library * refactor: move welcome screen to component * refactor: move grid display into /library * refactor: rename existing components * refactor: split up the current library page files * feat: add create group modal * feat: add instance group definitions * feat: update accordian for instance groups * feat: add delete group * feat: implement rename instance groups * refactor: move instance group api out * feat: add context menu for groups * feat: style instance items in library * feat: add selected instance item state * feat: implement action bar for selection * feat: drag and drop to move between groups * feat: show ungrouped with header * feat: improve edit group name * small fix * small style fixes * feat: improve selection behaviour * fix: dont toggle group when context menu open * feat: drag and drop multiple selected * small * feat: add remove from group in action bar * feat: add delay on icon hiding in inline input * feat: improve icon hiding delay * fix: bad routes.js * feat: implement sort and filter options * refactor: dont prefix filenames * feat: change open/collapse group target * feat: improve ungrouped behaviour * feat: add shift to select multiple instances * fix: icon disappear transition * feat: add esc while dragging to cancel * feat: update sorting and moved label * feat: update context menu * fix: multiple context menus can be opened * refactor: instance groups to be identified by ID instead of name, names become display data * feat: creating new group always adds instance * feat: create group button, fix instance selection, and increase group name max length * feat: strokes * fix: show getting started checklist in all sidebar * feat: set size on microsoft login screen * fix: use ButtonStyled in Chips component * feat: creating groups with selected instances and fix some moving selection and duplication bugs * pnpm prepr * feat: add tooltip for instance already in group * feat: create instance with content in creation flow modal - search modpack becomes search projects - creation flow modal context receives prepareProjectInstall and createProjectInstall callbacks, from content-install.ts (shared with ContentInstallModal) * feat: update instance card styles * refactor: separate out instance card content * update styles * refactor: instance card * fix: width of instance * feat: update empty state * feat: update sort and grouping dropdowns * feat: better context menu options * feat: add group action buttons * feat: add/remove favourites group * remove manual sort * feat: improve the "New group" jank * feat: add group by instance type and change group sorting * feat: welcome screen styles match figma * feat: not signed in skins demo * feat: new hosting empty state * feat: new instance card style * feat: add tooltip and spice up animation more * feat: improve the transitions for groups after dropping many instances * feat: set instance group membership for bulk moving * feat: remove fade out * fix: allow dragging from buttons inside instance card * fix: library page load in behaviour * small fix * feat: add group ordering and reordering * feat: add drag to reorder groups * fix: switching page not clearing selected instances * style fixes * style fix * fix spacing * feat: jump in changes * feat: add styled newly created instances * feat: improve jump in cards styles * fix: welcome screen style * fix: empty state styles * feat: max 5 jump in items * feat: max new instances to 3 * fix: search icon * feat: clear selection after move * feat: skins list use check icon styles * feat: list will be deleted instances and default sort last played * fix: stroke * remove: no more group settings in instance general settings * fix: resolving modpack server install * feat: icon editor * feat: add three hue shifted variants * fix: surprise me doesnt pick symbol * feat: add customize icon to create instance modal * feat: skins loading state style * fix: welcome logo * fix: copy * fix: left over usage of ButtonStyled from main's refactor * feat: some leaked merge stuff * feat: server empty state styles * remove: instance helper text * remove: auto link in set up stage * feat: auto focus on search when modal open * fix: delete instance modal style * remove: modpack meta repeating * fix: icon * fix: font weight * fix: dropdown filter preview heights * pnpm prepr * fix: sign into modrinth flashing change in position * feat: add avatar transparent corner gets padding * feat: drag and hold over group opens group * feat: add random icon for custom instance * feat: update loading icon * polish: empty state for search * polish: jump in cards * fix: avatar could skip checking for image if tauri loads it first * feat: add icon customize and randomizer in content install modal for add to new instances * fix: avatar cannot read image pixels due to cors * refactor: popup notifications toast type for instance sharing notifications * feat: apply random icons modal and add popup notification for it * polish: style * fix: set_webview_visible * QA * qa * feat: add inner white 15 opacity stroke around avatar * add to catalog * pnpm prepr * fix: app event import * feat: add new icons and backgrounds for icon editor * feat: recents list generate from randomized icons and save icon in editor + icon editor modal fixes * qa * refactor: recipe -> config * fix: event bus * polish * feat: add safe guard against randomizing empty icons * feat: navigate to instance after creation * fix: icon file names * i18n on welcome screen * i18n pass on PR * fix: inconsistent max group name length * refactor: use sqlx macro * feat: add instance icon background selection drag * feat: update catalog order * fix: remove gradient for welcome screen * remove: frog * fix: min width * feat: update corner size * fix: context menu styles * prepr * fix: instance name trimming * reorder * fix: instance card loading state stayed until hovering * feat: add icon randomizer blacklist * blog * prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
589 lines
16 KiB
Vue
589 lines
16 KiB
Vue
<template>
|
|
<div class="flex gap-2 items-center">
|
|
<div v-if="downloadState.total > 0 || hasActiveLoadingBars" class="relative">
|
|
<IconButton
|
|
v-tooltip="downloadToggleLabel"
|
|
:color="downloadState.hidden > 0 ? 'brand' : undefined"
|
|
type="quiet"
|
|
:label="downloadToggleLabel"
|
|
@click="toggleDownloadNotifications"
|
|
>
|
|
<DownloadIcon />
|
|
</IconButton>
|
|
</div>
|
|
<div v-if="offline" class="flex items-center gap-1">
|
|
<UnplugIcon class="text-secondary" />
|
|
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
|
|
</div>
|
|
<AppUpdateButton />
|
|
<div
|
|
class="flex border-solid border-surface-5 text-sm items-center gap-2 py-1.5 px-3 rounded-xl border"
|
|
>
|
|
<template v-if="selectedProcess">
|
|
<OnlineIndicatorIcon />
|
|
<div class="text-contrast flex items-center gap-2">
|
|
<router-link
|
|
v-tooltip="formatMessage(messages.viewInstance)"
|
|
:to="`/instance/${encodeURIComponent(selectedProcess.instance.id)}`"
|
|
class="hover:underline"
|
|
>
|
|
{{ selectedProcess.instance.name }}
|
|
</router-link>
|
|
<Dropdown
|
|
v-if="currentProcesses.length > 1"
|
|
placement="bottom"
|
|
:triggers="['click']"
|
|
:hide-triggers="['click']"
|
|
@show="showInstances = true"
|
|
@hide="showInstances = false"
|
|
>
|
|
<IconButton
|
|
v-tooltip="
|
|
showInstances
|
|
? formatMessage(messages.hideMoreRunningInstances)
|
|
: formatMessage(messages.showMoreRunningInstances)
|
|
"
|
|
class="!size-6"
|
|
type="quiet"
|
|
size="xs"
|
|
:label="
|
|
showInstances
|
|
? formatMessage(messages.hideMoreRunningInstances)
|
|
: formatMessage(messages.showMoreRunningInstances)
|
|
"
|
|
>
|
|
<DropdownIcon :class="{ 'rotate-180': !!showInstances }" />
|
|
</IconButton>
|
|
<template #popper>
|
|
<div class="flex w-[20rem] max-h-[24rem] flex-col gap-2 overflow-auto">
|
|
<div
|
|
v-for="process in currentProcesses"
|
|
:key="process.uuid"
|
|
class="flex w-full items-center gap-2 rounded-xl bg-surface-4 p-2 text-sm"
|
|
>
|
|
<button
|
|
v-tooltip.left="
|
|
process.uuid === selectedProcess.uuid
|
|
? formatMessage(messages.primaryInstance)
|
|
: formatMessage(messages.makePrimaryInstance)
|
|
"
|
|
class="flex flex-grow items-center gap-2"
|
|
:class="{
|
|
'active:scale-95 transition-transform': process.uuid !== selectedProcess.uuid,
|
|
}"
|
|
:disabled="process.uuid === selectedProcess.uuid"
|
|
@click="selectProcess(process)"
|
|
>
|
|
<OnlineIndicatorIcon />
|
|
<span class="mr-auto text-contrast flex items-center gap-2">
|
|
{{ process.instance.name }}
|
|
<StarIcon v-if="process.uuid === selectedProcess.uuid" class="text-orange" />
|
|
</span>
|
|
</button>
|
|
<button
|
|
v-tooltip="formatMessage(messages.stopInstance)"
|
|
class="active:scale-95 flex"
|
|
@click.stop="stop(process)"
|
|
>
|
|
<StopCircleIcon class="text-red size-5" />
|
|
</button>
|
|
<button
|
|
v-tooltip="formatMessage(messages.viewLogs)"
|
|
class="active:scale-95 flex"
|
|
@click.stop="goToTerminal(process.instance.id)"
|
|
>
|
|
<TerminalSquareIcon class="text-secondary size-5" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</Dropdown>
|
|
</div>
|
|
<button
|
|
v-tooltip="formatMessage(messages.stopInstance)"
|
|
class="active:scale-95 flex"
|
|
@click="stop(selectedProcess)"
|
|
>
|
|
<StopCircleIcon class="text-red size-5" />
|
|
</button>
|
|
<button
|
|
v-tooltip="formatMessage(messages.viewLogs)"
|
|
class="active:scale-95 flex"
|
|
@click="goToTerminal()"
|
|
>
|
|
<TerminalSquareIcon class="text-secondary size-5" />
|
|
</button>
|
|
</template>
|
|
<template v-else>
|
|
<span class="size-2 rounded-full bg-secondary" />
|
|
<span class="text-secondary"> {{ formatMessage(messages.noInstancesRunning) }} </span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import {
|
|
DownloadIcon,
|
|
DropdownIcon,
|
|
OnlineIndicatorIcon,
|
|
StarIcon,
|
|
StopCircleIcon,
|
|
TerminalSquareIcon,
|
|
UnplugIcon,
|
|
} from '@modrinth/assets'
|
|
import { IconButton } from '@modrinth/ui'
|
|
import {
|
|
defineMessages,
|
|
injectNotificationManager,
|
|
injectPopupNotificationManager,
|
|
type PopupNotificationProgressItem,
|
|
type PopupNotificationStandard,
|
|
useVIntl,
|
|
} from '@modrinth/ui'
|
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
|
import { Dropdown } from 'floating-vue'
|
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
|
import { useRouter } from 'vue-router'
|
|
|
|
import AppUpdateButton from '@/components/ui/app-update-button/index.vue'
|
|
import { useInstallJobNotifications } from '@/composables/browse/install-job-notifications'
|
|
import { useAppEvent } from '@/composables/use-app-event'
|
|
import { trackEvent } from '@/helpers/analytics'
|
|
import { toError } from '@/helpers/errors'
|
|
import { get_many as getInstances } from '@/helpers/instance'
|
|
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
|
import type { LoadingBar } from '@/helpers/state'
|
|
import { progress_bars_list } from '@/helpers/state'
|
|
import type { GameInstance } from '@/helpers/types'
|
|
|
|
const { handleError } = injectNotificationManager()
|
|
const popupNotificationManager = injectPopupNotificationManager()
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const router = useRouter()
|
|
|
|
const showInstances = ref(false)
|
|
|
|
interface RunningProcess {
|
|
uuid: string
|
|
instance_id: string
|
|
instance: GameInstance
|
|
}
|
|
|
|
const messages = defineMessages({
|
|
offline: {
|
|
id: 'app.action-bar.offline',
|
|
defaultMessage: 'Offline',
|
|
},
|
|
viewInstance: {
|
|
id: 'app.action-bar.view-instance',
|
|
defaultMessage: 'View instance',
|
|
},
|
|
showMoreRunningInstances: {
|
|
id: 'app.action-bar.show-more-running-instances',
|
|
defaultMessage: 'Show more running instances',
|
|
},
|
|
hideMoreRunningInstances: {
|
|
id: 'app.action-bar.hide-more-running-instances',
|
|
defaultMessage: 'Hide more running instances',
|
|
},
|
|
primaryInstance: {
|
|
id: 'app.action-bar.primary-instance',
|
|
defaultMessage: 'Primary instance',
|
|
},
|
|
makePrimaryInstance: {
|
|
id: 'app.action-bar.make-primary-instance',
|
|
defaultMessage: 'Make primary instance',
|
|
},
|
|
stopInstance: {
|
|
id: 'app.action-bar.stop-instance',
|
|
defaultMessage: 'Stop instance',
|
|
},
|
|
viewLogs: {
|
|
id: 'app.action-bar.view-logs',
|
|
defaultMessage: 'View logs',
|
|
},
|
|
noInstancesRunning: {
|
|
id: 'app.action-bar.no-instances-running',
|
|
defaultMessage: 'No instances running',
|
|
},
|
|
downloadingJava: {
|
|
id: 'app.action-bar.downloading-java',
|
|
defaultMessage: 'Downloading Java {version}',
|
|
},
|
|
downloads: {
|
|
id: 'app.action-bar.downloads',
|
|
defaultMessage: 'Downloads',
|
|
},
|
|
viewActiveDownloads: {
|
|
id: 'app.action-bar.view-active-downloads',
|
|
defaultMessage: 'View active downloads',
|
|
},
|
|
hideDownloads: {
|
|
id: 'app.action-bar.hide-downloads',
|
|
defaultMessage: 'Hide active downloads',
|
|
},
|
|
showDownloads: {
|
|
id: 'app.action-bar.show-downloads',
|
|
defaultMessage: 'Show active downloads',
|
|
},
|
|
})
|
|
|
|
const downloadState = computed(() => popupNotificationManager.getDownloadState())
|
|
const downloadToggleLabel = computed(() =>
|
|
formatMessage(
|
|
downloadState.value.hidden > 0
|
|
? messages.showDownloads
|
|
: downloadState.value.total > 0
|
|
? messages.hideDownloads
|
|
: messages.viewActiveDownloads,
|
|
),
|
|
)
|
|
|
|
function toggleDownloadNotifications(): void {
|
|
if (downloadState.value.total > 0) {
|
|
popupNotificationManager.toggleDownloadNotifications()
|
|
} else if (hasActiveLoadingBars.value) {
|
|
openDownloadToast()
|
|
}
|
|
}
|
|
|
|
const currentProcesses = ref<RunningProcess[]>([])
|
|
const selectedProcess = ref<RunningProcess | undefined>()
|
|
|
|
const refresh = async () => {
|
|
const processes = ((await getRunningProcesses().catch((error) => {
|
|
handleError(error)
|
|
return []
|
|
})) ?? []) as Array<{ uuid: string; instance_id: string }>
|
|
const instanceIds = processes.map((process) => process.instance_id)
|
|
const instances: GameInstance[] = await getInstances(instanceIds).catch((error) => {
|
|
handleError(error)
|
|
return []
|
|
})
|
|
|
|
currentProcesses.value = processes
|
|
.map((process) => {
|
|
const instance = instances.find((item) => process.instance_id === item.id)
|
|
if (!instance) {
|
|
return null
|
|
}
|
|
return {
|
|
...process,
|
|
instance,
|
|
}
|
|
})
|
|
.filter((process): process is RunningProcess => process !== null)
|
|
if (!selectedProcess.value || !currentProcesses.value.includes(selectedProcess.value)) {
|
|
selectedProcess.value = currentProcesses.value[0]
|
|
}
|
|
}
|
|
|
|
await refresh()
|
|
|
|
const offline = ref(!navigator.onLine)
|
|
function handleOffline() {
|
|
offline.value = true
|
|
}
|
|
function handleOnline() {
|
|
offline.value = false
|
|
}
|
|
|
|
onMounted(() => {
|
|
window.addEventListener('offline', handleOffline)
|
|
window.addEventListener('online', handleOnline)
|
|
})
|
|
|
|
useAppEvent('process', async () => {
|
|
await refresh()
|
|
})
|
|
|
|
const stop = async (process: RunningProcess) => {
|
|
try {
|
|
await killProcess(process.uuid).catch(handleError)
|
|
|
|
trackEvent('InstanceStop', {
|
|
loader: process.instance.loader,
|
|
game_version: process.instance.game_version,
|
|
source: 'AppBar',
|
|
})
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
await refresh()
|
|
}
|
|
|
|
function goToTerminal(instanceId?: string) {
|
|
const selectedInstanceId = instanceId ?? selectedProcess.value?.instance.id
|
|
if (!selectedInstanceId) {
|
|
return
|
|
}
|
|
router.push(`/instance/${encodeURIComponent(selectedInstanceId)}/logs`)
|
|
}
|
|
|
|
const currentLoadingBars = ref<LoadingBar[]>([])
|
|
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
|
|
const notificationId = ref<string | number | null>(null)
|
|
const terminalNotificationIds = new Map<string, string | number>()
|
|
const dismissed = ref(false)
|
|
|
|
function getLoadingBarKey(loadingBar: LoadingBar): string {
|
|
return `${loadingBar.loading_bar_uuid ?? loadingBar.id}`
|
|
}
|
|
|
|
function getLoadingProgress(loadingBar: LoadingBar): number {
|
|
if (!loadingBar.total || loadingBar.total <= 0) {
|
|
return 0
|
|
}
|
|
return Math.max(0, Math.min(1, (loadingBar.current ?? 0) / (loadingBar.total ?? 0)))
|
|
}
|
|
|
|
function getLoadingText(loadingBar: LoadingBar): string {
|
|
return loadingBar.message ?? ''
|
|
}
|
|
|
|
function getDisplayIconUrl(icon: string | null | undefined): string | null {
|
|
if (!icon) {
|
|
return null
|
|
}
|
|
if (/^(https?:|data:|blob:|asset:|tauri:)/.test(icon)) {
|
|
return icon
|
|
}
|
|
return convertFileSrc(icon)
|
|
}
|
|
|
|
function getNotification(): PopupNotificationStandard | null {
|
|
if (!notificationId.value) {
|
|
return null
|
|
}
|
|
const notification = popupNotificationManager
|
|
.getNotifications()
|
|
.find((notification) => notification.id === notificationId.value)
|
|
return notification?.contentType === 'standard' && notification.type === 'download'
|
|
? notification
|
|
: null
|
|
}
|
|
|
|
function removeNotification(): void {
|
|
if (!notificationId.value) {
|
|
return
|
|
}
|
|
popupNotificationManager.removeNotification(notificationId.value)
|
|
notificationId.value = null
|
|
}
|
|
|
|
function syncTerminalNotifications(): void {
|
|
const terminalNotifications = installJobNotifications.terminalNotifications.value
|
|
const currentJobIds = new Set(terminalNotifications.map((notification) => notification.id))
|
|
|
|
for (const terminal of terminalNotifications) {
|
|
const popupId = terminalNotificationIds.get(terminal.id)
|
|
let notification = popupId
|
|
? popupNotificationManager
|
|
.getNotifications()
|
|
.find(
|
|
(candidate): candidate is PopupNotificationStandard =>
|
|
candidate.id === popupId && candidate.contentType === 'standard',
|
|
)
|
|
: undefined
|
|
|
|
if (!notification) {
|
|
notification = popupNotificationManager.addPopupNotification({
|
|
contentType: 'standard',
|
|
title: terminal.title,
|
|
text: terminal.text,
|
|
type: terminal.type,
|
|
buttons: terminal.buttons,
|
|
onDismiss: terminal.onDismiss,
|
|
autoCloseMs: null,
|
|
})
|
|
terminalNotificationIds.set(terminal.id, notification.id)
|
|
continue
|
|
}
|
|
|
|
notification.title = terminal.title
|
|
notification.text = terminal.text
|
|
notification.type = terminal.type
|
|
notification.buttons = terminal.buttons
|
|
notification.onDismiss = terminal.onDismiss
|
|
}
|
|
|
|
for (const [jobId, popupId] of terminalNotificationIds) {
|
|
if (!currentJobIds.has(jobId)) {
|
|
popupNotificationManager.removeNotification(popupId)
|
|
terminalNotificationIds.delete(jobId)
|
|
}
|
|
}
|
|
}
|
|
|
|
function buildDownloadItems(): PopupNotificationProgressItem[] {
|
|
return [
|
|
...installJobNotifications.progressItems.value,
|
|
...currentLoadingBars.value.map<PopupNotificationProgressItem>((bar) => ({
|
|
id: getLoadingBarKey(bar),
|
|
title: bar.title ?? '',
|
|
text: getLoadingText(bar),
|
|
iconUrl: currentLoadingBarIconUrls.value[getLoadingBarKey(bar)] ?? null,
|
|
progress: getLoadingProgress(bar),
|
|
waiting: !bar.total || bar.total <= 0,
|
|
progressType: bar.bar_type?.type === 'pack_import' ? 'bytes' : 'percentage',
|
|
progressCurrent: bar.current,
|
|
progressTotal: bar.total,
|
|
})),
|
|
]
|
|
}
|
|
|
|
const hasActiveLoadingBars = computed(
|
|
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
|
|
)
|
|
|
|
function updateNotification(resummon = false): void {
|
|
syncTerminalNotifications()
|
|
|
|
if (resummon) {
|
|
dismissed.value = false
|
|
}
|
|
|
|
if (currentLoadingBars.value.length === 0 && !installJobNotifications.active.value) {
|
|
removeNotification()
|
|
dismissed.value = false
|
|
return
|
|
}
|
|
|
|
if (notificationId.value && !getNotification()) {
|
|
notificationId.value = null
|
|
dismissed.value = true
|
|
}
|
|
|
|
if (dismissed.value && !resummon) {
|
|
return
|
|
}
|
|
|
|
const notif = getNotification()
|
|
const progressItems = buildDownloadItems()
|
|
|
|
if (notif) {
|
|
notif.title = installJobNotifications.active.value
|
|
? installJobNotifications.title.value
|
|
: formatMessage(messages.downloads)
|
|
notif.text = undefined
|
|
notif.progressItems = progressItems
|
|
notif.progress = undefined
|
|
notif.waiting = undefined
|
|
} else {
|
|
const notification = popupNotificationManager.addPopupNotification({
|
|
contentType: 'standard',
|
|
title: installJobNotifications.active.value
|
|
? installJobNotifications.title.value
|
|
: formatMessage(messages.downloads),
|
|
type: 'download',
|
|
autoCloseMs: null,
|
|
progressItems,
|
|
})
|
|
notificationId.value = notification.id
|
|
}
|
|
}
|
|
|
|
function formatLoadingBars(loadingBar: LoadingBar): LoadingBar {
|
|
const formatted = { ...loadingBar }
|
|
if (formatted.bar_type?.type === 'java_download') {
|
|
formatted.title = formatMessage(messages.downloadingJava, {
|
|
version: formatted.bar_type.version,
|
|
})
|
|
}
|
|
if (formatted.bar_type?.instance_id) {
|
|
formatted.title = formatted.bar_type.instance_name ?? formatted.bar_type.instance_id
|
|
}
|
|
if (formatted.bar_type?.pack_name) {
|
|
formatted.title = formatted.bar_type.pack_name
|
|
}
|
|
return formatted
|
|
}
|
|
|
|
async function refreshLoadingBars() {
|
|
const bars: Record<string, LoadingBar> = await progress_bars_list().catch((error) => {
|
|
handleError(error)
|
|
return {}
|
|
})
|
|
|
|
currentLoadingBars.value = Object.values(bars)
|
|
.map(formatLoadingBars)
|
|
.filter(
|
|
(bar) =>
|
|
bar?.bar_type?.type !== 'launcher_update' &&
|
|
![
|
|
'java_download',
|
|
'pack_file_download',
|
|
'pack_download',
|
|
'minecraft_download',
|
|
'copy_instance',
|
|
].includes(bar?.bar_type?.type ?? ''),
|
|
)
|
|
|
|
const instanceIds = Array.from(
|
|
new Set(
|
|
currentLoadingBars.value
|
|
.map((bar) => bar.bar_type?.instance_id)
|
|
.filter((instanceId): instanceId is string => !!instanceId),
|
|
),
|
|
)
|
|
const instances = instanceIds.length
|
|
? await getInstances(instanceIds).catch((error) => {
|
|
handleError(error)
|
|
return []
|
|
})
|
|
: []
|
|
const instanceIconUrls = new Map(
|
|
instances.map((instance) => [instance.id, getDisplayIconUrl(instance.icon_path)]),
|
|
)
|
|
currentLoadingBarIconUrls.value = Object.fromEntries(
|
|
currentLoadingBars.value.map((bar) => {
|
|
const barIconUrl = getDisplayIconUrl(bar.bar_type?.icon)
|
|
const instanceIconUrl = bar.bar_type?.instance_id
|
|
? instanceIconUrls.get(bar.bar_type.instance_id)
|
|
: null
|
|
return [getLoadingBarKey(bar), barIconUrl ?? instanceIconUrl ?? null]
|
|
}),
|
|
)
|
|
|
|
currentLoadingBars.value.sort((a, b) => {
|
|
const aKey = `${a.loading_bar_uuid ?? a.id ?? ''}`
|
|
const bKey = `${b.loading_bar_uuid ?? b.id ?? ''}`
|
|
return aKey.localeCompare(bKey)
|
|
})
|
|
|
|
updateNotification()
|
|
}
|
|
|
|
const installJobNotifications = await useInstallJobNotifications({
|
|
router,
|
|
handleError: (error) => handleError(toError(error)),
|
|
onChange: updateNotification,
|
|
})
|
|
|
|
await refreshLoadingBars()
|
|
|
|
useAppEvent('loading', async () => {
|
|
await refreshLoadingBars()
|
|
})
|
|
|
|
function openDownloadToast() {
|
|
updateNotification(true)
|
|
}
|
|
|
|
function selectProcess(process: RunningProcess) {
|
|
selectedProcess.value = process
|
|
}
|
|
|
|
onBeforeUnmount(() => {
|
|
removeNotification()
|
|
terminalNotificationIds.forEach((id) => popupNotificationManager.removeNotification(id))
|
|
terminalNotificationIds.clear()
|
|
dismissed.value = false
|
|
window.removeEventListener('offline', handleOffline)
|
|
window.removeEventListener('online', handleOnline)
|
|
installJobNotifications.dispose()
|
|
})
|
|
</script>
|