mirror of
https://github.com/modrinth/code.git
synced 2026-08-26 17:44:50 +00:00
feat: server management in app (#5628)
* start new server settings tabs
* update properties tab to match design
* better stying in general tab
* feat: add suffix input for hostname field
* implement tables for allocations and DNS records
* add tags for dns record type
* small gap adjustment
* polish advanced page
* adjust properties page hierarchy
* fix searching properties, empty state and projection radius appearing
* pnpm prepr
* update copy to match designs
* fix suffix input component
* style fixes and match heading size
* small fix
* fix search allocations placeholder
* adjust table styles
* move all installation settings helper text to below input
* update icon to use overflow menu buttons
* fix modal to be consistent
* open advanced properties when search
* remove other and custom properties, and update styles
* remove hide/show all java versions
* handle mc 26
* refactor: move server settings pages into /ui and add app ServerSettingsModal
* hook up server pages for app
* add server page header to app
* hook up server settings modal
* use large size
* fix card box shadow style
* fix hostname input for app
* fix app/website card containers
* implement external tabs for billing and admin billing
* fix save banner fixed to parent instead of page body
* remove unused prop to FriendsList causing warning in app
* fix client-only not available for app
* fix bottom cut off
* wire node auth
* implement full copy buttons
* dedup copy button tailwind styles
* fix hover class not working in @apply
* fix spacing
* fix error validation styles
* apply consistent styles and spacing
* feat: update hosting server card (#5609)
* fix type errors
* fix some stylesheets not imported for storybook
* add server listing stories
* add fix for frontend stylesheet imports
* remove props.
* convert copy code to use tailwind
* update server listing component styles
* update server info label styles
* start status/player count info label, more style updates and fixes
* add new server card buttons
* hook up server cards and implement updated styles
* hook up on download button
* fix tauri throwing error when api returns 204 No Content
* hook up purchase server modal in app
* fix upgrading state loading icon
* pnpm prepr
* filter out servers past 30 days after cancellation
* do not apply opacity on lock or spiner icons
* fix disabled server icon background
* update pending change stage
* handle known suspension states
* refactor: reduce code duplication for server listing
* update disabled state text color
* fix loading icon color
* clean up copy
* fix disabled opacity for server card
* update server listing files kept to be countdown
* implement resubscribe modal
* implement proper provisioning state for resubscribe
* fix duplicate attribute and pnpm prepr
* feat: add shared UI package auth DI
* feat: update purchase server flow (#5714)
* implement server list empty state component
* fix stories and adjust spacing
* implement select plan design refresh
* implement auth for empty server list
* use refs instead of reactive
* pnpm prepr
* fix auth usage for empty servers list
* move app auth provider setup to src/providers/setup
* pnpm prepr
* fix max height
* style fix
* fix getCreds no auth is blocking api client
* implement servers guest plan modal and signin which redirects back to modal's next step
* refactor guest plan select logic into provider
* implement sign in or create account popup
* remove force empty serverList
* add download button for suspended mod and generic
* add handling for when user logs out
* QA pass style fixes
* more consistent page styles
* fix duplicate export
* refactor: remove all fallback stuff from resubscribe modal
* implement shared download latest backup util
* i18n pass
* pnpm prepr
* fix region being selected if ping failed
* pnpm prepr
* feat: servers in app finalization (#5744)
* feat: start on shared console implementation into logs and overview pages
* fix: terminal gap issues
* feat: swap word wrap for full screen
* fix: stats cards alignment
* fix: stats
* feat: fix console clear + remove copy
* fix: lint
* fix: use reset not clear
* feat: shared server header & overview page for app and website (#5736)
* feat: implement shared server header for app and website
* feat: implement wrapped overview page with shared composable and hook it up
* pnpm prepr
* fix: bugs
* qa: cleanup
* feat: root.vue shared layout
* feat: delete old options pages + fix discovery frontend
* fix: discovery
* fix: misc style/layout issues
* fix page padding
* fix: modal height jankiness
* feat: implement server install content in app and server setup modal with DI
* fix: spacing
* remove servers in app feature flag
* Revert "remove servers in app feature flag"
This reverts commit 86e284c4bd.
* fix: qa
* feat: remove legacy components from apps/frontend/src/components/ui/servers
---------
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
* qa pass (#5738)
* fix: qa
* feat: qa
* fix: server icon fetch fails due to global node auth race condition overriding each other
* fix: lint
* fix: server icon upload/sync and centralize logic
* fix: server settings modal not closing for server reset
* fix: better server sorting
* feat: copy address in server listing card
* fix: notification panel in modal and when overlapping with action bar
* fix: empty server list empty state flashing when refresh, fixed by adding isReady auth flag
* feat: use floating action bar for save banner
* fix: saving state in save bar
* fix: edit server icon styling
* fix: confirm modal to have consistent buttons
* feat: loading animation for server panel + caching improvements for app
* pnpm prepr
* feat: search page deduplication (#5754)
* fix: action bar behind modal
* fix: remove warning modal for stopping
* fix: server cards states
* we hate webkit we hate webkit
* fix: update allocation creation to not use modal
* fix: properties tab spacing and styles
* feat: add files tab copy
* fix: advanced properties icon
* fix: remove back to all servers link
* feat: add files tab link in copy
* fix: server header styles to be consistent with instance
* fix: add header icons back
* feat: update instance settings icon to be consistent
* fix: icon container
* feat: upload state persistence across tabs
* fix: server labels text wrapping
* fix: use surface-5 border
* fix: loading spinner showing with onboarding below
* feat: new server button shows purchase modal in website
* fix: billing page not showing quarterly interval
* fix: server downgrade not showing updated subscription notification
* fix: server settings invalidate saved state and remove server context provider since its already provided in the page
* pnpm prepr
* add stripe publishable key to app build
* feat: console highlighting
* fix: rename servers title to modrinth hosting
* feat: search fix
* fix: qa/styles
* fix: ip click active and remove power dont ask again
* fix: qa
* feat: highlighting fix console
* fix: disable conflicts action
* fix: error dismiss bug
* feat: modal clarification
* fix: files perms issue
* fix: lint
* feat: modal fix
* enable show uptime
* fix: add loading state to edit server icon
* fix: notification panel take in has sidebar from settings
* fix: consistency pass on app settings
* fix: consistency pass on instance settings
* pnpm prepr
* fix: nagivate to billing button in app to go to website
* fix: stripe return url in app causing app to open modrinth.com in tauri
* refactor: better show polling UI code
* fix: new server polling comparison to use server ids instead of length
* fix: buttonstyled story
* fix: button styling
* fix: content.vue regression
* feat: project url redirects
* fix: breadcrumbs
* fix: purchase with newly added card
* fix: console ordering problems
* fix: app-frontend missing env config and staging environment
* fix: log syncing for instances and server panel accidentally
* fix: QA issues
* fix: server page loading state
* fix: stats card logic
* fix: lint
* fix: qa
* fix: console height padding
* fix: terminal padding + loading indicator
* feat: update medal server listing styling
* fix: no upgrade button for medal server listing in app
* fix: go to overview instead of content tab after onboarding
* fix: qa
* fix: teleport modals to body
* fix: logs tab + qa
* fix: local storage for user preferences
* fix: qa loading indic
* feat: considitonal debug and trace
* fix: jump to top on install bug
* feat: swap out server hard drive icon to server stack icon
* feat: servers in app feature flag default true
* fix: highlight row ufll
* fix: webkit thing onto a tag
* fix: input field
* fix: clear fix
* fix: lint
* fix: fmt
* feat: improve share modal and bring it back for sharing log
* pnpm prepr
* fix: menu overflowing
* feat: remove servers in app feature flag
* fix: server stat charts no longer showing color
* fix: library nav no primary state
* fix: better modal height and width
* fix: highlighting bugs
* fix: empty states
* fix: delay import to fix overview page slow load on MacOS
* fix: medal server listing too bright on light mode
* fix: admon analysis + fix logs
* fix: bug
* fix: clear purchase intent from sign-in after closing modal
* performance: improve server manage stats loading by splitting reactivity
* fix: deploy + admon + disable highlighting
* fix: clippy
---------
Co-authored-by: tdgao <mr.trumgao@gmail.com>
Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com>
* feat: temp wrangler
* fix: lint
* fix: logs upload
* fix: console empty state and admon regressions
* fix: fields
* feat: log deleting + prefetch for Logs.vue
* feat: move delete before share
* feat: clear endpoint
* feat: we ball!
---------
Co-authored-by: Calum H. <calum@modrinth.com>
Co-authored-by: Calum H. (IMB11) <contact@cal.engineer>
This commit is contained in:
co-authored by
Calum H.
tdgao
Calum H.
parent
a2a97d1313
commit
693a371d61
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :noblur="noblur" :danger="danger" :on-hide="onHide">
|
||||
<NewModal ref="modal" :noblur="noblur" :danger="danger" :on-hide="onHide" max-width="550px">
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
<span class="font-extrabold text-contrast text-lg">{{ title }}</span>
|
||||
@@ -30,19 +30,19 @@
|
||||
placeholder="Type here..."
|
||||
wrapper-class="max-w-[20rem]"
|
||||
/>
|
||||
<div class="flex gap-2">
|
||||
<div class="flex gap-2 justify-end">
|
||||
<ButtonStyled>
|
||||
<button class="!shadow-none" @click="hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled :color="danger ? 'red' : 'brand'">
|
||||
<button :disabled="action_disabled" @click="proceed">
|
||||
<component :is="proceedIcon" />
|
||||
{{ proceedLabel }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled>
|
||||
<button @click="hide()">
|
||||
<XIcon />
|
||||
Cancel
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</NewModal>
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
import { computed, nextTick, ref } from 'vue'
|
||||
|
||||
import { Button, Modal, StyledInput } from '../index'
|
||||
import { injectNotificationManager } from '#ui/providers'
|
||||
|
||||
import { Button, ButtonStyled, NewModal, StyledInput } from '../index'
|
||||
|
||||
const props = defineProps({
|
||||
header: {
|
||||
@@ -38,6 +40,10 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
socialButtons: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
@@ -47,6 +53,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const shareModal = ref(null)
|
||||
const { addNotification } = injectNotificationManager()
|
||||
|
||||
const qrCode = ref(null)
|
||||
const qrImage = ref(null)
|
||||
@@ -94,7 +101,21 @@ const copyImage = async () => {
|
||||
}
|
||||
|
||||
const copyText = async () => {
|
||||
await navigator.clipboard.writeText(url.value ?? content.value)
|
||||
try {
|
||||
await navigator.clipboard.writeText(url.value ?? content.value)
|
||||
addNotification({
|
||||
type: 'success',
|
||||
title: 'Link copied',
|
||||
text: 'The link has been copied to your clipboard.',
|
||||
})
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
addNotification({
|
||||
type: 'error',
|
||||
title: 'Failed to copy text',
|
||||
text: message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const sendEmail = computed(
|
||||
@@ -125,50 +146,59 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="shareModal" :header="header" :noblur="noblur" :on-hide="onHide">
|
||||
<div class="share-body">
|
||||
<div v-if="link" class="qr-wrapper">
|
||||
<NewModal ref="shareModal" :header="header" :noblur="noblur" :on-hide="onHide">
|
||||
<div class="flex flex-row flex-wrap items-center gap-2">
|
||||
<div v-if="link" class="group relative mx-auto">
|
||||
<div ref="qrCode">
|
||||
<QrcodeVue :value="url" class="qr-code" margin="3" />
|
||||
<QrcodeVue :value="url" class="!bg-white rounded-[var(--radius-md)]" margin="3" />
|
||||
</div>
|
||||
<Button
|
||||
v-tooltip="'Copy QR code'"
|
||||
icon-only
|
||||
class="copy-button"
|
||||
aria-label="Copy QR code"
|
||||
@click="copyImage"
|
||||
>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<ButtonStyled circular>
|
||||
<button
|
||||
v-tooltip="'Copy QR code'"
|
||||
class="absolute top-0 right-0 m-2 opacity-0 transition-all duration-200 ease-in-out group-hover:opacity-100 group-focus-within:opacity-100 motion-reduce:transition-none"
|
||||
aria-label="Copy QR code"
|
||||
@click="copyImage"
|
||||
>
|
||||
<ClipboardCopyIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<StyledInput v-else v-model="content" multiline resize="vertical" wrapper-class="h-full">
|
||||
<template #right>
|
||||
<Button
|
||||
<button
|
||||
v-tooltip="'Copy Text'"
|
||||
icon-only
|
||||
type="button"
|
||||
aria-label="Copy Text"
|
||||
class="copy-button transparent"
|
||||
class="absolute top-0 right-0 m-2 grid h-10 w-10 cursor-pointer place-content-center rounded-lg border-none bg-button-bg text-primary transition-all hover:bg-button-bg-hover hover:brightness-125 active:scale-95"
|
||||
@click="copyText"
|
||||
>
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<ClipboardCopyIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</button>
|
||||
</template>
|
||||
</StyledInput>
|
||||
<div class="all-buttons">
|
||||
<StyledInput v-if="link" type="text" :model-value="url" readonly wrapper-class="w-full">
|
||||
<template #right>
|
||||
<Button v-tooltip="'Copy Text'" aria-label="Copy Text" class="r-btn" @click="copyText">
|
||||
<ClipboardCopyIcon aria-hidden="true" />
|
||||
</Button>
|
||||
</template>
|
||||
</StyledInput>
|
||||
<div class="button-row">
|
||||
<div class="flex flex-grow flex-col justify-center gap-2">
|
||||
<button
|
||||
v-if="link"
|
||||
v-tooltip="'Copy Link'"
|
||||
type="button"
|
||||
aria-label="Copy Link"
|
||||
class="flex h-10 w-full cursor-pointer items-center justify-between gap-2 rounded-lg border-none bg-button-bg px-3 pr-1.5 text-primary transition-all hover:bg-button-bg-hover hover:brightness-125 active:scale-95"
|
||||
@click="copyText"
|
||||
>
|
||||
<span class="cursor-pointer truncate text-left font-semibold text-primary">
|
||||
{{ url }}
|
||||
</span>
|
||||
<div class="grid h-10 w-10 place-content-center">
|
||||
<ClipboardCopyIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="socialButtons" class="flex flex-row gap-2">
|
||||
<Button v-if="canShare" v-tooltip="'Share'" aria-label="Share" icon-only @click="share">
|
||||
<ShareIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<a
|
||||
v-tooltip="'Send as an email'"
|
||||
class="btn icon-only"
|
||||
class="btn icon-only fill-contrast text-contrast"
|
||||
:href="sendEmail"
|
||||
:target="targetParameter"
|
||||
aria-label="Send as an email"
|
||||
@@ -178,7 +208,7 @@ defineExpose({
|
||||
<a
|
||||
v-if="link"
|
||||
v-tooltip="'Open link in browser'"
|
||||
class="btn icon-only"
|
||||
class="btn icon-only fill-contrast text-contrast"
|
||||
:target="targetParameter"
|
||||
:href="url"
|
||||
aria-label="Open link in browser"
|
||||
@@ -187,7 +217,7 @@ defineExpose({
|
||||
</a>
|
||||
<a
|
||||
v-tooltip="'Toot about it'"
|
||||
class="btn mastodon icon-only"
|
||||
class="btn icon-only fill-contrast text-contrast bg-[#563acc]"
|
||||
:target="targetParameter"
|
||||
:href="sendToot"
|
||||
aria-label="Toot about it"
|
||||
@@ -196,7 +226,7 @@ defineExpose({
|
||||
</a>
|
||||
<a
|
||||
v-tooltip="'Tweet about it'"
|
||||
class="btn twitter icon-only"
|
||||
class="btn icon-only fill-contrast text-contrast bg-[#1da1f2]"
|
||||
:target="targetParameter"
|
||||
:href="sendTweet"
|
||||
aria-label="Tweet about it"
|
||||
@@ -205,7 +235,7 @@ defineExpose({
|
||||
</a>
|
||||
<a
|
||||
v-tooltip="'Share on Reddit'"
|
||||
class="btn reddit icon-only"
|
||||
class="btn icon-only fill-contrast text-contrast bg-[#ff4500]"
|
||||
:target="targetParameter"
|
||||
:href="postOnReddit"
|
||||
aria-label="Share on Reddit"
|
||||
@@ -215,76 +245,5 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.share-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--gap-sm);
|
||||
padding: var(--gap-lg);
|
||||
}
|
||||
|
||||
.all-buttons {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
flex-grow: 1;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
.btn {
|
||||
fill: var(--color-contrast);
|
||||
color: var(--color-contrast);
|
||||
|
||||
&.reddit {
|
||||
background-color: #ff4500;
|
||||
}
|
||||
|
||||
&.mastodon {
|
||||
background-color: #563acc;
|
||||
}
|
||||
|
||||
&.twitter {
|
||||
background-color: #1da1f2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr-wrapper {
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
|
||||
&:hover {
|
||||
.copy-button {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
background-color: white !important;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.copy-button {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin: var(--gap-sm);
|
||||
transition: all 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts"></script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { RightArrowIcon } from '@modrinth/assets'
|
||||
import { type Component, computed, nextTick, ref } from 'vue'
|
||||
|
||||
import { type MessageDescriptor, useVIntl } from '../../composables/i18n'
|
||||
@@ -9,7 +10,8 @@ import NewModal from './NewModal.vue'
|
||||
export interface Tab {
|
||||
name: MessageDescriptor
|
||||
icon: Component
|
||||
content: Component
|
||||
content?: Component
|
||||
href?: string
|
||||
badge?: MessageDescriptor
|
||||
shown?: boolean
|
||||
}
|
||||
@@ -75,15 +77,19 @@ defineExpose({ show, hide, selectedTab, setTab })
|
||||
<template v-if="$slots.title" #title>
|
||||
<slot name="title" />
|
||||
</template>
|
||||
<div class="grid grid-cols-[auto_1fr] p-4">
|
||||
<div class="grid grid-cols-[auto_1fr] p-6 pb-3 pr-0">
|
||||
<div
|
||||
class="flex flex-col gap-1 border-solid pr-4 border-0 border-r-[1px] border-divider min-w-[200px]"
|
||||
>
|
||||
<button
|
||||
<component
|
||||
:is="tab.href ? 'a' : 'button'"
|
||||
v-for="(tab, index) in visibleTabs"
|
||||
:key="index"
|
||||
:class="`flex gap-2 items-center text-left rounded-xl px-4 py-2 border-none text-nowrap font-semibold cursor-pointer active:scale-[0.97] transition-all ${selectedTab === index ? 'bg-button-bgSelected text-button-textSelected' : 'bg-transparent text-button-text hover:bg-button-bg hover:text-contrast'}`"
|
||||
@click="() => setTab(index)"
|
||||
:href="tab.href ?? undefined"
|
||||
:target="tab.href ? '_blank' : undefined"
|
||||
:rel="tab.href ? 'noopener noreferrer' : undefined"
|
||||
:class="`flex gap-2 items-center text-left rounded-xl px-4 py-2 border-none text-nowrap font-semibold cursor-pointer active:scale-[0.97] transition-all no-underline ${!tab.href && selectedTab === index ? 'bg-button-bgSelected text-button-textSelected' : 'bg-transparent text-button-text hover:bg-button-bg hover:text-contrast'}`"
|
||||
@click="!tab.href && setTab(index)"
|
||||
>
|
||||
<component :is="tab.icon" class="w-4 h-4 flex-shrink-0" />
|
||||
<span>{{ formatMessage(tab.name) }}</span>
|
||||
@@ -93,7 +99,8 @@ defineExpose({ show, hide, selectedTab, setTab })
|
||||
>
|
||||
{{ formatMessage(tab.badge) }}
|
||||
</span>
|
||||
</button>
|
||||
<RightArrowIcon v-if="tab.href" class="size-4 ml-auto" />
|
||||
</component>
|
||||
|
||||
<slot name="footer" />
|
||||
</div>
|
||||
@@ -101,38 +108,41 @@ defineExpose({ show, hide, selectedTab, setTab })
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-10"
|
||||
enter-to-class="opacity-100 max-h-4"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 max-h-10"
|
||||
leave-from-class="opacity-100 max-h-4"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="showTopFade"
|
||||
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-10 bg-gradient-to-b from-bg-raised to-transparent"
|
||||
class="pointer-events-none absolute left-0 right-0 top-0 z-10 h-4 bg-gradient-to-b from-bg-raised to-transparent"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<div
|
||||
ref="scrollContainer"
|
||||
class="min-w-[400px] h-[500px] overflow-y-auto px-4"
|
||||
class="overflow-y-auto px-6 pb-6 h-screen max-h-[min(65vh,600px)]"
|
||||
@scroll="checkScrollState"
|
||||
>
|
||||
<Suspense>
|
||||
<component :is="visibleTabs[selectedTab].content" />
|
||||
<component
|
||||
:is="visibleTabs[selectedTab]?.content"
|
||||
v-if="visibleTabs[selectedTab]?.content"
|
||||
/>
|
||||
</Suspense>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
enter-active-class="transition-all duration-200 ease-out"
|
||||
enter-from-class="opacity-0 max-h-0"
|
||||
enter-to-class="opacity-100 max-h-10"
|
||||
enter-to-class="opacity-100 max-h-16"
|
||||
leave-active-class="transition-all duration-200 ease-in"
|
||||
leave-from-class="opacity-100 max-h-10"
|
||||
leave-from-class="opacity-100 max-h-16"
|
||||
leave-to-class="opacity-0 max-h-0"
|
||||
>
|
||||
<div
|
||||
v-if="showBottomFade"
|
||||
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-10 bg-gradient-to-t from-bg-raised to-transparent"
|
||||
class="pointer-events-none absolute bottom-0 left-0 right-0 z-10 h-16 bg-gradient-to-t from-bg-raised to-transparent"
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :header="formatMessage(messages.header)" :closable="false">
|
||||
<div class="flex flex-col gap-4 md:w-[400px]">
|
||||
<AppearingProgressBar :max-value="totalBytes" :current-value="uploadedBytes" />
|
||||
<p class="m-0 text-sm text-secondary">{{ formatMessage(messages.warningText) }}</p>
|
||||
</div>
|
||||
</NewModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { UploadHandle } from '@modrinth/api-client'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { AppearingProgressBar } from '#ui/components/base'
|
||||
import { defineMessages, useVIntl } from '#ui/composables/i18n'
|
||||
|
||||
import NewModal from './NewModal.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
header: {
|
||||
id: 'servers.setup.uploading-modpack.header',
|
||||
defaultMessage: 'Uploading modpack',
|
||||
},
|
||||
warningText: {
|
||||
id: 'servers.setup.upload-warning',
|
||||
defaultMessage: "Please don't close this page while uploading.",
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref<InstanceType<typeof NewModal>>()
|
||||
const uploadedBytes = ref(0)
|
||||
const totalBytes = ref(0)
|
||||
|
||||
function track<T>(handle: UploadHandle<T>): Promise<T> {
|
||||
uploadedBytes.value = 0
|
||||
totalBytes.value = 0
|
||||
modal.value?.show()
|
||||
handle.onProgress(({ loaded, total }) => {
|
||||
uploadedBytes.value = loaded
|
||||
totalBytes.value = total
|
||||
})
|
||||
return handle.promise.finally(() => {
|
||||
modal.value?.hide()
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({ track })
|
||||
</script>
|
||||
@@ -8,3 +8,4 @@ export { default as OpenInAppModal } from './OpenInAppModal.vue'
|
||||
export { default as ShareModal } from './ShareModal.vue'
|
||||
export type { Tab as TabbedModalTab } from './TabbedModal.vue'
|
||||
export { default as TabbedModal } from './TabbedModal.vue'
|
||||
export { default as UploadProgressModal } from './UploadProgressModal.vue'
|
||||
|
||||
Reference in New Issue
Block a user