mirror of
https://github.com/modrinth/code.git
synced 2026-09-01 12:36:44 +00:00
fix: lint + styling
This commit is contained in:
@@ -11,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ArrowBigUpDashIcon,
|
ArrowBigUpDashIcon,
|
||||||
ChangeSkinIcon,
|
ChangeSkinIcon,
|
||||||
|
CheckIcon,
|
||||||
CompassIcon,
|
CompassIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ExternalIcon,
|
ExternalIcon,
|
||||||
@@ -40,6 +41,7 @@ import {
|
|||||||
defineMessages,
|
defineMessages,
|
||||||
I18nDebugPanel,
|
I18nDebugPanel,
|
||||||
LoadingBar,
|
LoadingBar,
|
||||||
|
ModrinthHostingLogo,
|
||||||
NewsArticleCard,
|
NewsArticleCard,
|
||||||
NotificationPanel,
|
NotificationPanel,
|
||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
@@ -84,6 +86,7 @@ import InstallToPlayModal from '@/components/ui/modal/InstallToPlayModal.vue'
|
|||||||
import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
|
import ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
|
||||||
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
|
import UpdateToPlayModal from '@/components/ui/modal/UpdateToPlayModal.vue'
|
||||||
import NavButton from '@/components/ui/NavButton.vue'
|
import NavButton from '@/components/ui/NavButton.vue'
|
||||||
|
import ServerInvitePopupBody from '@/components/ui/notifications/ServerInvitePopupBody.vue'
|
||||||
import PrideFundraiserBanner from '@/components/ui/PrideFundraiserBanner.vue'
|
import PrideFundraiserBanner from '@/components/ui/PrideFundraiserBanner.vue'
|
||||||
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||||
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
|
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
|
||||||
@@ -756,7 +759,15 @@ command_listener(handleCommand)
|
|||||||
notification_listener(handleLiveNotification)
|
notification_listener(handleLiveNotification)
|
||||||
|
|
||||||
async function markLiveNotificationRead(notification) {
|
async function markLiveNotificationRead(notification) {
|
||||||
await tauriApiClient.labrinth.notifications_v2.markAsRead(notification.id)
|
try {
|
||||||
|
await tauriApiClient.labrinth.notifications_v2.markAsRead(notification.id)
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ModrinthApiError && error.statusCode === 404) {
|
||||||
|
console.warn(`notification ${notification.id} could not be marked as read`, error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
throw error
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function respondToServerInvite(notification, action) {
|
async function respondToServerInvite(notification, action) {
|
||||||
@@ -765,12 +776,12 @@ async function respondToServerInvite(notification, action) {
|
|||||||
throw new Error('Missing server ID for invite notification.')
|
throw new Error('Missing server ID for invite notification.')
|
||||||
}
|
}
|
||||||
|
|
||||||
await markLiveNotificationRead(notification)
|
|
||||||
await tauriApiClient.request(`/servers/${serverId}/invites/${action}`, {
|
await tauriApiClient.request(`/servers/${serverId}/invites/${action}`, {
|
||||||
api: 'archon',
|
api: 'archon',
|
||||||
version: 1,
|
version: 1,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
})
|
})
|
||||||
|
await markLiveNotificationRead(notification)
|
||||||
|
|
||||||
return serverId
|
return serverId
|
||||||
}
|
}
|
||||||
@@ -804,21 +815,28 @@ async function handleLiveNotification(notification) {
|
|||||||
const inviterId = notification.body.invited_by
|
const inviterId = notification.body.invited_by
|
||||||
const invitedBy =
|
const invitedBy =
|
||||||
typeof inviterId === 'string' ? await get_user(inviterId, 'bypass').catch(() => null) : null
|
typeof inviterId === 'string' ? await get_user(inviterId, 'bypass').catch(() => null) : null
|
||||||
const inviterName = invitedBy?.username ?? 'Someone'
|
|
||||||
|
|
||||||
addPopupNotification({
|
addPopupNotification({
|
||||||
title: 'Modrinth Hosting',
|
title: 'Modrinth Hosting',
|
||||||
text: `${inviterName} has invited you to join ${serverName}.`,
|
titleLogo: ModrinthHostingLogo,
|
||||||
|
bodyComponent: ServerInvitePopupBody,
|
||||||
|
bodyProps: {
|
||||||
|
inviterName: invitedBy?.username ?? null,
|
||||||
|
inviterAvatarUrl: invitedBy?.avatar_url ?? null,
|
||||||
|
serverName,
|
||||||
|
},
|
||||||
type: 'info',
|
type: 'info',
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
label: 'Accept',
|
label: 'Accept',
|
||||||
action: () => acceptServerInviteNotification(notification),
|
action: () => acceptServerInviteNotification(notification),
|
||||||
|
icon: CheckIcon,
|
||||||
color: 'brand',
|
color: 'brand',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Decline',
|
label: 'Decline',
|
||||||
action: () => declineServerInviteNotification(notification),
|
action: () => declineServerInviteNotification(notification),
|
||||||
|
icon: XIcon,
|
||||||
color: 'red',
|
color: 'red',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
<template>
|
||||||
|
<div class="flex flex-wrap items-center gap-x-1.5 gap-y-1 leading-snug text-primary">
|
||||||
|
<button
|
||||||
|
v-if="inviterName"
|
||||||
|
type="button"
|
||||||
|
class="inline-flex min-w-0 items-center border-0 bg-transparent p-0 font-semibold text-contrast hover:underline"
|
||||||
|
@click="openInviterProfile(inviterName)"
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
:src="inviterAvatarUrl"
|
||||||
|
:alt="inviterName"
|
||||||
|
circle
|
||||||
|
size="xxs"
|
||||||
|
no-shadow
|
||||||
|
class="mr-1.5 inline-flex"
|
||||||
|
/>
|
||||||
|
<span>{{ inviterName }}</span>
|
||||||
|
</button>
|
||||||
|
<span>
|
||||||
|
<span v-if="inviterName" class="whitespace-nowrap">has invited you to manage</span>
|
||||||
|
<span v-else class="whitespace-nowrap">You have been invited to manage</span>
|
||||||
|
<span class="font-semibold text-contrast ml-1">{{ serverName }}</span>
|
||||||
|
<span>.</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Avatar } from '@modrinth/ui'
|
||||||
|
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||||
|
|
||||||
|
import { config } from '@/config'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
inviterName: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
inviterAvatarUrl: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
serverName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
function openInviterProfile(username) {
|
||||||
|
openUrl(`${config.siteUrl}/user/${encodeURIComponent(username)}`)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -42,6 +42,12 @@
|
|||||||
{
|
{
|
||||||
"url": "https://api.purpurmc.org/*"
|
"url": "https://api.purpurmc.org/*"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "http://localhost:8000/*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "http://127.0.0.1:8000/*"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "http://*.taila228c5.ts.net/*"
|
"url": "http://*.taila228c5.ts.net/*"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ use crate::auth::AuthenticationError;
|
|||||||
use crate::auth::validate::get_user_record_from_bearer_token;
|
use crate::auth::validate::get_user_record_from_bearer_token;
|
||||||
use crate::database::PgPool;
|
use crate::database::PgPool;
|
||||||
use crate::database::models::friend_item::DBFriend;
|
use crate::database::models::friend_item::DBFriend;
|
||||||
|
use crate::database::models::notification_item::DBNotification;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::notifications::Notification;
|
use crate::models::notifications::{Notification, NotificationBody};
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::users::User;
|
use crate::models::users::User;
|
||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
@@ -129,6 +130,26 @@ pub async fn ws_init(
|
|||||||
)?)
|
)?)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
let unread_server_invites = DBNotification::get_many_user_exposed_on_site(
|
||||||
|
user_id.into(),
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.into_iter()
|
||||||
|
.filter(|notification| {
|
||||||
|
!notification.read
|
||||||
|
&& matches!(
|
||||||
|
¬ification.body,
|
||||||
|
NotificationBody::ServerInvite { .. }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.map(Notification::from);
|
||||||
|
|
||||||
|
for notification in unread_server_invites {
|
||||||
|
let _ = session.text(serde_json::to_string(¬ification)?).await;
|
||||||
|
}
|
||||||
|
|
||||||
let db = db.clone();
|
let db = db.clone();
|
||||||
let socket_id = db.next_socket_id.fetch_add(1, Ordering::Relaxed);
|
let socket_id = db.next_socket_id.fetch_add(1, Ordering::Relaxed);
|
||||||
db.sockets
|
db.sockets
|
||||||
|
|||||||
@@ -18,28 +18,35 @@
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col gap-2 w-full">
|
<div class="flex flex-col gap-2 w-full">
|
||||||
<div class="flex items-center justify-between gap-2.5">
|
<div class="flex items-center justify-between gap-2.5">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex min-w-0 flex-1 items-center gap-2">
|
||||||
<div
|
<component
|
||||||
class="flex items-center"
|
:is="item.titleLogo"
|
||||||
:class="{
|
v-if="item.titleLogo"
|
||||||
'text-red': item.type === 'error',
|
class="h-7 w-auto min-w-0 max-w-full text-contrast"
|
||||||
'text-orange': item.type === 'warning',
|
/>
|
||||||
'text-green': item.type === 'download',
|
<template v-else>
|
||||||
'text-contrast': item.type === 'success',
|
<div
|
||||||
'text-blue':
|
class="flex items-center"
|
||||||
!item.type ||
|
:class="{
|
||||||
!['error', 'warning', 'success', 'download'].includes(item.type),
|
'text-red': item.type === 'error',
|
||||||
}"
|
'text-orange': item.type === 'warning',
|
||||||
>
|
'text-green': item.type === 'download',
|
||||||
<IssuesIcon v-if="item.type === 'warning'" class="h-5 w-5" />
|
'text-contrast': item.type === 'success',
|
||||||
<DownloadIcon v-else-if="item.type === 'download'" class="h-5 w-5" />
|
'text-blue':
|
||||||
<CheckCircleIcon v-else-if="item.type === 'success'" class="h-5 w-5" />
|
!item.type ||
|
||||||
<XCircleIcon v-else-if="item.type === 'error'" class="h-5 w-5" />
|
!['error', 'warning', 'success', 'download'].includes(item.type),
|
||||||
<InfoIcon v-else class="h-5 w-5" />
|
}"
|
||||||
</div>
|
>
|
||||||
<div class="text-contrast font-semibold m-0 grow">
|
<IssuesIcon v-if="item.type === 'warning'" class="h-5 w-5" />
|
||||||
{{ item.title }}
|
<DownloadIcon v-else-if="item.type === 'download'" class="h-5 w-5" />
|
||||||
</div>
|
<CheckCircleIcon v-else-if="item.type === 'success'" class="h-5 w-5" />
|
||||||
|
<XCircleIcon v-else-if="item.type === 'error'" class="h-5 w-5" />
|
||||||
|
<InfoIcon v-else class="h-5 w-5" />
|
||||||
|
</div>
|
||||||
|
<div class="text-contrast font-semibold m-0 grow">
|
||||||
|
{{ item.title }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<ButtonStyled size="small" type="transparent" circular>
|
<ButtonStyled size="small" type="transparent" circular>
|
||||||
<button @click="dismiss(item.id)">
|
<button @click="dismiss(item.id)">
|
||||||
@@ -50,6 +57,11 @@
|
|||||||
<span v-if="item.text" class="text-primary">
|
<span v-if="item.text" class="text-primary">
|
||||||
{{ item.text }}
|
{{ item.text }}
|
||||||
</span>
|
</span>
|
||||||
|
<component
|
||||||
|
:is="item.bodyComponent"
|
||||||
|
v-if="item.bodyComponent"
|
||||||
|
v-bind="item.bodyProps ?? {}"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="item.progressItems?.length" class="flex flex-col gap-3">
|
<div v-if="item.progressItems?.length" class="flex flex-col gap-3">
|
||||||
<div
|
<div
|
||||||
@@ -89,6 +101,7 @@
|
|||||||
:color="btn.color || (idx === 0 ? 'brand' : undefined)"
|
:color="btn.color || (idx === 0 ? 'brand' : undefined)"
|
||||||
>
|
>
|
||||||
<button @click="handleButtonClick(item.id, btn)">
|
<button @click="handleButtonClick(item.id, btn)">
|
||||||
|
<component :is="btn.icon" v-if="btn.icon" />
|
||||||
{{ btn.label }}
|
{{ btn.label }}
|
||||||
</button>
|
</button>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
@@ -166,7 +179,7 @@ withDefaults(
|
|||||||
top: calc(var(--top-bar-height, 3rem) + 1.5rem);
|
top: calc(var(--top-bar-height, 3rem) + 1.5rem);
|
||||||
right: 1.5rem;
|
right: 1.5rem;
|
||||||
z-index: 200;
|
z-index: 200;
|
||||||
width: 400px;
|
width: 520px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.75rem;
|
gap: 0.75rem;
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ export * from './icons'
|
|||||||
export { default as InstallingBanner } from './InstallingBanner.vue'
|
export { default as InstallingBanner } from './InstallingBanner.vue'
|
||||||
export * from './labels'
|
export * from './labels'
|
||||||
export * from './marketing'
|
export * from './marketing'
|
||||||
|
export { default as ModrinthHostingLogo } from './ModrinthServersIcon.vue'
|
||||||
|
export { default as ModrinthServersIcon } from './ModrinthServersIcon.vue'
|
||||||
export { default as SaveBanner } from './SaveBanner.vue'
|
export { default as SaveBanner } from './SaveBanner.vue'
|
||||||
export * from './server-header'
|
export * from './server-header'
|
||||||
export { default as ServerListEmpty } from './server-list-empty/ServerListEmpty.vue'
|
export { default as ServerListEmpty } from './server-list-empty/ServerListEmpty.vue'
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
|
import type { Component } from 'vue'
|
||||||
|
|
||||||
import { createContext } from '.'
|
import { createContext } from '.'
|
||||||
|
|
||||||
export interface PopupNotificationButton {
|
export interface PopupNotificationButton {
|
||||||
label: string
|
label: string
|
||||||
action: () => void
|
action: () => void
|
||||||
|
icon?: Component
|
||||||
color?: 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'standard'
|
color?: 'brand' | 'red' | 'orange' | 'green' | 'blue' | 'standard'
|
||||||
keepOpen?: boolean
|
keepOpen?: boolean
|
||||||
}
|
}
|
||||||
@@ -18,6 +21,9 @@ export interface PopupNotificationProgressItem {
|
|||||||
export interface PopupNotification {
|
export interface PopupNotification {
|
||||||
id: string | number
|
id: string | number
|
||||||
title: string
|
title: string
|
||||||
|
titleLogo?: Component
|
||||||
|
bodyComponent?: Component
|
||||||
|
bodyProps?: Record<string, unknown>
|
||||||
text?: string
|
text?: string
|
||||||
type?: 'error' | 'warning' | 'success' | 'info' | 'download'
|
type?: 'error' | 'warning' | 'success' | 'info' | 'download'
|
||||||
progress?: number
|
progress?: number
|
||||||
|
|||||||
Reference in New Issue
Block a user