feat: instance sharing thru shared-instances service (#6569)

* feat: implement instance share page + search_users backend call

* feat: invite players modal

* feat: use tanstack queries for friends sync across app pages

* feat: base shared instances implementation

* fix: admon style

* feat: impl instance admonitions like server panel

* fix: impl get + del usage

* feat: support modpack links

* feat: invite notif accepting

* fix: lint + fmt

* feat: impl install to play

* feat: impl usage of UpdateToPlayModal

* feat: warnings on deleting/disabling shared-instance version content

* fix: send instance name

* feat: align with backend

* feat: shared instances qa

* feat: wrong account protection

* feat: qa

* fix: smartly apply updates

* fix: install bug

* fix: 401/404 differentiation

* fix: fmt+prepr

* feat: qa

* feat: qa

* fix: signing out messes up revoke/deleted checks

* feat: qa

* fix: fmt + lint

* feat: lock content if part of shared instance

* fix: lint

* [do not merge] feat: rough invite links impl temp (#6666)

* fix: wrong cmd

* feat: invite page

* fix: server-manager DTO mismatch

* fix: drop anonymous invite link acceptance

* refactor: structured shared-instance unavailable errors

* refactor: centralise error presentations

* refactor: dedupe shared instance diff detection

* fix: logging in reqwests

* refactor: move app.vue shared instances into handler

* refactor: break up Share.vue

* refactor: split up shared instances state outside of instance index

* refactor: dedicated shared instances install/update modals + split up page

* refactor: centralized managed content

* refactor: split up install shared to own runner + shared.rs split up

* refactor: dedupe sql for instance metadata enrichmnt

* refactor: friends composable + dedupe friends logic across usages

* chore: reduced unused code

* fix: align with backend

* fix: lint

* fix: file sha changes

* fix: invite links not working due to icon signed

* feat: qa

* feat: reporting frontend dummy

* fix: try use header

* remove: file hash field

* fix: pin box

* feat: malware warning for shared instances

* fix: cache rule

* feat: config files syncing

* feat: disable config sharing

* fix: header

* fix: use mark ready

* fix: dont cause push update for configs

* fix: lint

* feat: sharing page in settings

* feat: move config + change flow

* fix: qa

* fix: lint prepr

* feat: proxy file upload thru shared instances backend

* fix: use collapisible

* fix: push config

* fix: config

* feat: swap out sign in modal for new one

* fix: report flow

* fix: exclude configs.zip from external warnings

* fix: nuxi init

* fix: config bundle downloading

* fix: error notif

* fix: polling

* fix: qa

* fix: lint + prepr

* feat: shared instances moderation frontend + hook up report flow

* fix: report copy

* fix: lint

* fix: lint

* fix: modrinth ids being undefined

* feat: instance quarantining

* fix: prepr + fmt

* fix: quarantined -> locked terminology

* fix: missing endpoint impls + fmt

* fix: missing api in build.rs

* fix: share tab jittery

* fix: fmt

*PT bug

* fix: invites count as users even if pending

* fix: prepr

* fix: invite page owner in users list

* fix: lint

* fix: qa

* fix: lint

* fix: members stale not clearing

* fix: invite use joined_at field

* fix: lint

* fix: qa

---------

Co-authored-by: sychic <47618543+Sychic@users.noreply.github.com>
This commit is contained in:
Calum H.
2026-07-24 13:06:38 +00:00
committed by GitHub
co-authored by sychic
parent 2e0d797bb0
commit e58af98f21
269 changed files with 18202 additions and 2579 deletions
+48 -15
View File
@@ -9,25 +9,47 @@
<slot name="icon" :icon-class="['h-6 w-6 flex-none', iconClasses[type]]">
<component :is="getSeverityIcon(type)" :class="['h-6 w-6 flex-none', iconClasses[type]]" />
</slot>
<div class="col-start-2 flex min-w-0 flex-1 flex-col gap-2">
<div
class="col-start-2 min-w-0"
:class="
inlineActions && !showActionsUnderneath && $slots.actions
? 'flex flex-wrap items-start gap-x-4 gap-y-3'
: 'flex flex-1 flex-col gap-2'
"
>
<div
v-if="header || $slots.header || normalizedTimestamp"
class="flex flex-wrap items-center gap-2 text-lg font-semibold leading-6"
class="flex min-w-0 flex-1 flex-col gap-2"
:class="
inlineActions && !showActionsUnderneath && $slots.actions
? 'admonition-inline-content'
: ''
"
>
<slot name="header">{{ header }}</slot>
<span
v-if="normalizedTimestamp"
v-tooltip="timestampTooltip"
class="flex items-center gap-1.5 text-base font-medium leading-normal text-secondary"
<div
v-if="header || $slots.header || normalizedTimestamp"
class="flex flex-wrap items-center gap-2 text-lg font-semibold leading-6"
>
<ClockIcon class="size-4" />
{{ relativeTimeLabel }}
</span>
<slot name="header">{{ header }}</slot>
<span
v-if="normalizedTimestamp"
v-tooltip="timestampTooltip"
class="flex items-center gap-1.5 text-base font-medium leading-normal text-secondary"
>
<ClockIcon class="size-4" />
{{ relativeTimeLabel }}
</span>
</div>
<div class="font-normal text-contrast/85 leading-tight">
<slot>{{ body }}</slot>
</div>
</div>
<div class="font-normal text-contrast/85 leading-tight">
<slot>{{ body }}</slot>
<div
v-if="inlineActions && !showActionsUnderneath && $slots.actions"
class="ml-auto flex shrink-0 items-center justify-end self-center"
>
<slot name="actions" />
</div>
<div v-if="showActionsUnderneath || $slots.actions" class="mt-2">
<div v-else-if="showActionsUnderneath || $slots.actions" class="mt-2">
<slot name="actions" />
</div>
</div>
@@ -80,9 +102,10 @@ import ButtonStyled from './ButtonStyled.vue'
const props = withDefaults(
defineProps<{
type?: 'info' | 'warning' | 'critical' | 'success' | 'moderation' | 'circle-warning'
type?: 'info' | 'warning' | 'critical' | 'success' | 'moderation' | 'circle-warning' | 'neutral'
header?: string
body?: string
inlineActions?: boolean
showActionsUnderneath?: boolean
dismissible?: boolean
progress?: number
@@ -95,6 +118,7 @@ const props = withDefaults(
type: 'info',
header: '',
body: '',
inlineActions: false,
showActionsUnderneath: false,
dismissible: false,
progress: undefined,
@@ -143,6 +167,7 @@ const typeClasses = {
critical: 'border-brand-red bg-bg-red',
success: 'border-brand-green bg-bg-green',
moderation: 'border-brand-orange bg-bg-orange',
neutral: 'border-surface-4 bg-surface-3',
}
const iconClasses = {
@@ -152,6 +177,7 @@ const iconClasses = {
critical: 'text-brand-red',
success: 'text-brand-green',
moderation: 'text-brand-orange',
neutral: 'text-secondary',
}
const buttonColors = {
@@ -161,6 +187,7 @@ const buttonColors = {
critical: 'red',
success: 'green',
moderation: 'orange',
neutral: 'standard',
} as const
const progressTrackClasses = {
@@ -170,6 +197,7 @@ const progressTrackClasses = {
critical: 'bg-brand-red/20',
success: 'bg-brand-green/20',
moderation: 'bg-brand-orange/20',
neutral: 'bg-surface-4',
}
const progressFillClasses = {
@@ -181,10 +209,15 @@ const progressFillClasses = {
blue: 'bg-brand-blue',
green: 'bg-brand-green',
red: 'bg-brand-red',
neutral: 'bg-surface-5',
}
</script>
<style scoped>
.admonition-inline-content {
min-width: min(100%, 16rem);
}
.admonition-progress--waiting {
animation: admonition-progress-waiting 1s linear infinite;
position: relative;
@@ -34,6 +34,7 @@
</div>
<div class="ml-2 flex shrink-0 items-center gap-4">
<button
v-if="showSize"
type="button"
class="hidden w-[92px] appearance-none items-center gap-1 border-0 bg-transparent p-0 text-left font-semibold hover:text-primary sm:flex"
:class="sortField === 'size' ? 'text-contrast' : 'text-secondary'"
@@ -52,6 +53,7 @@
/>
</button>
<button
v-if="showModified"
type="button"
class="hidden w-[132px] appearance-none items-center gap-1 border-0 bg-transparent p-0 text-left font-semibold hover:text-primary sm:flex"
:class="sortField === 'modified' ? 'text-contrast' : 'text-secondary'"
@@ -90,8 +92,11 @@
{{ formatMessage(messages.parentFolder) }}
</span>
<div class="ml-2 flex shrink-0 items-center gap-4">
<span class="hidden w-[92px] text-left text-sm text-secondary sm:block" />
<span class="hidden w-[132px] text-left text-sm text-secondary sm:block" />
<span v-if="showSize" class="hidden w-[92px] text-left text-sm text-secondary sm:block" />
<span
v-if="showModified"
class="hidden w-[132px] text-left text-sm text-secondary sm:block"
/>
<span class="size-4 shrink-0" aria-hidden="true" />
</div>
</div>
@@ -140,10 +145,16 @@
{{ entry.name }}
</span>
<div class="ml-2 flex shrink-0 items-center gap-4">
<span class="hidden w-[92px] truncate text-left text-sm text-secondary sm:block">
<span
v-if="showSize"
class="hidden w-[92px] truncate text-left text-sm text-secondary sm:block"
>
{{ formatSize(entry) }}
</span>
<span class="hidden w-[132px] truncate text-left text-sm text-secondary sm:block">
<span
v-if="showModified"
class="hidden w-[132px] truncate text-left text-sm text-secondary sm:block"
>
{{ formatModified(entry) }}
</span>
<ChevronRightIcon
@@ -164,8 +175,8 @@
<span class="size-4 shrink-0" />
<span class="min-w-0 flex-1 truncate text-sm font-medium opacity-0">.</span>
<div class="ml-2 flex shrink-0 items-center gap-4">
<span class="hidden w-[92px] text-left text-sm sm:block" />
<span class="hidden w-[132px] text-left text-sm sm:block" />
<span v-if="showSize" class="hidden w-[92px] text-left text-sm sm:block" />
<span v-if="showModified" class="hidden w-[132px] text-left text-sm sm:block" />
<span class="size-4 shrink-0" />
</div>
</div>
@@ -262,10 +273,14 @@ const props = withDefaults(
defineProps<{
items: FileTreeSelectItem[]
modelValue: string[]
showSize?: boolean
showModified?: boolean
}>(),
{
items: () => [],
modelValue: () => [],
showSize: true,
showModified: true,
},
)
@@ -1,7 +1,7 @@
<template>
<div
ref="metadata"
class="page-header-metadata flex min-w-0 flex-wrap items-center gap-x-2 gap-y-2"
class="page-header-metadata flex min-w-0 flex-wrap items-center gap-x-[1.625rem] gap-y-2"
>
<slot />
</div>
@@ -1,6 +1,10 @@
<template>
<div :class="rootClass" data-page-header-metadata-item v-bind="$attrs">
<BulletDivider class="page-header-metadata-item-divider shrink-0" />
<span
class="page-header-metadata-item-divider absolute right-full flex h-full w-[1.625rem] items-center justify-center"
>
<BulletDivider class="shrink-0" />
</span>
<AutoLink
v-if="to && !disabled"
v-tooltip="tooltip"
@@ -72,7 +76,7 @@ const props = withDefaults(defineProps<PageHeaderMetadataItemProps>(), {
const defaultIconClass = 'block size-5 shrink-0 text-current'
const baseClass =
'flex min-w-0 items-center gap-2 font-medium leading-none text-secondary text-nowrap'
'relative flex min-w-0 items-center font-medium leading-none text-secondary text-nowrap'
const contentBaseClass = 'inline-flex min-w-0 items-center gap-2 text-inherit'
const interactiveClass = 'm-0 cursor-pointer border-0 bg-transparent p-0 hover:underline'