mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78d6b20c24 | ||
|
|
c0393cba2b | ||
|
|
79981818fa | ||
|
|
9c5afa50ad | ||
|
|
35950b809d | ||
|
|
1bf23dc21b | ||
|
|
7891afbd44 | ||
|
|
35d8b2d25c |
@@ -72,6 +72,7 @@ import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
|
||||
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import UpdateToast from '@/components/ui/UpdateToast.vue'
|
||||
import UpdateAvailableToast from '@/components/ui/UpdateAvailableToast.vue'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js'
|
||||
@@ -143,6 +144,7 @@ const showOnboarding = ref(false)
|
||||
const nativeDecorations = ref(false)
|
||||
|
||||
const os = ref('')
|
||||
const isDevEnvironment = ref(false)
|
||||
|
||||
const stateInitialized = ref(false)
|
||||
|
||||
@@ -247,6 +249,7 @@ async function setupApp() {
|
||||
|
||||
os.value = await getOS()
|
||||
const dev = await isDev()
|
||||
isDevEnvironment.value = dev
|
||||
const version = await getVersion()
|
||||
showOnboarding.value = !onboarded
|
||||
|
||||
@@ -513,13 +516,13 @@ async function checkUpdates() {
|
||||
|
||||
async function performCheck() {
|
||||
const update = await invoke('plugin:updater|check')
|
||||
const isExistingUpdate = update.version === availableUpdate.value?.version
|
||||
|
||||
if (!update) {
|
||||
console.log('No update available')
|
||||
return
|
||||
}
|
||||
|
||||
const isExistingUpdate = update.version === availableUpdate.value?.version
|
||||
|
||||
if (isExistingUpdate) {
|
||||
console.log('Update is already known')
|
||||
return
|
||||
@@ -769,6 +772,7 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
|
||||
@restart="installUpdate"
|
||||
@download="downloadAvailableUpdate"
|
||||
/>
|
||||
<UpdateAvailableToast v-else-if="os === 'Linux' && !isDevEnvironment" />
|
||||
</Transition>
|
||||
</Suspense>
|
||||
<Transition name="fade">
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
<script setup lang="ts">
|
||||
import { ExternalIcon, XIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled, commonMessages, defineMessages, useVIntl } from '@modrinth/ui'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const { formatMessage } = useVIntl()
|
||||
|
||||
const dismissed = ref(false)
|
||||
const availableUpdate = ref<{ version: string } | null>(null)
|
||||
|
||||
let checkInterval: ReturnType<typeof setInterval> | null = null
|
||||
|
||||
async function checkForUpdate() {
|
||||
try {
|
||||
const [response, currentVersion] = await Promise.all([
|
||||
fetch('https://launcher-files.modrinth.com/updates.json'),
|
||||
getVersion(),
|
||||
])
|
||||
const updates = await response.json()
|
||||
const latestVersion = updates?.version
|
||||
|
||||
if (latestVersion && latestVersion !== currentVersion) {
|
||||
if (latestVersion !== availableUpdate.value?.version) {
|
||||
availableUpdate.value = { version: latestVersion }
|
||||
dismissed.value = false
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to check for updates:', e)
|
||||
}
|
||||
}
|
||||
|
||||
function dismiss() {
|
||||
dismissed.value = true
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkForUpdate()
|
||||
checkInterval = setInterval(checkForUpdate, 5 * 60 * 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (checkInterval) {
|
||||
clearInterval(checkInterval)
|
||||
}
|
||||
})
|
||||
|
||||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'app.update-toast.title',
|
||||
defaultMessage: 'Update available',
|
||||
},
|
||||
body: {
|
||||
id: 'app.update-toast.body.linux',
|
||||
defaultMessage:
|
||||
'Modrinth App v{version} is now available! Update using your system package manager, or download the latest version from our website.',
|
||||
},
|
||||
download: {
|
||||
id: 'app.update-toast.download-page',
|
||||
defaultMessage: 'Download',
|
||||
},
|
||||
changelog: {
|
||||
id: 'app.update-toast.changelog',
|
||||
defaultMessage: 'Changelog',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div
|
||||
v-if="availableUpdate && !dismissed"
|
||||
class="grid grid-cols-[min-content] fixed card-shadow rounded-2xl top-[--top-bar-height] mt-6 right-6 p-4 z-10 bg-bg-raised border-divider border-solid border-[2px]"
|
||||
>
|
||||
<div class="flex min-w-[25rem] gap-4">
|
||||
<h2 class="whitespace-nowrap text-base text-contrast font-semibold m-0 grow">
|
||||
{{ formatMessage(messages.title) }}
|
||||
</h2>
|
||||
<ButtonStyled size="small" circular>
|
||||
<button v-tooltip="formatMessage(commonMessages.closeButton)" @click="dismiss">
|
||||
<XIcon />
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<p class="text-sm mt-2 mb-0">
|
||||
{{ formatMessage(messages.body, { version: availableUpdate.version }) }}
|
||||
</p>
|
||||
<div class="flex gap-2 mt-4">
|
||||
<ButtonStyled>
|
||||
<a href="https://modrinth.com/news/changelog?filter=app">
|
||||
{{ formatMessage(messages.changelog) }} <ExternalIcon />
|
||||
</a>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -63,6 +63,7 @@ async function openEditVersionModal(versionId: string, projectId: string, stageI
|
||||
existing_files: versionData.files ?? [],
|
||||
environment: versionData.environment,
|
||||
mrpack_loaders: versionData.mrpack_loaders,
|
||||
status: versionData.status,
|
||||
}
|
||||
|
||||
openCreateVersionModal(draftVersionData, stageId)
|
||||
|
||||
@@ -40,7 +40,27 @@
|
||||
/>
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version changlog </span>
|
||||
<span class="font-semibold text-contrast"> Status </span>
|
||||
<Combobox
|
||||
v-model="draftVersion.status"
|
||||
:options="[
|
||||
{
|
||||
value: 'listed',
|
||||
label: 'Listed',
|
||||
subLabel: 'Visible on project page and in discovery.',
|
||||
},
|
||||
{
|
||||
value: 'unlisted',
|
||||
label: 'Unlisted',
|
||||
subLabel: 'Hidden on project page and discovery, but accessible by URL.',
|
||||
},
|
||||
]"
|
||||
:disabled="isUploading"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-2">
|
||||
<span class="font-semibold text-contrast"> Version changelog </span>
|
||||
|
||||
<div class="w-full">
|
||||
<MarkdownEditor
|
||||
@@ -55,7 +75,7 @@
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Chips, MarkdownEditor } from '@modrinth/ui'
|
||||
import { Chips, Combobox, MarkdownEditor } from '@modrinth/ui'
|
||||
|
||||
import { useImageUpload } from '~/composables/image-upload.ts'
|
||||
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
|
||||
|
||||
@@ -195,35 +195,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Admonition v-if="!hideGalleryAdmonition && currentMember" type="info" class="mb-4">
|
||||
Creating and editing gallery images can now be done directly from the
|
||||
<NuxtLink to="settings/gallery" class="font-medium text-blue hover:underline"
|
||||
>project settings</NuxtLink
|
||||
>.
|
||||
<template #actions>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled color="blue">
|
||||
<button
|
||||
aria-label="Project Settings"
|
||||
class="!shadow-none"
|
||||
@click="() => $router.push('settings/gallery')"
|
||||
>
|
||||
<SettingsIcon />
|
||||
Edit gallery
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
aria-label="Dismiss"
|
||||
class="!shadow-none"
|
||||
@click="() => (hideGalleryAdmonition = true)"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</Admonition>
|
||||
<div v-if="currentMember && project.gallery.length" class="card header-buttons">
|
||||
<FileInput
|
||||
:max-size="5242880"
|
||||
@@ -327,7 +298,6 @@ import {
|
||||
PlusIcon,
|
||||
RightArrowIcon,
|
||||
SaveIcon,
|
||||
SettingsIcon,
|
||||
StarIcon,
|
||||
TransferIcon,
|
||||
TrashIcon,
|
||||
@@ -335,15 +305,12 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
ButtonStyled,
|
||||
ConfirmModal,
|
||||
DropArea,
|
||||
FileInput,
|
||||
injectNotificationManager,
|
||||
NewModal as Modal,
|
||||
} from '@modrinth/ui'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
|
||||
import { isPermission } from '~/utils/permissions.ts'
|
||||
|
||||
@@ -376,11 +343,6 @@ useSeoMeta({
|
||||
ogTitle: title,
|
||||
ogDescription: description,
|
||||
})
|
||||
|
||||
const hideGalleryAdmonition = useLocalStorage(
|
||||
'hideGalleryHasMovedAdmonition',
|
||||
!props.project.gallery.length,
|
||||
)
|
||||
</script>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -59,6 +59,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<Admonition
|
||||
v-if="version.withheldForReview"
|
||||
type="warning"
|
||||
class="mb-4 grid"
|
||||
header="Version held for review"
|
||||
body="This project version is currently held for human review and is not publicly listed. We'll update the moderation thread and apply the intended visibility once the review is complete."
|
||||
style="grid-area: banner"
|
||||
/>
|
||||
|
||||
<div class="version-page__title universal-card">
|
||||
<Breadcrumbs
|
||||
:current-title="version.name"
|
||||
@@ -664,6 +674,7 @@ import {
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Admonition,
|
||||
Avatar,
|
||||
Badge,
|
||||
ButtonStyled,
|
||||
@@ -714,6 +725,7 @@ export default defineNuxtComponent({
|
||||
Avatar,
|
||||
Badge,
|
||||
Breadcrumbs,
|
||||
Admonition,
|
||||
CopyCode,
|
||||
Multiselect,
|
||||
BoxIcon,
|
||||
@@ -1367,6 +1379,7 @@ export default defineNuxtComponent({
|
||||
display: grid;
|
||||
|
||||
grid-template:
|
||||
'banner' auto
|
||||
'title' auto
|
||||
'changelog' auto
|
||||
'dependencies' auto
|
||||
@@ -1376,6 +1389,7 @@ export default defineNuxtComponent({
|
||||
|
||||
@media (min-width: 1200px) {
|
||||
grid-template:
|
||||
'banner banner' auto
|
||||
'title title' auto
|
||||
'changelog metadata' auto
|
||||
'dependencies metadata' auto
|
||||
|
||||
@@ -15,35 +15,13 @@
|
||||
@proceed="deleteVersion()"
|
||||
/>
|
||||
|
||||
<Admonition v-if="!hideVersionsAdmonition && currentMember" type="info" class="mb-4">
|
||||
Creating and editing project versions can now be done directly from the
|
||||
<NuxtLink to="settings/versions" class="font-medium text-blue hover:underline"
|
||||
>project settings</NuxtLink
|
||||
>.
|
||||
<template #actions>
|
||||
<div class="flex gap-2">
|
||||
<ButtonStyled color="blue">
|
||||
<button
|
||||
aria-label="Project Settings"
|
||||
class="!shadow-none"
|
||||
@click="() => router.push('settings/versions')"
|
||||
>
|
||||
<SettingsIcon />
|
||||
Edit versions
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
<ButtonStyled type="transparent">
|
||||
<button
|
||||
aria-label="Dismiss"
|
||||
class="!shadow-none"
|
||||
@click="() => (hideVersionsAdmonition = true)"
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
</template>
|
||||
</Admonition>
|
||||
<Admonition
|
||||
v-if="heldForReviewVersionNames.length > 0"
|
||||
type="warning"
|
||||
class="mb-4"
|
||||
:header="`${heldForReviewVersionNames.join(', ')} ${heldForReviewVersionNames.length > 1 ? 'are' : 'is'} held for review`"
|
||||
body="This project version is currently held for human review and is not publicly listed. We'll update the moderation thread and apply the intended visibility once the review is complete."
|
||||
/>
|
||||
|
||||
<ProjectPageVersions
|
||||
v-if="versions.length"
|
||||
@@ -271,7 +249,6 @@ import {
|
||||
LinkIcon,
|
||||
MoreVerticalIcon,
|
||||
ReportIcon,
|
||||
SettingsIcon,
|
||||
ShareIcon,
|
||||
TrashIcon,
|
||||
} from '@modrinth/assets'
|
||||
@@ -285,7 +262,6 @@ import {
|
||||
OverflowMenu,
|
||||
ProjectPageVersions,
|
||||
} from '@modrinth/ui'
|
||||
import { useLocalStorage } from '@vueuse/core'
|
||||
import { useTemplateRef } from 'vue'
|
||||
|
||||
import CreateProjectVersionModal from '~/components/ui/create-project-version/CreateProjectVersionModal.vue'
|
||||
@@ -334,14 +310,11 @@ const handleOpenEditVersionModal = (versionId, projectId, stageId) => {
|
||||
createProjectVersionModal.value?.openEditVersionModal(versionId, projectId, stageId)
|
||||
}
|
||||
|
||||
const hideVersionsAdmonition = useLocalStorage(
|
||||
'hideVersionsHasMovedAdmonition',
|
||||
!props.versions.length,
|
||||
)
|
||||
|
||||
const emit = defineEmits(['onDownload', 'deleteVersion'])
|
||||
|
||||
const router = useNativeRouter()
|
||||
const heldForReviewVersionNames = computed(() =>
|
||||
props.versions.filter((version) => version.withheldForReview).map((version) => version.name),
|
||||
)
|
||||
|
||||
const baseDropdownId = useId()
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ const EMPTY_DRAFT_VERSION: Labrinth.Versions.v3.DraftVersion = {
|
||||
loaders: [],
|
||||
game_versions: [],
|
||||
featured: false,
|
||||
status: 'draft',
|
||||
status: 'listed',
|
||||
changelog: '',
|
||||
dependencies: [],
|
||||
}
|
||||
@@ -699,6 +699,7 @@ export function createManageVersionContext(
|
||||
hash: file.hashes.sha1,
|
||||
file_type: file.file_type ?? null,
|
||||
})),
|
||||
status: version.status,
|
||||
}
|
||||
|
||||
await labrinth.versions_v3.modifyVersion(version.version_id, data)
|
||||
|
||||
@@ -456,10 +456,10 @@ export namespace Labrinth {
|
||||
dependencies: Dependency[]
|
||||
game_versions: string[]
|
||||
loaders: string[]
|
||||
withheldForReview?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: consolidate duplicated types between v2 and v3 versions
|
||||
export namespace v3 {
|
||||
export interface Dependency {
|
||||
dependency_type: Labrinth.Versions.v2.DependencyType
|
||||
@@ -516,6 +516,7 @@ export namespace Labrinth {
|
||||
files: VersionFile[]
|
||||
environment?: Labrinth.Projects.v3.Environment
|
||||
mrpack_loaders?: string[]
|
||||
withheldForReview?: boolean
|
||||
}
|
||||
|
||||
export interface DraftVersionFile {
|
||||
|
||||
@@ -173,6 +173,7 @@ export class LabrinthVersionsV3Module extends AbstractModule {
|
||||
primary_file: fileParts[0],
|
||||
environment: draftVersion.environment,
|
||||
loaders: draftVersion.loaders,
|
||||
status: draftVersion.status,
|
||||
}
|
||||
|
||||
if (projectType === 'modpack') {
|
||||
|
||||
@@ -6,17 +6,15 @@ import { withThemeByClassName } from '@storybook/addon-themes'
|
||||
import type { Preview } from '@storybook/vue3-vite'
|
||||
import { setup } from '@storybook/vue3-vite'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import IntlMessageFormat from 'intl-messageformat'
|
||||
import { defineComponent, ref } from 'vue'
|
||||
import { createI18n } from 'vue-i18n'
|
||||
|
||||
import NotificationPanel from '../src/components/nav/NotificationPanel.vue'
|
||||
import {
|
||||
buildLocaleMessages,
|
||||
createMessageCompiler,
|
||||
type CrowdinMessages,
|
||||
} from '../src/composables/i18n'
|
||||
import { buildLocaleMessages, type CrowdinMessages } from '../src/composables/i18n'
|
||||
import {
|
||||
AbstractWebNotificationManager,
|
||||
I18N_INJECTION_KEY,
|
||||
type I18nContext,
|
||||
type NotificationPanelLocation,
|
||||
provideNotificationManager,
|
||||
type WebNotification,
|
||||
@@ -28,16 +26,33 @@ const localeModules = import.meta.glob('../src/locales/*/index.json', {
|
||||
eager: true,
|
||||
}) as Record<string, { default: CrowdinMessages }>
|
||||
|
||||
// Set up vue-i18n for Storybook - provides useVIntl() context for components
|
||||
const i18n = createI18n({
|
||||
legacy: false,
|
||||
locale: 'en-US',
|
||||
fallbackLocale: 'en-US',
|
||||
messageCompiler: createMessageCompiler(),
|
||||
missingWarn: false,
|
||||
fallbackWarn: false,
|
||||
messages: buildLocaleMessages(localeModules),
|
||||
})
|
||||
const messages = buildLocaleMessages(localeModules)
|
||||
const currentLocale = ref('en-US')
|
||||
|
||||
// Create a simple t function that uses IntlMessageFormat for ICU syntax support
|
||||
function createTranslateFunction(locale: string, msgs: Record<string, Record<string, string>>) {
|
||||
return (key: string, values?: Record<string, unknown>): string => {
|
||||
const localeMessages = msgs[locale] ?? msgs['en-US'] ?? {}
|
||||
const message = localeMessages[key]
|
||||
if (!message) return key
|
||||
|
||||
try {
|
||||
const formatter = new IntlMessageFormat(message, locale)
|
||||
return formatter.format(values ?? {}) as string
|
||||
} catch {
|
||||
return message
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set up i18n context for Storybook - provides useVIntl() context for components
|
||||
const i18nContext: I18nContext = {
|
||||
locale: currentLocale,
|
||||
t: (key, values) => createTranslateFunction(currentLocale.value, messages)(key, values),
|
||||
setLocale: (newLocale) => {
|
||||
currentLocale.value = newLocale
|
||||
},
|
||||
}
|
||||
|
||||
class StorybookNotificationManager extends AbstractWebNotificationManager {
|
||||
private readonly state = ref<WebNotification[]>([])
|
||||
@@ -76,7 +91,7 @@ class StorybookNotificationManager extends AbstractWebNotificationManager {
|
||||
}
|
||||
|
||||
setup((app) => {
|
||||
app.use(i18n)
|
||||
app.provide(I18N_INJECTION_KEY, i18nContext)
|
||||
app.use(FloatingVue, {
|
||||
themes: {
|
||||
'ribbit-popout': {
|
||||
|
||||
@@ -95,12 +95,21 @@
|
||||
<slot :name="`option-${item.value}`" :item="item">
|
||||
<div class="flex items-center gap-2">
|
||||
<component :is="item.icon" v-if="item.icon" class="h-5 w-5" />
|
||||
<span
|
||||
class="font-semibold leading-tight"
|
||||
:class="item.value === modelValue ? 'text-contrast' : 'text-primary'"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span
|
||||
class="font-semibold leading-tight"
|
||||
:class="item.value === modelValue ? 'text-contrast' : 'text-primary'"
|
||||
>
|
||||
{{ item.label }}
|
||||
</span>
|
||||
<span
|
||||
v-if="item.subLabel"
|
||||
class="text-sm"
|
||||
:class="item.value === modelValue ? 'text-contrast' : 'text-secondary'"
|
||||
>
|
||||
{{ item.subLabel }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</component>
|
||||
@@ -132,6 +141,7 @@ import {
|
||||
export interface ComboboxOption<T> {
|
||||
value: T
|
||||
label: string
|
||||
subLabel?: string
|
||||
icon?: Component
|
||||
disabled?: boolean
|
||||
class?: string
|
||||
|
||||
@@ -109,14 +109,31 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="pointer-events-none relative z-[1] flex flex-col justify-center"
|
||||
:class="{
|
||||
'group-hover:underline': !!versionLink,
|
||||
}"
|
||||
>
|
||||
<div class="font-bold text-contrast">{{ version.version_number }}</div>
|
||||
<div class="text-xs font-medium">{{ version.name }}</div>
|
||||
<div class="flex gap-2 items-center flex-wrap content-center">
|
||||
<div
|
||||
class="pointer-events-none relative z-[1] flex flex-col justify-center"
|
||||
:class="{
|
||||
'group-hover:underline': !!versionLink,
|
||||
}"
|
||||
>
|
||||
<div class="font-bold text-contrast">{{ version.version_number }}</div>
|
||||
<div class="text-xs font-medium">{{ version.name }}</div>
|
||||
</div>
|
||||
<TagItem
|
||||
v-if="version.withheldForReview"
|
||||
class="border !border-solid border-orange !font-medium h-max"
|
||||
style="--_bg-color: var(--color-orange-highlight); --_color: var(--color-orange)"
|
||||
>
|
||||
<InfoIcon />
|
||||
Withheld
|
||||
</TagItem>
|
||||
<TagItem
|
||||
v-if="version.status === 'unlisted' && currentMember"
|
||||
class="border !border-solid border-surface-5 !font-medium"
|
||||
style="--_bg-color: var(--surface-4); --_color: var(--color-text-default)"
|
||||
>
|
||||
Unlisted
|
||||
</TagItem>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center gap-2 sm:contents">
|
||||
@@ -181,7 +198,7 @@
|
||||
class="z-[1] flex cursor-help items-center gap-1 text-nowrap font-medium xl:self-center"
|
||||
>
|
||||
<CalendarIcon class="xl:hidden" />
|
||||
{{ formatRelativeTime(version.date_published) }}
|
||||
{{ formatRelativeTime(new Date(version.date_published)) }}
|
||||
</div>
|
||||
<div
|
||||
class="pointer-events-none z-[1] flex items-center gap-1 font-medium xl:self-center"
|
||||
@@ -222,7 +239,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { Labrinth } from '@modrinth/api-client'
|
||||
import { CalendarIcon, DownloadIcon, PlusIcon, StarIcon } from '@modrinth/assets'
|
||||
import { ButtonStyled } from '@modrinth/ui'
|
||||
import { ButtonStyled, TagItem } from '@modrinth/ui'
|
||||
import {
|
||||
formatBytes,
|
||||
formatCategory,
|
||||
@@ -234,11 +251,11 @@ import {
|
||||
import { computed, type Ref, ref } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
|
||||
import { InfoIcon } from '@modrinth/assets'
|
||||
import { useRelativeTime } from '../../composables'
|
||||
import { useVIntl } from '../../composables/i18n'
|
||||
import { commonMessages } from '../../utils/common-messages'
|
||||
import AutoLink from '../base/AutoLink.vue'
|
||||
import TagItem from '../base/TagItem.vue'
|
||||
import { Pagination, VersionChannelIndicator, VersionFilterControl } from '../index'
|
||||
import { getEnvironmentTags } from './settings/environment/environments'
|
||||
|
||||
|
||||
@@ -44,3 +44,27 @@ export const Disabled: Story = {
|
||||
disabled: true,
|
||||
},
|
||||
}
|
||||
|
||||
export const WithSubLabels: Story = {
|
||||
args: {
|
||||
modelValue: '2',
|
||||
options: [
|
||||
{ value: '1', label: 'Fabric', subLabel: 'Lightweight modding toolchain' },
|
||||
{ value: '2', label: 'Forge', subLabel: 'The original Minecraft modding API' },
|
||||
{ value: '3', label: 'NeoForge', subLabel: 'Community-driven Forge fork' },
|
||||
{ value: '4', label: 'Quilt', subLabel: 'The mod-loader that cares' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
export const MixedSubLabels: Story = {
|
||||
args: {
|
||||
options: [
|
||||
{ value: '1', label: 'Minecraft', subLabel: 'The base game' },
|
||||
{ value: '2', label: 'Fabric' },
|
||||
{ value: '3', label: 'Forge', subLabel: 'Supports most mods' },
|
||||
{ value: '4', label: 'NeoForge' },
|
||||
{ value: '5', label: 'Quilt', subLabel: 'Fabric-compatible' },
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
@@ -40,3 +40,19 @@ export const MultipleTags: Story = {
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
export const ColorVariants: Story = {
|
||||
render: () => ({
|
||||
components: { TagItem },
|
||||
template: /*html*/ `
|
||||
<div style="display: flex; flex-wrap: wrap; gap: 0.5rem;">
|
||||
<TagItem class="border !border-solid border-brand !font-medium" style="--_bg-color: var(--color-brand-highlight); --_color: var(--color-text-primary);">Brand</TagItem>
|
||||
<TagItem class="border !border-solid border-red !font-medium" style="--_bg-color: var(--color-red-highlight); --_color: var(--color-text-primary);">Red</TagItem>
|
||||
<TagItem class="border !border-solid border-orange !font-medium" style="--_bg-color: var(--color-orange-highlight); --_color: var(--color-text-primary);">Orange</TagItem>
|
||||
<TagItem class="border !border-solid border-blue !font-medium" style="--_bg-color: var(--color-blue-highlight); --_color: var(--color-text-primary);">Blue</TagItem>
|
||||
<TagItem class="border !border-solid border-purple !font-medium" style="--_bg-color: var(--color-purple-highlight); --_color: var(--color-text-primary);">Purple</TagItem>
|
||||
<TagItem class="border !border-solid border-gray !font-medium" style="--_bg-color: var(--color-gray-highlight); --_color: var(--color-text-primary);">Gray</TagItem>
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user