mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
feat: add hide and show download items (#7106)
* feat: add hide and show download items * pnpm prepr
This commit is contained in:
@@ -1,15 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex gap-2 items-center">
|
<div class="flex gap-2 items-center">
|
||||||
<IconButton
|
<div v-if="downloadState.total > 0 || hasActiveLoadingBars" class="relative">
|
||||||
v-if="hasActiveLoadingBars && !hasVisibleActiveDownloadToasts"
|
<IconButton
|
||||||
v-tooltip="formatMessage(messages.viewActiveDownloads)"
|
v-tooltip="downloadToggleLabel"
|
||||||
type="quiet"
|
:color="downloadState.hidden > 0 ? 'brand' : undefined"
|
||||||
color="brand"
|
type="quiet"
|
||||||
:label="formatMessage(messages.viewActiveDownloads)"
|
:label="downloadToggleLabel"
|
||||||
@click="openDownloadToast()"
|
@click="toggleDownloadNotifications"
|
||||||
>
|
>
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</div>
|
||||||
<div v-if="offline" class="flex items-center gap-1">
|
<div v-if="offline" class="flex items-center gap-1">
|
||||||
<UnplugIcon class="text-secondary" />
|
<UnplugIcon class="text-secondary" />
|
||||||
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
|
<span class="text-sm text-contrast"> {{ formatMessage(messages.offline) }} </span>
|
||||||
@@ -218,8 +219,35 @@ const messages = defineMessages({
|
|||||||
id: 'app.action-bar.view-active-downloads',
|
id: 'app.action-bar.view-active-downloads',
|
||||||
defaultMessage: '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 currentProcesses = ref<RunningProcess[]>([])
|
||||||
const selectedProcess = ref<RunningProcess | undefined>()
|
const selectedProcess = ref<RunningProcess | undefined>()
|
||||||
|
|
||||||
@@ -296,6 +324,7 @@ function goToTerminal(instanceId?: string) {
|
|||||||
const currentLoadingBars = ref<LoadingBar[]>([])
|
const currentLoadingBars = ref<LoadingBar[]>([])
|
||||||
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
|
const currentLoadingBarIconUrls = ref<Record<string, string | null>>({})
|
||||||
const notificationId = ref<string | number | null>(null)
|
const notificationId = ref<string | number | null>(null)
|
||||||
|
const terminalNotificationIds = new Map<string, string | number>()
|
||||||
const dismissed = ref(false)
|
const dismissed = ref(false)
|
||||||
|
|
||||||
function getLoadingBarKey(loadingBar: LoadingBar): string {
|
function getLoadingBarKey(loadingBar: LoadingBar): string {
|
||||||
@@ -341,6 +370,44 @@ function removeNotification(): void {
|
|||||||
notificationId.value = null
|
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.id === popupId)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
if (!notification) {
|
||||||
|
notification = popupNotificationManager.addPopupNotification({
|
||||||
|
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[] {
|
function buildDownloadItems(): PopupNotificationProgressItem[] {
|
||||||
return [
|
return [
|
||||||
...installJobNotifications.progressItems.value,
|
...installJobNotifications.progressItems.value,
|
||||||
@@ -358,12 +425,13 @@ function buildDownloadItems(): PopupNotificationProgressItem[] {
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasVisibleActiveDownloadToasts = computed(() => !!getNotification())
|
|
||||||
const hasActiveLoadingBars = computed(
|
const hasActiveLoadingBars = computed(
|
||||||
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
|
() => currentLoadingBars.value.length > 0 || installJobNotifications.active.value,
|
||||||
)
|
)
|
||||||
|
|
||||||
function updateNotification(resummon = false): void {
|
function updateNotification(resummon = false): void {
|
||||||
|
syncTerminalNotifications()
|
||||||
|
|
||||||
if (resummon) {
|
if (resummon) {
|
||||||
dismissed.value = false
|
dismissed.value = false
|
||||||
}
|
}
|
||||||
@@ -392,7 +460,6 @@ function updateNotification(resummon = false): void {
|
|||||||
: formatMessage(messages.downloads)
|
: formatMessage(messages.downloads)
|
||||||
notif.text = undefined
|
notif.text = undefined
|
||||||
notif.progressItems = progressItems
|
notif.progressItems = progressItems
|
||||||
notif.buttons = installJobNotifications.buttons.value
|
|
||||||
notif.progress = undefined
|
notif.progress = undefined
|
||||||
notif.waiting = undefined
|
notif.waiting = undefined
|
||||||
} else {
|
} else {
|
||||||
@@ -403,7 +470,6 @@ function updateNotification(resummon = false): void {
|
|||||||
type: 'download',
|
type: 'download',
|
||||||
autoCloseMs: null,
|
autoCloseMs: null,
|
||||||
progressItems,
|
progressItems,
|
||||||
buttons: installJobNotifications.buttons.value,
|
|
||||||
})
|
})
|
||||||
notificationId.value = notif.id
|
notificationId.value = notif.id
|
||||||
}
|
}
|
||||||
@@ -502,6 +568,8 @@ function selectProcess(process: RunningProcess) {
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
removeNotification()
|
removeNotification()
|
||||||
|
terminalNotificationIds.forEach((id) => popupNotificationManager.removeNotification(id))
|
||||||
|
terminalNotificationIds.clear()
|
||||||
dismissed.value = false
|
dismissed.value = false
|
||||||
window.removeEventListener('offline', handleOffline)
|
window.removeEventListener('offline', handleOffline)
|
||||||
window.removeEventListener('online', handleOnline)
|
window.removeEventListener('online', handleOnline)
|
||||||
|
|||||||
@@ -404,8 +404,6 @@ export async function useInstallJobNotifications(opts: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getProgress(job: InstallJobSnapshot): number {
|
function getProgress(job: InstallJobSnapshot): number {
|
||||||
if (job.status === 'succeeded') return 1
|
|
||||||
if (job.status === 'failed' || job.status === 'interrupted') return 0
|
|
||||||
const progress = getEffectiveProgress(job)
|
const progress = getEffectiveProgress(job)
|
||||||
if (!progress || progress.total <= 0) return 0
|
if (!progress || progress.total <= 0) return 0
|
||||||
return Math.max(0, Math.min(1, progress.current / progress.total))
|
return Math.max(0, Math.min(1, progress.current / progress.total))
|
||||||
@@ -526,8 +524,12 @@ export async function useInstallJobNotifications(opts: {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeJobs = computed(() =>
|
||||||
|
jobs.value.filter((job) => job.status === 'queued' || job.status === 'running'),
|
||||||
|
)
|
||||||
|
|
||||||
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
|
const progressItems = computed<PopupNotificationProgressItem[]>(() =>
|
||||||
jobs.value.map((job) => {
|
activeJobs.value.map((job) => {
|
||||||
const progress = getEffectiveProgress(job)
|
const progress = getEffectiveProgress(job)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -536,20 +538,26 @@ export async function useInstallJobNotifications(opts: {
|
|||||||
text: getText(job),
|
text: getText(job),
|
||||||
iconUrl: iconUrls.value[job.job_id] ?? null,
|
iconUrl: iconUrls.value[job.job_id] ?? null,
|
||||||
progress: getProgress(job),
|
progress: getProgress(job),
|
||||||
waiting: !job.progress && ['queued', 'running'].includes(job.status),
|
waiting: !job.progress && job.status === 'running',
|
||||||
showProgress: !isTerminalJob(job),
|
showProgress: job.status === 'running',
|
||||||
wrapText: isTerminalJob(job),
|
progressType: getProgressType(job),
|
||||||
progressType: isTerminalJob(job) ? undefined : getProgressType(job),
|
progressCurrent: progress?.current,
|
||||||
progressCurrent: isTerminalJob(job) ? undefined : progress?.current,
|
progressTotal: progress?.total,
|
||||||
progressTotal: isTerminalJob(job) ? undefined : progress?.total,
|
|
||||||
buttons: getButtons(job),
|
buttons: getButtons(job),
|
||||||
dismissible: isTerminalJob(job),
|
|
||||||
onDismiss: getDismissHandler(job),
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const buttons = computed<PopupNotificationButton[] | undefined>(() => undefined)
|
const terminalNotifications = computed(() =>
|
||||||
|
jobs.value.filter(isTerminalJob).map((job) => ({
|
||||||
|
id: job.job_id,
|
||||||
|
title: getTitle(job),
|
||||||
|
text: getText(job),
|
||||||
|
type: job.status === 'failed' ? ('error' as const) : ('warning' as const),
|
||||||
|
buttons: getButtons(job),
|
||||||
|
onDismiss: getDismissHandler(job),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
|
||||||
async function refreshMetadata(notify = true) {
|
async function refreshMetadata(notify = true) {
|
||||||
const request = ++metadataRequest
|
const request = ++metadataRequest
|
||||||
@@ -631,10 +639,10 @@ export async function useInstallJobNotifications(opts: {
|
|||||||
await refresh(false)
|
await refresh(false)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
active: computed(() => jobs.value.length > 0),
|
active: computed(() => activeJobs.value.length > 0),
|
||||||
title: computed(() => formatMessage(messages.installs)),
|
title: computed(() => formatMessage(messages.installs)),
|
||||||
progressItems,
|
progressItems,
|
||||||
buttons,
|
terminalNotifications,
|
||||||
refresh,
|
refresh,
|
||||||
dispose: () => {
|
dispose: () => {
|
||||||
for (const timeout of copiedResetTimeouts.values()) {
|
for (const timeout of copiedResetTimeouts.values()) {
|
||||||
|
|||||||
@@ -8,6 +8,9 @@
|
|||||||
"app.action-bar.downloads": {
|
"app.action-bar.downloads": {
|
||||||
"message": "Downloads"
|
"message": "Downloads"
|
||||||
},
|
},
|
||||||
|
"app.action-bar.hide-downloads": {
|
||||||
|
"message": "Hide active downloads"
|
||||||
|
},
|
||||||
"app.action-bar.hide-more-running-instances": {
|
"app.action-bar.hide-more-running-instances": {
|
||||||
"message": "Hide more running instances"
|
"message": "Hide more running instances"
|
||||||
},
|
},
|
||||||
@@ -110,6 +113,9 @@
|
|||||||
"app.action-bar.reload-to-update": {
|
"app.action-bar.reload-to-update": {
|
||||||
"message": "Reload to update"
|
"message": "Reload to update"
|
||||||
},
|
},
|
||||||
|
"app.action-bar.show-downloads": {
|
||||||
|
"message": "Show active downloads"
|
||||||
|
},
|
||||||
"app.action-bar.show-more-running-instances": {
|
"app.action-bar.show-more-running-instances": {
|
||||||
"message": "Show more running instances"
|
"message": "Show more running instances"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
>
|
>
|
||||||
<NotificationToast
|
<NotificationToast
|
||||||
v-if="item.toast"
|
v-if="item.toast"
|
||||||
|
class="min-w-full"
|
||||||
:type="item.toast.type"
|
:type="item.toast.type"
|
||||||
:action-loading="toastActionLoading(item.id)"
|
:action-loading="toastActionLoading(item.id)"
|
||||||
:actor-name="item.toast.actorName"
|
:actor-name="item.toast.actorName"
|
||||||
@@ -37,9 +38,15 @@
|
|||||||
@open-actor="item.toast.onOpenActor?.()"
|
@open-actor="item.toast.onOpenActor?.()"
|
||||||
@open-instance="handleToastAction(item, item.toast.onOpenInstance)"
|
@open-instance="handleToastAction(item, item.toast.onOpenInstance)"
|
||||||
/>
|
/>
|
||||||
<div v-else-if="isDownloadNotification(item)" class="flex flex-col gap-4">
|
<TransitionGroup
|
||||||
|
v-else-if="isDownloadNotification(item)"
|
||||||
|
name="popup-downloads"
|
||||||
|
tag="div"
|
||||||
|
class="flex flex-col gap-3"
|
||||||
|
>
|
||||||
<div v-for="progressItem in downloadToastItems(item)" :key="progressItem.id">
|
<div v-for="progressItem in downloadToastItems(item)" :key="progressItem.id">
|
||||||
<NotificationToast
|
<NotificationToast
|
||||||
|
class="min-w-full"
|
||||||
type="instance-download"
|
type="instance-download"
|
||||||
:entity-name="progressItem.title || item.title"
|
:entity-name="progressItem.title || item.title"
|
||||||
:entity-icon-url="progressItem.iconUrl ?? item.iconUrl ?? MinecraftServerIcon"
|
:entity-icon-url="progressItem.iconUrl ?? item.iconUrl ?? MinecraftServerIcon"
|
||||||
@@ -54,10 +61,10 @@
|
|||||||
:actions="progressItem.buttons"
|
:actions="progressItem.buttons"
|
||||||
:dismissible="progressItem.dismissible"
|
:dismissible="progressItem.dismissible"
|
||||||
@dismiss="handleProgressItemDismiss(item, progressItem)"
|
@dismiss="handleProgressItemDismiss(item, progressItem)"
|
||||||
@action="(index) => handleProgressItemAction(progressItem, index)"
|
@action="(index) => handleProgressItemAction(item, progressItem, index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</TransitionGroup>
|
||||||
<div
|
<div
|
||||||
v-else
|
v-else
|
||||||
class="flex w-full flex-col gap-3 overflow-hidden rounded-2xl bg-bg-raised shadow-xl border-surface-5 border-solid border p-4"
|
class="flex w-full flex-col gap-3 overflow-hidden rounded-2xl bg-bg-raised shadow-xl border-surface-5 border-solid border p-4"
|
||||||
@@ -100,7 +107,7 @@
|
|||||||
type="quiet"
|
type="quiet"
|
||||||
label="Close"
|
label="Close"
|
||||||
class="-m-1.5"
|
class="-m-1.5"
|
||||||
@click="dismiss(item.id)"
|
@click="handleNotificationDismiss(item)"
|
||||||
>
|
>
|
||||||
<XIcon />
|
<XIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
@@ -201,7 +208,7 @@ import NotificationToast from '../notifications/NotificationToast.vue'
|
|||||||
|
|
||||||
const popupNotificationManager = injectPopupNotificationManager()
|
const popupNotificationManager = injectPopupNotificationManager()
|
||||||
const notifications = computed<PopupNotification[]>(() =>
|
const notifications = computed<PopupNotification[]>(() =>
|
||||||
popupNotificationManager.getNotifications(),
|
popupNotificationManager.getVisibleNotifications(),
|
||||||
)
|
)
|
||||||
const { stackCount } = useModalStack()
|
const { stackCount } = useModalStack()
|
||||||
const hasModalActive = computed(() => stackCount.value > 0)
|
const hasModalActive = computed(() => stackCount.value > 0)
|
||||||
@@ -213,7 +220,6 @@ const activeToastActions = ref<Record<string, 'accept'>>({})
|
|||||||
const stopTimer = (n: PopupNotification) => popupNotificationManager.stopNotificationTimer(n)
|
const stopTimer = (n: PopupNotification) => popupNotificationManager.stopNotificationTimer(n)
|
||||||
const setNotificationTimer = (n: PopupNotification) =>
|
const setNotificationTimer = (n: PopupNotification) =>
|
||||||
popupNotificationManager.setNotificationTimer(n)
|
popupNotificationManager.setNotificationTimer(n)
|
||||||
const dismiss = (id: string | number) => popupNotificationManager.removeNotification(id)
|
|
||||||
const toastActionLoading = (id: string | number) => activeToastActions.value[String(id)] ?? null
|
const toastActionLoading = (id: string | number) => activeToastActions.value[String(id)] ?? null
|
||||||
|
|
||||||
function isDownloadNotification(item: PopupNotification) {
|
function isDownloadNotification(item: PopupNotification) {
|
||||||
@@ -225,7 +231,7 @@ function isDownloadNotification(item: PopupNotification) {
|
|||||||
|
|
||||||
function downloadToastItems(item: PopupNotification): PopupNotificationProgressItem[] {
|
function downloadToastItems(item: PopupNotification): PopupNotificationProgressItem[] {
|
||||||
if (item.progressItems?.length) {
|
if (item.progressItems?.length) {
|
||||||
return item.progressItems
|
return popupNotificationManager.getVisibleDownloadProgressItems(item)
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
@@ -242,35 +248,32 @@ function downloadToastItems(item: PopupNotification): PopupNotificationProgressI
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleProgressItemDismiss(
|
function handleProgressItemDismiss(
|
||||||
item: PopupNotification,
|
item: PopupNotification,
|
||||||
progressItem: PopupNotificationProgressItem,
|
progressItem: PopupNotificationProgressItem,
|
||||||
) {
|
) {
|
||||||
if (progressItem.onDismiss) {
|
popupNotificationManager.hideDownloadItem(item.id, progressItem.id)
|
||||||
await progressItem.onDismiss()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
dismiss(item.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleProgressItemAction(
|
async function handleProgressItemAction(
|
||||||
|
item: PopupNotification,
|
||||||
progressItem: PopupNotificationProgressItem,
|
progressItem: PopupNotificationProgressItem,
|
||||||
index: number,
|
index: number,
|
||||||
) {
|
) {
|
||||||
const button = progressItem.buttons?.[index]
|
const button = progressItem.buttons?.[index]
|
||||||
if (button) {
|
if (button) {
|
||||||
await handleProgressItemButtonClick(progressItem, button)
|
await handleProgressItemButtonClick(item, progressItem, button)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleProgressItemButtonClick(
|
async function handleProgressItemButtonClick(
|
||||||
|
item: PopupNotification,
|
||||||
progressItem: PopupNotificationProgressItem,
|
progressItem: PopupNotificationProgressItem,
|
||||||
btn: PopupNotificationButton,
|
btn: PopupNotificationButton,
|
||||||
) {
|
) {
|
||||||
await btn.action()
|
await btn.action()
|
||||||
if (!btn.keepOpen) {
|
if (!btn.keepOpen) {
|
||||||
await progressItem.onDismiss?.()
|
popupNotificationManager.hideDownloadItem(item.id, progressItem.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -281,6 +284,11 @@ async function handleButtonClick(id: string | number, btn: PopupNotificationButt
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleNotificationDismiss(item: PopupNotification) {
|
||||||
|
await item.onDismiss?.()
|
||||||
|
popupNotificationManager.removeNotification(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleToastAction(item: PopupNotification, action?: () => void | Promise<void>) {
|
async function handleToastAction(item: PopupNotification, action?: () => void | Promise<void>) {
|
||||||
popupNotificationManager.removeNotification(item.id)
|
popupNotificationManager.removeNotification(item.id)
|
||||||
await action?.()
|
await action?.()
|
||||||
@@ -381,6 +389,21 @@ withDefaults(
|
|||||||
|
|
||||||
.popup-notifs-leave-to {
|
.popup-notifs-leave-to {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transform: translateX(100%) scale(0.8);
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-downloads-move {
|
||||||
|
transition: transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-downloads-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity 0.3s ease-in-out,
|
||||||
|
transform 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-downloads-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(100%);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Component } from 'vue'
|
import { type Component, type Ref, ref } from 'vue'
|
||||||
|
|
||||||
import { createContext } from '.'
|
import { createContext } from '.'
|
||||||
|
|
||||||
@@ -25,7 +25,6 @@ export interface PopupNotificationProgressItem {
|
|||||||
progressCurrent?: number
|
progressCurrent?: number
|
||||||
progressTotal?: number
|
progressTotal?: number
|
||||||
dismissible?: boolean
|
dismissible?: boolean
|
||||||
onDismiss?: () => void | Promise<void>
|
|
||||||
buttons?: PopupNotificationButton[]
|
buttons?: PopupNotificationButton[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,15 +72,51 @@ export interface PopupNotification {
|
|||||||
buttons?: PopupNotificationButton[]
|
buttons?: PopupNotificationButton[]
|
||||||
toast?: PopupNotificationToast
|
toast?: PopupNotificationToast
|
||||||
dismissible?: boolean
|
dismissible?: boolean
|
||||||
|
onDismiss?: () => void | Promise<void>
|
||||||
autoCloseMs?: number | null
|
autoCloseMs?: number | null
|
||||||
timer?: NodeJS.Timeout
|
timer?: NodeJS.Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PopupNotificationDownloadState {
|
||||||
|
total: number
|
||||||
|
hidden: number
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class AbstractPopupNotificationManager {
|
export abstract class AbstractPopupNotificationManager {
|
||||||
protected readonly DEFAULT_AUTO_CLOSE_MS = 30 * 1000
|
protected readonly DEFAULT_AUTO_CLOSE_MS = 30 * 1000
|
||||||
|
private readonly hiddenDownloadItemKeys: Ref<Set<string>> = ref(new Set())
|
||||||
|
|
||||||
abstract getNotifications(): PopupNotification[]
|
abstract getNotifications(): PopupNotification[]
|
||||||
|
|
||||||
|
getDownloadState = (): PopupNotificationDownloadState => {
|
||||||
|
const itemKeys = this.getDownloadNotifications().flatMap((notification) =>
|
||||||
|
this.getDownloadItemKeys(notification),
|
||||||
|
)
|
||||||
|
return {
|
||||||
|
total: itemKeys.length,
|
||||||
|
hidden: itemKeys.filter((key) => this.hiddenDownloadItemKeys.value.has(key)).length,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getVisibleNotifications = (): PopupNotification[] =>
|
||||||
|
this.getNotifications().filter(
|
||||||
|
(notification) =>
|
||||||
|
!this.isDownloadNotification(notification) ||
|
||||||
|
this.getDownloadItemKeys(notification).some(
|
||||||
|
(key) => !this.hiddenDownloadItemKeys.value.has(key),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
getVisibleDownloadProgressItems = (
|
||||||
|
notification: PopupNotification,
|
||||||
|
): PopupNotificationProgressItem[] =>
|
||||||
|
(notification.progressItems ?? []).filter(
|
||||||
|
(progressItem) =>
|
||||||
|
!this.hiddenDownloadItemKeys.value.has(
|
||||||
|
this.getDownloadItemKey(notification.id, progressItem.id),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
protected abstract addNotificationToStorage(notification: PopupNotification): void
|
protected abstract addNotificationToStorage(notification: PopupNotification): void
|
||||||
protected abstract removeNotificationFromStorage(id: string | number): void
|
protected abstract removeNotificationFromStorage(id: string | number): void
|
||||||
protected abstract clearAllNotificationsFromStorage(): void
|
protected abstract clearAllNotificationsFromStorage(): void
|
||||||
@@ -103,6 +138,9 @@ export abstract class AbstractPopupNotificationManager {
|
|||||||
const notification = notifications.find((n) => n.id === id)
|
const notification = notifications.find((n) => n.id === id)
|
||||||
if (notification) {
|
if (notification) {
|
||||||
this.clearNotificationTimer(notification)
|
this.clearNotificationTimer(notification)
|
||||||
|
this.getDownloadItemKeys(notification).forEach((key) =>
|
||||||
|
this.hiddenDownloadItemKeys.value.delete(key),
|
||||||
|
)
|
||||||
this.removeNotificationFromStorage(id)
|
this.removeNotificationFromStorage(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,6 +148,45 @@ export abstract class AbstractPopupNotificationManager {
|
|||||||
clearAllNotifications = (): void => {
|
clearAllNotifications = (): void => {
|
||||||
this.getNotifications().forEach((n) => this.clearNotificationTimer(n))
|
this.getNotifications().forEach((n) => this.clearNotificationTimer(n))
|
||||||
this.clearAllNotificationsFromStorage()
|
this.clearAllNotificationsFromStorage()
|
||||||
|
this.hiddenDownloadItemKeys.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
hideDownloadItem = (notificationId: string | number, progressItemId: string): void => {
|
||||||
|
const notification = this.getDownloadNotifications().find(
|
||||||
|
(candidate) => candidate.id === notificationId,
|
||||||
|
)
|
||||||
|
if (!notification?.progressItems?.some((item) => item.id === progressItemId)) return
|
||||||
|
|
||||||
|
this.hiddenDownloadItemKeys.value.add(this.getDownloadItemKey(notificationId, progressItemId))
|
||||||
|
if (
|
||||||
|
this.getDownloadItemKeys(notification).every((key) =>
|
||||||
|
this.hiddenDownloadItemKeys.value.has(key),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
this.clearNotificationTimer(notification)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleDownloadNotifications = (): void => {
|
||||||
|
const downloadNotifications = this.getDownloadNotifications()
|
||||||
|
const hasHiddenDownloads = downloadNotifications.some((notification) =>
|
||||||
|
this.getDownloadItemKeys(notification).some((key) =>
|
||||||
|
this.hiddenDownloadItemKeys.value.has(key),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
if (hasHiddenDownloads) {
|
||||||
|
this.hiddenDownloadItemKeys.value.clear()
|
||||||
|
downloadNotifications.forEach((notification) => this.setNotificationTimer(notification))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadNotifications.forEach((notification) => {
|
||||||
|
this.getDownloadItemKeys(notification).forEach((key) =>
|
||||||
|
this.hiddenDownloadItemKeys.value.add(key),
|
||||||
|
)
|
||||||
|
this.clearNotificationTimer(notification)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setNotificationTimer = (notification: PopupNotification): void => {
|
setNotificationTimer = (notification: PopupNotification): void => {
|
||||||
@@ -134,6 +211,30 @@ export abstract class AbstractPopupNotificationManager {
|
|||||||
notification.timer = undefined
|
notification.timer = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private isDownloadNotification(notification: PopupNotification): boolean {
|
||||||
|
return notification.type === 'download' || notification.toast?.type === 'instance-download'
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDownloadNotifications(): PopupNotification[] {
|
||||||
|
return this.getNotifications().filter((notification) =>
|
||||||
|
this.isDownloadNotification(notification),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDownloadItemKeys(notification: PopupNotification): string[] {
|
||||||
|
if (!this.isDownloadNotification(notification)) return []
|
||||||
|
if (notification.progressItems?.length) {
|
||||||
|
return notification.progressItems.map((progressItem) =>
|
||||||
|
this.getDownloadItemKey(notification.id, progressItem.id),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return [this.getDownloadItemKey(notification.id)]
|
||||||
|
}
|
||||||
|
|
||||||
|
private getDownloadItemKey(notificationId: string | number, progressItemId?: string): string {
|
||||||
|
return JSON.stringify([typeof notificationId, notificationId, progressItemId ?? null])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const [injectPopupNotificationManager, providePopupNotificationManager] =
|
export const [injectPopupNotificationManager, providePopupNotificationManager] =
|
||||||
|
|||||||
Reference in New Issue
Block a user