feat:移除了弹窗,服务器添加sls
This commit is contained in:
commit
6a295f9a7a
4082 files changed
+1322534
No files matched your search
@@ -0,0 +1,321 @@
|
||||
<template>
|
||||
<div v-if="open" class="open-in-app-modal">
|
||||
<div :class="{ shown: visible }" class="fullscreen-overlay" @click="hide" />
|
||||
<div class="modal-content" :class="{ shown: visible }">
|
||||
<div class="flex flex-col items-center gap-6">
|
||||
<div class="flex flex-col gap-6">
|
||||
<div v-if="countdown > 0" class="countdown-container">
|
||||
<svg class="countdown-svg" viewBox="0 0 100 100">
|
||||
<circle
|
||||
class="stroke-surface-4"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45"
|
||||
fill="none"
|
||||
stroke-width="6"
|
||||
/>
|
||||
<circle
|
||||
class="countdown-progress stroke-green"
|
||||
cx="50"
|
||||
cy="50"
|
||||
r="45"
|
||||
fill="none"
|
||||
stroke-width="6"
|
||||
:stroke-dasharray="circumference"
|
||||
:stroke-dashoffset="strokeDashoffset"
|
||||
stroke-linecap="round"
|
||||
/>
|
||||
</svg>
|
||||
<span class="text-5xl font-bold text-contrast z-[1]">{{ countdown }}</span>
|
||||
</div>
|
||||
|
||||
<h2 class="m-0 text-3xl font-bold text-contrast text-center">
|
||||
{{ formatMessage(messages.openingApp) }}
|
||||
</h2>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center gap-4 bg-surface-3 rounded-3xl border border-solid border-surface-5 p-6"
|
||||
>
|
||||
<div class="flex items-center gap-3 rounded-xl bg-surface-2 p-3 w-full">
|
||||
<Avatar :src="serverProject.icon" :alt="serverProject.name" size="48px" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-semibold text-contrast">{{ serverProject.name }}</span>
|
||||
<div class="flex items-center gap-2 text-secondary">
|
||||
<ServerOnlinePlayers
|
||||
:online="serverProject.numPlayers ?? 0"
|
||||
:status-online="serverProject.statusOnline"
|
||||
/>
|
||||
<ServerRegion v-if="serverProject.region" :region="serverProject.region" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col text-left gap-3">
|
||||
<span class="font-semibold text-contrast">{{
|
||||
formatMessage(messages.whyUseApp)
|
||||
}}</span>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex text-base gap-2 items-center">
|
||||
<div
|
||||
class="w-5 h-5 border border-solid rounded-full flex items-center justify-center border-brand bg-brand-highlight text-brand"
|
||||
>
|
||||
<CheckIcon />
|
||||
</div>
|
||||
<span>{{ formatMessage(messages.benefitLaunch) }}</span>
|
||||
</div>
|
||||
<div class="flex text-base gap-2 items-center">
|
||||
<div
|
||||
class="w-5 h-5 border border-solid rounded-full flex items-center justify-center border-brand bg-brand-highlight text-brand"
|
||||
>
|
||||
<CheckIcon />
|
||||
</div>
|
||||
<span>{{ formatMessage(messages.benefitInstall) }}</span>
|
||||
</div>
|
||||
<div class="flex text-base gap-2 items-center">
|
||||
<div
|
||||
class="w-5 h-5 border border-solid rounded-full flex items-center justify-center border-brand bg-brand-highlight text-brand"
|
||||
>
|
||||
<CheckIcon />
|
||||
</div>
|
||||
<span>{{ formatMessage(messages.benefitUpdate) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<span v-if="countdown > 0" class="text-secondary">{{
|
||||
formatMessage(messages.openingAutomatically)
|
||||
}}</span>
|
||||
<div v-else class="grid grid-cols-2 gap-2 w-full">
|
||||
<ButtonStyled>
|
||||
<button class="flex-1" @click="hide">
|
||||
<XIcon />
|
||||
{{ formatMessage(commonMessages.closeButton) }}
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled color="brand">
|
||||
<a
|
||||
class="flex-1"
|
||||
href="https://modrinth.com/app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<DownloadIcon />
|
||||
{{ formatMessage(messages.getApp) }}
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon, DownloadIcon, XIcon } from '@modrinth/assets'
|
||||
import { commonMessages } from '@modrinth/ui'
|
||||
import { computed, nextTick, onUnmounted, ref } from 'vue'
|
||||
|
||||
import { defineMessages, useVIntl } from '../../composables/i18n'
|
||||
import { Avatar, ButtonStyled } from '../base'
|
||||
import ServerOnlinePlayers from '../project/server/ServerOnlinePlayers.vue'
|
||||
import ServerRegion from '../project/server/ServerRegion.vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const messages = defineMessages({
|
||||
openingApp: {
|
||||
id: 'modal.open-in-app.title',
|
||||
defaultMessage: 'Opening Modrinth App',
|
||||
},
|
||||
whyUseApp: {
|
||||
id: 'modal.open-in-app.why-use',
|
||||
defaultMessage: 'Why use the Modrinth App',
|
||||
},
|
||||
benefitLaunch: {
|
||||
id: 'modal.open-in-app.benefit.launch',
|
||||
defaultMessage: 'Launch the game straight into the server',
|
||||
},
|
||||
benefitInstall: {
|
||||
id: 'modal.open-in-app.benefit.install',
|
||||
defaultMessage: 'Automatically install required content',
|
||||
},
|
||||
benefitUpdate: {
|
||||
id: 'modal.open-in-app.benefit.update',
|
||||
defaultMessage: 'Keep files updated when the server changes',
|
||||
},
|
||||
openingAutomatically: {
|
||||
id: 'modal.open-in-app.opening-automatically',
|
||||
defaultMessage: 'The Modrinth App will open automatically...',
|
||||
},
|
||||
getApp: {
|
||||
id: 'modal.open-in-app.get-app',
|
||||
defaultMessage: 'Get Modrinth App',
|
||||
},
|
||||
})
|
||||
|
||||
export interface ServerProject {
|
||||
name: string
|
||||
slug?: string
|
||||
numPlayers?: number
|
||||
icon?: string
|
||||
statusOnline?: boolean
|
||||
region?: string
|
||||
}
|
||||
|
||||
const open = ref(false)
|
||||
const visible = ref(false)
|
||||
const countdown = ref(3)
|
||||
const countdownProgress = ref(1)
|
||||
let countdownInterval: ReturnType<typeof setInterval> | null = null
|
||||
let progressInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
const circumference = 2 * Math.PI * 45
|
||||
const strokeDashoffset = computed(() => {
|
||||
return circumference * (1 - countdownProgress.value)
|
||||
})
|
||||
|
||||
const serverProject = ref<ServerProject>({
|
||||
name: '',
|
||||
slug: '',
|
||||
numPlayers: 0,
|
||||
icon: undefined,
|
||||
statusOnline: false,
|
||||
region: '',
|
||||
})
|
||||
const appLink = computed(() => {
|
||||
return `modrinth://server/${serverProject.value.slug}`
|
||||
})
|
||||
|
||||
function startCountdown() {
|
||||
countdown.value = 3
|
||||
countdownProgress.value = 1
|
||||
|
||||
const totalDuration = 3000
|
||||
const progressUpdateInterval = 16
|
||||
const progressDecrement = progressUpdateInterval / totalDuration
|
||||
|
||||
progressInterval = setInterval(() => {
|
||||
countdownProgress.value = Math.max(0, countdownProgress.value - progressDecrement)
|
||||
}, progressUpdateInterval)
|
||||
|
||||
countdownInterval = setInterval(() => {
|
||||
countdown.value--
|
||||
if (countdown.value <= 0) {
|
||||
stopCountdown()
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
function stopCountdown() {
|
||||
if (countdownInterval) {
|
||||
clearInterval(countdownInterval)
|
||||
countdownInterval = null
|
||||
}
|
||||
if (progressInterval) {
|
||||
clearInterval(progressInterval)
|
||||
progressInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
interface ShowOpenInAppOptions {
|
||||
serverProject: ServerProject
|
||||
}
|
||||
|
||||
async function show(options: ShowOpenInAppOptions) {
|
||||
serverProject.value = options.serverProject
|
||||
|
||||
await nextTick()
|
||||
|
||||
window.open(appLink.value, '_self')
|
||||
open.value = true
|
||||
document.body.style.overflow = 'hidden'
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
setTimeout(() => {
|
||||
visible.value = true
|
||||
startCountdown()
|
||||
}, 50)
|
||||
}
|
||||
|
||||
function hide() {
|
||||
visible.value = false
|
||||
document.body.style.overflow = ''
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
stopCountdown()
|
||||
setTimeout(() => {
|
||||
open.value = false
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
hide()
|
||||
}
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
stopCountdown()
|
||||
})
|
||||
|
||||
defineExpose({ show, hide, open })
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.open-in-app-modal {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.fullscreen-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: linear-gradient(to bottom, rgba(66, 131, 92, 0.23) 0%, rgba(17, 35, 43, 0.4) 97%);
|
||||
backdrop-filter: blur(12px);
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease-out;
|
||||
cursor: pointer;
|
||||
|
||||
&.shown {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
padding: 2.5rem;
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
transition: all 0.3s ease-out;
|
||||
|
||||
&.shown {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.countdown-container {
|
||||
position: relative;
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.countdown-svg {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
|
||||
.countdown-progress {
|
||||
transition: stroke-dashoffset 0.05s linear;
|
||||
}
|
||||
</style>
|
||||
Reference in new issue
Block a user