mirror of
https://github.com/modrinth/code.git
synced 2026-08-25 00:55:25 +00:00
fix: pre-release shared instances fixes (#6856)
* fix: dedupe notifications * fix: notifs + members btn robustness * fix: temp fix for external file wrongly showing updated * fix: fmt
This commit is contained in:
@@ -290,6 +290,9 @@ const {
|
||||
const news = ref([])
|
||||
const availableSurvey = ref(false)
|
||||
const displayedServerInviteNotifications = new Set()
|
||||
const serverInvitePopupNotificationIds = new Set()
|
||||
let liveNotificationGeneration = 0
|
||||
let liveNotificationsEnabled = true
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
@@ -786,16 +789,19 @@ async function validateSession(sessionToken) {
|
||||
}
|
||||
|
||||
async function fetchCredentials() {
|
||||
const hadSession = !!credentials.value?.session
|
||||
const refreshId = ++credentialsRefreshId
|
||||
credentials.value = undefined
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
if (refreshId !== credentialsRefreshId) return
|
||||
if (!creds && hadSession) clearLiveNotifications()
|
||||
|
||||
if (creds && creds.user_id) {
|
||||
if (creds.session && !(await validateSession(creds.session))) {
|
||||
if (refreshId !== credentialsRefreshId) return
|
||||
|
||||
clearLiveNotifications()
|
||||
await logout().catch(handleError)
|
||||
if (refreshId !== credentialsRefreshId) return
|
||||
|
||||
@@ -806,6 +812,7 @@ async function fetchCredentials() {
|
||||
if (refreshId !== credentialsRefreshId) return
|
||||
}
|
||||
credentials.value = creds ?? null
|
||||
liveNotificationsEnabled = !!creds?.session
|
||||
}
|
||||
|
||||
async function signIn(flow = 'sign-in') {
|
||||
@@ -841,6 +848,7 @@ async function logOut() {
|
||||
async function performLogOut() {
|
||||
credentialsRefreshId++
|
||||
credentials.value = undefined
|
||||
clearLiveNotifications()
|
||||
|
||||
await logout().catch(handleError)
|
||||
await fetchCredentials()
|
||||
@@ -962,12 +970,13 @@ function openServerInviteInviterProfile(inviterName) {
|
||||
}
|
||||
|
||||
async function handleLiveNotification(notification) {
|
||||
if (!notification?.body || notification.read) return
|
||||
if (!liveNotificationsEnabled || !notification?.body || notification.read) return
|
||||
if (await sharedInstanceInviteHandler.value?.handleNotification(notification)) return
|
||||
|
||||
if (notification.body.type === 'server_invite') {
|
||||
if (displayedServerInviteNotifications.has(notification.id)) return
|
||||
|
||||
const generation = liveNotificationGeneration
|
||||
displayedServerInviteNotifications.add(notification.id)
|
||||
|
||||
const serverName =
|
||||
@@ -975,8 +984,9 @@ async function handleLiveNotification(notification) {
|
||||
const inviterId = notification.body.invited_by
|
||||
const invitedBy =
|
||||
typeof inviterId === 'string' ? await get_user(inviterId, 'bypass').catch(() => null) : null
|
||||
if (generation !== liveNotificationGeneration) return
|
||||
|
||||
addPopupNotification({
|
||||
const popupNotification = addPopupNotification({
|
||||
title: serverName,
|
||||
autoCloseMs: null,
|
||||
toast: {
|
||||
@@ -989,9 +999,21 @@ async function handleLiveNotification(notification) {
|
||||
onOpenActor: () => openServerInviteInviterProfile(invitedBy?.username ?? null),
|
||||
},
|
||||
})
|
||||
serverInvitePopupNotificationIds.add(popupNotification.id)
|
||||
}
|
||||
}
|
||||
|
||||
function clearLiveNotifications() {
|
||||
liveNotificationGeneration++
|
||||
liveNotificationsEnabled = false
|
||||
for (const id of serverInvitePopupNotificationIds) {
|
||||
popupNotificationManager.removeNotification(id)
|
||||
}
|
||||
displayedServerInviteNotifications.clear()
|
||||
serverInvitePopupNotificationIds.clear()
|
||||
sharedInstanceInviteHandler.value?.clearNotifications()
|
||||
}
|
||||
|
||||
async function handleCommand(e) {
|
||||
if (!e) return
|
||||
|
||||
|
||||
+13
-10
@@ -56,16 +56,19 @@ const { formatMessage } = useVIntl()
|
||||
const { notifySharedInstanceError } = useSharedInstanceErrors()
|
||||
const diffs = computed<ContentDiffItem[]>(
|
||||
() =>
|
||||
preview.value?.diffs.map((diff) => ({
|
||||
type: diff.type,
|
||||
projectName: diff.projectName ?? undefined,
|
||||
fileName: diff.fileName ?? undefined,
|
||||
currentVersionName: diff.currentVersionName ?? undefined,
|
||||
newVersionName: diff.newVersionName ?? undefined,
|
||||
fileCount: diff.configFileCount ?? undefined,
|
||||
disabled: diff.disabled,
|
||||
external: diff.type === 'added' && !diff.projectId && !!diff.fileName,
|
||||
})) ?? [],
|
||||
preview.value?.diffs
|
||||
// TODO: This is TEMP!!! Hashing needs to be done on backend
|
||||
.filter((diff) => !(diff.type === 'updated' && !diff.projectId && diff.fileName))
|
||||
.map((diff) => ({
|
||||
type: diff.type,
|
||||
projectName: diff.projectName ?? undefined,
|
||||
fileName: diff.fileName ?? undefined,
|
||||
currentVersionName: diff.currentVersionName ?? undefined,
|
||||
newVersionName: diff.newVersionName ?? undefined,
|
||||
fileCount: diff.configFileCount ?? undefined,
|
||||
disabled: diff.disabled,
|
||||
external: diff.type === 'added' && !diff.projectId && !!diff.fileName,
|
||||
})) ?? [],
|
||||
)
|
||||
|
||||
async function update() {
|
||||
|
||||
+2
@@ -28,6 +28,7 @@ const accountRequiredModal = ref<InstanceType<typeof ModrinthAccountRequiredModa
|
||||
const {
|
||||
handleNotification,
|
||||
installFromInviteId,
|
||||
clearNotifications,
|
||||
handleAlreadyInstalledCancel,
|
||||
handleAlreadyInstalledGoToInstance,
|
||||
handleAlreadyInstalledInstallAnyway,
|
||||
@@ -42,5 +43,6 @@ async function requestAuth(flow: ModrinthAuthFlow) {
|
||||
defineExpose<SharedInstanceInviteHandler>({
|
||||
handleNotification,
|
||||
installFromInviteId,
|
||||
clearNotifications,
|
||||
})
|
||||
</script>
|
||||
|
||||
+1
@@ -26,4 +26,5 @@ export type SharedInstanceInvite = {
|
||||
export type SharedInstanceInviteHandler = {
|
||||
handleNotification(notification: AppNotification): Promise<boolean>
|
||||
installFromInviteId(inviteId: string): Promise<void>
|
||||
clearNotifications(): void
|
||||
}
|
||||
|
||||
+32
-2
@@ -47,11 +47,14 @@ export function useSharedInstanceInviteHandler(
|
||||
const auth = injectAuth()
|
||||
const client = injectModrinthClient()
|
||||
const { handleError } = injectNotificationManager()
|
||||
const { addPopupNotification } = injectPopupNotificationManager()
|
||||
const popupNotificationManager = injectPopupNotificationManager()
|
||||
const queryClient = useQueryClient()
|
||||
const router = useRouter()
|
||||
const themeStore = useTheming()
|
||||
const displayedNotifications = new Set<string | number>()
|
||||
const displayedNotificationKeys = new Set<string>()
|
||||
const popupNotificationIds = new Set<string | number>()
|
||||
let notificationGeneration = 0
|
||||
let pendingAlreadyInstalled:
|
||||
| {
|
||||
instanceId: string
|
||||
@@ -183,9 +186,23 @@ export function useSharedInstanceInviteHandler(
|
||||
if (!parsedInvite) return false
|
||||
if (displayedNotifications.has(notification.id)) return true
|
||||
|
||||
const generation = notificationGeneration
|
||||
displayedNotifications.add(notification.id)
|
||||
const invite = await resolveInvite(parsedInvite)
|
||||
addPopupNotification({
|
||||
if (generation !== notificationGeneration) return true
|
||||
|
||||
const notificationKey = JSON.stringify([
|
||||
invite.invitedById ?? invite.invitedByUsername,
|
||||
invite.sharedInstanceName,
|
||||
invite.instanceIconUrl,
|
||||
])
|
||||
if (displayedNotificationKeys.has(notificationKey)) {
|
||||
await markNotificationRead(notification).catch((error) => handleError(toError(error)))
|
||||
return true
|
||||
}
|
||||
|
||||
displayedNotificationKeys.add(notificationKey)
|
||||
const popupNotification = popupNotificationManager.addPopupNotification({
|
||||
title: invite.sharedInstanceName,
|
||||
autoCloseMs: null,
|
||||
toast: {
|
||||
@@ -204,9 +221,21 @@ export function useSharedInstanceInviteHandler(
|
||||
},
|
||||
},
|
||||
})
|
||||
popupNotificationIds.add(popupNotification.id)
|
||||
return true
|
||||
}
|
||||
|
||||
function clearNotifications() {
|
||||
notificationGeneration++
|
||||
for (const id of popupNotificationIds) {
|
||||
popupNotificationManager.removeNotification(id)
|
||||
}
|
||||
displayedNotifications.clear()
|
||||
displayedNotificationKeys.clear()
|
||||
popupNotificationIds.clear()
|
||||
pendingAlreadyInstalled = undefined
|
||||
}
|
||||
|
||||
async function requireAccount() {
|
||||
if (!auth.isReady?.value) {
|
||||
await new Promise<void>((resolve) => {
|
||||
@@ -245,6 +274,7 @@ export function useSharedInstanceInviteHandler(
|
||||
return {
|
||||
handleNotification,
|
||||
installFromInviteId,
|
||||
clearNotifications,
|
||||
handleAlreadyInstalledCancel,
|
||||
handleAlreadyInstalledGoToInstance,
|
||||
handleAlreadyInstalledInstallAnyway,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
:link-max-uses="inviteLink.details.value?.maxUses"
|
||||
:update-invite-link="inviteLink.update"
|
||||
:user-profile-link="userProfileLink"
|
||||
:can-invite="!members.exclusiveMutationPending.value && !inviteLink.pending.value"
|
||||
@invite="invitePlayer"
|
||||
@cancel="cancelInvite"
|
||||
/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { InvitePlayersUser } from '@modrinth/ui'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query'
|
||||
import { computed, type Ref } from 'vue'
|
||||
import { computed, type Ref, ref } from 'vue'
|
||||
|
||||
import { get_user_many } from '@/helpers/cache.js'
|
||||
import {
|
||||
@@ -26,12 +26,14 @@ type OptimisticChange = {
|
||||
type InviteVariables = {
|
||||
user: InvitePlayersUser
|
||||
change: OptimisticChange
|
||||
exclusive: boolean
|
||||
}
|
||||
|
||||
type RemoveVariables = {
|
||||
id: string
|
||||
hasPendingRecipients: boolean
|
||||
change: OptimisticChange
|
||||
exclusive: boolean
|
||||
}
|
||||
|
||||
export function useSharedInstanceMembers(options: {
|
||||
@@ -45,6 +47,7 @@ export function useSharedInstanceMembers(options: {
|
||||
const queryKey = computed(() => ['sharedInstanceUsers', options.instance.value.id] as const)
|
||||
const invitingUserIds = new Set<string>()
|
||||
const removingUserIds = new Set<string>()
|
||||
const exclusiveMutationPending = ref(false)
|
||||
|
||||
const query = useQuery({
|
||||
queryKey,
|
||||
@@ -65,8 +68,9 @@ export function useSharedInstanceMembers(options: {
|
||||
rollback(change)
|
||||
options.onError(error)
|
||||
},
|
||||
onSettled: (_data, _error, { user }) => {
|
||||
onSettled: (_data, _error, { user, exclusive }) => {
|
||||
invitingUserIds.delete(normalizeInviteKey(user.id))
|
||||
if (exclusive) exclusiveMutationPending.value = false
|
||||
},
|
||||
})
|
||||
|
||||
@@ -77,8 +81,9 @@ export function useSharedInstanceMembers(options: {
|
||||
rollback(change)
|
||||
options.onError(error)
|
||||
},
|
||||
onSettled: (_data, _error, { id }) => {
|
||||
onSettled: (_data, _error, { id, exclusive }) => {
|
||||
removingUserIds.delete(normalizeInviteKey(id))
|
||||
if (exclusive) exclusiveMutationPending.value = false
|
||||
},
|
||||
})
|
||||
|
||||
@@ -133,23 +138,34 @@ export function useSharedInstanceMembers(options: {
|
||||
const normalizedId = normalizeInviteKey(user.id)
|
||||
if (
|
||||
options.actionsLocked.value ||
|
||||
exclusiveMutationPending.value ||
|
||||
invitingUserIds.has(normalizedId) ||
|
||||
find(user.id, user.username)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const exclusive = rows.value.length === 0
|
||||
invitingUserIds.add(normalizedId)
|
||||
if (exclusive) exclusiveMutationPending.value = true
|
||||
const change = beginOptimisticChange(user.id)
|
||||
updateRows(change.queryKey, (currentRows) => [...currentRows, inviteUserToRow(user)])
|
||||
inviteMutation.mutate({ user, change })
|
||||
inviteMutation.mutate({ user, change, exclusive })
|
||||
}
|
||||
|
||||
function remove(id: string) {
|
||||
const normalizedId = normalizeInviteKey(id)
|
||||
if (options.actionsLocked.value || removingUserIds.has(normalizedId)) return
|
||||
if (
|
||||
options.actionsLocked.value ||
|
||||
exclusiveMutationPending.value ||
|
||||
removingUserIds.has(normalizedId)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const exclusive = rows.value.length === 1
|
||||
removingUserIds.add(normalizedId)
|
||||
if (exclusive) exclusiveMutationPending.value = true
|
||||
const hasPendingRecipients = rows.value.some(
|
||||
(row) => row.pending && normalizeInviteKey(row.id) !== normalizedId,
|
||||
)
|
||||
@@ -157,7 +173,7 @@ export function useSharedInstanceMembers(options: {
|
||||
updateRows(change.queryKey, (currentRows) =>
|
||||
currentRows.filter((row) => normalizeInviteKey(row.id) !== normalizedId),
|
||||
)
|
||||
removeMutation.mutate({ id, hasPendingRecipients, change })
|
||||
removeMutation.mutate({ id, hasPendingRecipients, change, exclusive })
|
||||
}
|
||||
|
||||
function beginOptimisticChange(userId: string): OptimisticChange {
|
||||
@@ -197,7 +213,7 @@ export function useSharedInstanceMembers(options: {
|
||||
queryClient.setQueryData<ShareRow[]>(activeQueryKey, (currentRows = []) => update(currentRows))
|
||||
}
|
||||
|
||||
return { rows, query, find, invite, remove }
|
||||
return { rows, query, exclusiveMutationPending, find, invite, remove }
|
||||
}
|
||||
|
||||
function userEntries(users: SharedInstanceUsers): SharedInstanceUser[] {
|
||||
|
||||
Reference in New Issue
Block a user