mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 21:26:40 +00:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f76378d1c2 |
@@ -0,0 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { ProjectPage } from '@modrinth/ui'
|
||||
</script>
|
||||
<template>
|
||||
<ProjectPage />
|
||||
</template>
|
||||
@@ -6,35 +6,35 @@ import _AlignLeftIcon from './icons/align-left.svg?component'
|
||||
import _ArchiveIcon from './icons/archive.svg?component'
|
||||
import _ArrowBigRightDashIcon from './icons/arrow-big-right-dash.svg?component'
|
||||
import _ArrowBigUpDashIcon from './icons/arrow-big-up-dash.svg?component'
|
||||
import _ArrowDownIcon from './icons/arrow-down.svg?component'
|
||||
import _ArrowDownLeftIcon from './icons/arrow-down-left.svg?component'
|
||||
import _ArrowDownIcon from './icons/arrow-down.svg?component'
|
||||
import _ArrowLeftRightIcon from './icons/arrow-left-right.svg?component'
|
||||
import _ArrowUpIcon from './icons/arrow-up.svg?component'
|
||||
import _ArrowUpRightIcon from './icons/arrow-up-right.svg?component'
|
||||
import _ArrowUpIcon from './icons/arrow-up.svg?component'
|
||||
import _AsteriskIcon from './icons/asterisk.svg?component'
|
||||
import _BadgeCheckIcon from './icons/badge-check.svg?component'
|
||||
import _BadgeDollarSignIcon from './icons/badge-dollar-sign.svg?component'
|
||||
import _BanIcon from './icons/ban.svg?component'
|
||||
import _BellIcon from './icons/bell.svg?component'
|
||||
import _BellRingIcon from './icons/bell-ring.svg?component'
|
||||
import _BellIcon from './icons/bell.svg?component'
|
||||
import _BlocksIcon from './icons/blocks.svg?component'
|
||||
import _BoldIcon from './icons/bold.svg?component'
|
||||
import _BookIcon from './icons/book.svg?component'
|
||||
import _BookOpenIcon from './icons/book-open.svg?component'
|
||||
import _BookTextIcon from './icons/book-text.svg?component'
|
||||
import _BookIcon from './icons/book.svg?component'
|
||||
import _BookmarkIcon from './icons/bookmark.svg?component'
|
||||
import _BotIcon from './icons/bot.svg?component'
|
||||
import _BoxIcon from './icons/box.svg?component'
|
||||
import _BoxImportIcon from './icons/box-import.svg?component'
|
||||
import _BoxIcon from './icons/box.svg?component'
|
||||
import _BracesIcon from './icons/braces.svg?component'
|
||||
import _BrushCleaningIcon from './icons/brush-cleaning.svg?component'
|
||||
import _CalendarIcon from './icons/calendar.svg?component'
|
||||
import _CardIcon from './icons/card.svg?component'
|
||||
import _ChangeSkinIcon from './icons/change-skin.svg?component'
|
||||
import _ChartIcon from './icons/chart.svg?component'
|
||||
import _CheckIcon from './icons/check.svg?component'
|
||||
import _CheckCheckIcon from './icons/check-check.svg?component'
|
||||
import _CheckCircleIcon from './icons/check-circle.svg?component'
|
||||
import _CheckIcon from './icons/check.svg?component'
|
||||
import _ChevronLeftIcon from './icons/chevron-left.svg?component'
|
||||
import _ChevronRightIcon from './icons/chevron-right.svg?component'
|
||||
import _CircleUserIcon from './icons/circle-user.svg?component'
|
||||
@@ -65,13 +65,13 @@ import _EditIcon from './icons/edit.svg?component'
|
||||
import _EllipsisVerticalIcon from './icons/ellipsis-vertical.svg?component'
|
||||
import _ExpandIcon from './icons/expand.svg?component'
|
||||
import _ExternalIcon from './icons/external.svg?component'
|
||||
import _EyeIcon from './icons/eye.svg?component'
|
||||
import _EyeOffIcon from './icons/eye-off.svg?component'
|
||||
import _FileIcon from './icons/file.svg?component'
|
||||
import _EyeIcon from './icons/eye.svg?component'
|
||||
import _FileArchiveIcon from './icons/file-archive.svg?component'
|
||||
import _FileTextIcon from './icons/file-text.svg?component'
|
||||
import _FilterIcon from './icons/filter.svg?component'
|
||||
import _FileIcon from './icons/file.svg?component'
|
||||
import _FilterXIcon from './icons/filter-x.svg?component'
|
||||
import _FilterIcon from './icons/filter.svg?component'
|
||||
import _FolderArchiveIcon from './icons/folder-archive.svg?component'
|
||||
import _FolderOpenIcon from './icons/folder-open.svg?component'
|
||||
import _FolderSearchIcon from './icons/folder-search.svg?component'
|
||||
@@ -91,12 +91,13 @@ import _HashIcon from './icons/hash.svg?component'
|
||||
import _Heading1Icon from './icons/heading-1.svg?component'
|
||||
import _Heading2Icon from './icons/heading-2.svg?component'
|
||||
import _Heading3Icon from './icons/heading-3.svg?component'
|
||||
import _HeartIcon from './icons/heart.svg?component'
|
||||
import _HeartHandshakeIcon from './icons/heart-handshake.svg?component'
|
||||
import _HeartMinusIcon from './icons/heart-minus.svg?component'
|
||||
import _HeartIcon from './icons/heart.svg?component'
|
||||
import _HistoryIcon from './icons/history.svg?component'
|
||||
import _HomeIcon from './icons/home.svg?component'
|
||||
import _ImageIcon from './icons/image.svg?component'
|
||||
import _ImagesIcon from './icons/images.svg?component'
|
||||
import _ImportIcon from './icons/import.svg?component'
|
||||
import _InProgressIcon from './icons/in-progress.svg?component'
|
||||
import _InfoIcon from './icons/info.svg?component'
|
||||
@@ -110,14 +111,14 @@ import _LeftArrowIcon from './icons/left-arrow.svg?component'
|
||||
import _LibraryIcon from './icons/library.svg?component'
|
||||
import _LightBulbIcon from './icons/light-bulb.svg?component'
|
||||
import _LinkIcon from './icons/link.svg?component'
|
||||
import _ListIcon from './icons/list.svg?component'
|
||||
import _ListBulletedIcon from './icons/list-bulleted.svg?component'
|
||||
import _ListEndIcon from './icons/list-end.svg?component'
|
||||
import _ListOrderedIcon from './icons/list-ordered.svg?component'
|
||||
import _LoaderIcon from './icons/loader.svg?component'
|
||||
import _ListIcon from './icons/list.svg?component'
|
||||
import _LoaderCircleIcon from './icons/loader-circle.svg?component'
|
||||
import _LockIcon from './icons/lock.svg?component'
|
||||
import _LoaderIcon from './icons/loader.svg?component'
|
||||
import _LockOpenIcon from './icons/lock-open.svg?component'
|
||||
import _LockIcon from './icons/lock.svg?component'
|
||||
import _LogInIcon from './icons/log-in.svg?component'
|
||||
import _LogOutIcon from './icons/log-out.svg?component'
|
||||
import _MailIcon from './icons/mail.svg?component'
|
||||
@@ -128,8 +129,8 @@ import _MessageIcon from './icons/message.svg?component'
|
||||
import _MicrophoneIcon from './icons/microphone.svg?component'
|
||||
import _MinimizeIcon from './icons/minimize.svg?component'
|
||||
import _MinusIcon from './icons/minus.svg?component'
|
||||
import _MonitorIcon from './icons/monitor.svg?component'
|
||||
import _MonitorSmartphoneIcon from './icons/monitor-smartphone.svg?component'
|
||||
import _MonitorIcon from './icons/monitor.svg?component'
|
||||
import _MoonIcon from './icons/moon.svg?component'
|
||||
import _MoreHorizontalIcon from './icons/more-horizontal.svg?component'
|
||||
import _MoreVerticalIcon from './icons/more-vertical.svg?component'
|
||||
@@ -138,16 +139,16 @@ import _NoSignalIcon from './icons/no-signal.svg?component'
|
||||
import _NotepadTextIcon from './icons/notepad-text.svg?component'
|
||||
import _OmorphiaIcon from './icons/omorphia.svg?component'
|
||||
import _OrganizationIcon from './icons/organization.svg?component'
|
||||
import _PackageIcon from './icons/package.svg?component'
|
||||
import _PackageClosedIcon from './icons/package-closed.svg?component'
|
||||
import _PackageOpenIcon from './icons/package-open.svg?component'
|
||||
import _PackageIcon from './icons/package.svg?component'
|
||||
import _PaintbrushIcon from './icons/paintbrush.svg?component'
|
||||
import _PickaxeIcon from './icons/pickaxe.svg?component'
|
||||
import _PlayIcon from './icons/play.svg?component'
|
||||
import _PlugIcon from './icons/plug.svg?component'
|
||||
import _PlusIcon from './icons/plus.svg?component'
|
||||
import _RadioButtonIcon from './icons/radio-button.svg?component'
|
||||
import _RadioButtonCheckedIcon from './icons/radio-button-checked.svg?component'
|
||||
import _RadioButtonIcon from './icons/radio-button.svg?component'
|
||||
import _ReceiptTextIcon from './icons/receipt-text.svg?component'
|
||||
import _RedoIcon from './icons/redo.svg?component'
|
||||
import _RefreshCwIcon from './icons/refresh-cw.svg?component'
|
||||
@@ -164,8 +165,8 @@ import _ScaleIcon from './icons/scale.svg?component'
|
||||
import _ScanEyeIcon from './icons/scan-eye.svg?component'
|
||||
import _SearchIcon from './icons/search.svg?component'
|
||||
import _SendIcon from './icons/send.svg?component'
|
||||
import _ServerIcon from './icons/server.svg?component'
|
||||
import _ServerPlusIcon from './icons/server-plus.svg?component'
|
||||
import _ServerIcon from './icons/server.svg?component'
|
||||
import _SettingsIcon from './icons/settings.svg?component'
|
||||
import _ShareIcon from './icons/share.svg?component'
|
||||
import _ShieldIcon from './icons/shield.svg?component'
|
||||
@@ -195,26 +196,26 @@ import _TrashIcon from './icons/trash.svg?component'
|
||||
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
|
||||
import _UnderlineIcon from './icons/underline.svg?component'
|
||||
import _UndoIcon from './icons/undo.svg?component'
|
||||
import _UnknownIcon from './icons/unknown.svg?component'
|
||||
import _UnknownDonationIcon from './icons/unknown-donation.svg?component'
|
||||
import _UnknownIcon from './icons/unknown.svg?component'
|
||||
import _UnlinkIcon from './icons/unlink.svg?component'
|
||||
import _UnplugIcon from './icons/unplug.svg?component'
|
||||
import _UpdatedIcon from './icons/updated.svg?component'
|
||||
import _UploadIcon from './icons/upload.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UserCogIcon from './icons/user-cog.svg?component'
|
||||
import _UserPlusIcon from './icons/user-plus.svg?component'
|
||||
import _UserRoundIcon from './icons/user-round.svg?component'
|
||||
import _UserSearchIcon from './icons/user-search.svg?component'
|
||||
import _UserXIcon from './icons/user-x.svg?component'
|
||||
import _UserIcon from './icons/user.svg?component'
|
||||
import _UsersIcon from './icons/users.svg?component'
|
||||
import _VersionIcon from './icons/version.svg?component'
|
||||
import _WikiIcon from './icons/wiki.svg?component'
|
||||
import _WindowIcon from './icons/window.svg?component'
|
||||
import _WorldIcon from './icons/world.svg?component'
|
||||
import _WrenchIcon from './icons/wrench.svg?component'
|
||||
import _XIcon from './icons/x.svg?component'
|
||||
import _XCircleIcon from './icons/x-circle.svg?component'
|
||||
import _XIcon from './icons/x.svg?component'
|
||||
import _ZoomInIcon from './icons/zoom-in.svg?component'
|
||||
import _ZoomOutIcon from './icons/zoom-out.svg?component'
|
||||
|
||||
@@ -314,6 +315,7 @@ export const HeartIcon = _HeartIcon
|
||||
export const HistoryIcon = _HistoryIcon
|
||||
export const HomeIcon = _HomeIcon
|
||||
export const ImageIcon = _ImageIcon
|
||||
export const ImagesIcon = _ImagesIcon
|
||||
export const ImportIcon = _ImportIcon
|
||||
export const InProgressIcon = _InProgressIcon
|
||||
export const InfoIcon = _InfoIcon
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-images-icon lucide-images"><path d="m22 11-1.296-1.296a2.4 2.4 0 0 0-3.408 0L11 16"/><path d="M4 8a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2"/><circle cx="13" cy="7" r="1" fill="currentColor"/><rect x="8" y="2" width="14" height="14" rx="2"/></svg>
|
||||
|
After Width: | Height: | Size: 456 B |
@@ -1,37 +1,37 @@
|
||||
// AUTO-GENERATED FILE - DO NOT EDIT
|
||||
import { article as a_new_chapter_for_modrinth_servers } from "./a_new_chapter_for_modrinth_servers";
|
||||
import { article as accelerating_development } from "./accelerating_development";
|
||||
import { article as becoming_sustainable } from "./becoming_sustainable";
|
||||
import { article as capital_return } from "./capital_return";
|
||||
import { article as carbon_ads } from "./carbon_ads";
|
||||
import { article as creator_monetization } from "./creator_monetization";
|
||||
import { article as creator_update } from "./creator_update";
|
||||
import { article as creator_updates_july_2025 } from "./creator_updates_july_2025";
|
||||
import { article as creator_withdrawals_overhaul } from "./creator_withdrawals_overhaul";
|
||||
import { article as design_refresh } from "./design_refresh";
|
||||
import { article as download_adjustment } from "./download_adjustment";
|
||||
import { article as free_server_medal } from "./free_server_medal";
|
||||
import { article as knossos_v2_1_0 } from "./knossos_v2_1_0";
|
||||
import { article as licensing_guide } from "./licensing_guide";
|
||||
import { article as modpack_changes } from "./modpack_changes";
|
||||
import { article as modpacks_alpha } from "./modpacks_alpha";
|
||||
import { article as modrinth_app_beta } from "./modrinth_app_beta";
|
||||
import { article as modrinth_beta } from "./modrinth_beta";
|
||||
import { article as modrinth_servers_asia } from "./modrinth_servers_asia";
|
||||
import { article as modrinth_servers_beta } from "./modrinth_servers_beta";
|
||||
import { article as new_environments } from "./new_environments";
|
||||
import { article as new_site_beta } from "./new_site_beta";
|
||||
import { article as plugins_resource_packs } from "./plugins_resource_packs";
|
||||
import { article as pride_campaign_2025 } from "./pride_campaign_2025";
|
||||
import { article as redesign } from "./redesign";
|
||||
import { article as russian_censorship } from "./russian_censorship";
|
||||
import { article as skins_now_in_modrinth_app } from "./skins_now_in_modrinth_app";
|
||||
import { article as standing_by_our_values } from "./standing_by_our_values";
|
||||
import { article as standing_by_our_values_russian } from "./standing_by_our_values_russian";
|
||||
import { article as windows_borderless_malware_disclosure } from "./windows_borderless_malware_disclosure";
|
||||
import { article as whats_modrinth } from "./whats_modrinth";
|
||||
import { article as two_years_of_modrinth } from "./two_years_of_modrinth";
|
||||
import { article as two_years_of_modrinth_history } from "./two_years_of_modrinth_history";
|
||||
import { article as whats_modrinth } from "./whats_modrinth";
|
||||
import { article as windows_borderless_malware_disclosure } from "./windows_borderless_malware_disclosure";
|
||||
import { article as standing_by_our_values } from "./standing_by_our_values";
|
||||
import { article as standing_by_our_values_russian } from "./standing_by_our_values_russian";
|
||||
import { article as skins_now_in_modrinth_app } from "./skins_now_in_modrinth_app";
|
||||
import { article as russian_censorship } from "./russian_censorship";
|
||||
import { article as redesign } from "./redesign";
|
||||
import { article as pride_campaign_2025 } from "./pride_campaign_2025";
|
||||
import { article as plugins_resource_packs } from "./plugins_resource_packs";
|
||||
import { article as new_site_beta } from "./new_site_beta";
|
||||
import { article as new_environments } from "./new_environments";
|
||||
import { article as modrinth_servers_beta } from "./modrinth_servers_beta";
|
||||
import { article as modrinth_servers_asia } from "./modrinth_servers_asia";
|
||||
import { article as modrinth_beta } from "./modrinth_beta";
|
||||
import { article as modrinth_app_beta } from "./modrinth_app_beta";
|
||||
import { article as modpacks_alpha } from "./modpacks_alpha";
|
||||
import { article as modpack_changes } from "./modpack_changes";
|
||||
import { article as licensing_guide } from "./licensing_guide";
|
||||
import { article as knossos_v2_1_0 } from "./knossos_v2_1_0";
|
||||
import { article as free_server_medal } from "./free_server_medal";
|
||||
import { article as download_adjustment } from "./download_adjustment";
|
||||
import { article as design_refresh } from "./design_refresh";
|
||||
import { article as creator_withdrawals_overhaul } from "./creator_withdrawals_overhaul";
|
||||
import { article as creator_updates_july_2025 } from "./creator_updates_july_2025";
|
||||
import { article as creator_update } from "./creator_update";
|
||||
import { article as creator_monetization } from "./creator_monetization";
|
||||
import { article as carbon_ads } from "./carbon_ads";
|
||||
import { article as capital_return } from "./capital_return";
|
||||
import { article as becoming_sustainable } from "./becoming_sustainable";
|
||||
import { article as accelerating_development } from "./accelerating_development";
|
||||
import { article as a_new_chapter_for_modrinth_servers } from "./a_new_chapter_for_modrinth_servers";
|
||||
|
||||
export const articles = [
|
||||
windows_borderless_malware_disclosure,
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<slot name="icon" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||
<h1 class="m-0 text-2xl font-semibold leading-none text-contrast">
|
||||
<slot name="title" />
|
||||
</h1>
|
||||
<slot name="title-suffix" />
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup lang="ts">
|
||||
import { CheckIcon, CopyIcon } from '@modrinth/assets';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
text: string
|
||||
}>()
|
||||
|
||||
const copying = ref(false)
|
||||
|
||||
function copyText() {
|
||||
if (!copying.value) {
|
||||
copying.value = true
|
||||
setTimeout(() => (copying.value = false), 2000)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<button
|
||||
class="transition-all appearance-none border-0 rounded-xl text-start p-3 grid grid-cols-[1fr_auto] items-center gap-2"
|
||||
:class="{
|
||||
'bg-surface-4 hover:bg-surface-5 active:scale-[0.98]': !copying,
|
||||
'bg-brand-highlight': copying,
|
||||
}"
|
||||
:disabled="copying"
|
||||
@click="copyText"
|
||||
>
|
||||
<span class="text-contrast font-semibold">{{ text }}</span>
|
||||
<span class="text-primary icon-swapper">
|
||||
<Transition name="icon-swap-up">
|
||||
<CheckIcon v-if="copying" />
|
||||
</Transition>
|
||||
<Transition name="icon-swap-down">
|
||||
<CopyIcon v-if="!copying" />
|
||||
</Transition>
|
||||
</span>
|
||||
</button>
|
||||
</template>
|
||||
<style scoped>
|
||||
.icon-swapper {
|
||||
display: grid;
|
||||
place-content: center;
|
||||
|
||||
> * {
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-swap-up-enter-active, .icon-swap-up-leave-active, .icon-swap-down-enter-active, .icon-swap-down-leave-active {
|
||||
transition: opacity 0.2s ease-in-out, translate 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.icon-swap-up-enter-from {
|
||||
opacity: 0;
|
||||
translate: 0 -100%;
|
||||
}
|
||||
|
||||
.icon-swap-up-leave-to {
|
||||
opacity: 0;
|
||||
translate: 0 -100%;
|
||||
}
|
||||
|
||||
.icon-swap-down-enter-from {
|
||||
opacity: 0;
|
||||
translate: 0 100%;
|
||||
}
|
||||
|
||||
.icon-swap-down-leave-to {
|
||||
opacity: 0;
|
||||
translate: 0 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -29,6 +29,7 @@ export { default as HorizontalRule } from './HorizontalRule.vue'
|
||||
export { default as IconSelect } from './IconSelect.vue'
|
||||
export type { JoinedButtonAction } from './JoinedButtons.vue'
|
||||
export { default as JoinedButtons } from './JoinedButtons.vue'
|
||||
export { default as LargeCopyField } from './LargeCopyField.vue'
|
||||
export { default as LoadingIndicator } from './LoadingIndicator.vue'
|
||||
export { default as ManySelect } from './ManySelect.vue'
|
||||
export { default as MarkdownEditor } from './MarkdownEditor.vue'
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<nav
|
||||
ref="scrollContainer"
|
||||
class="card-shadow experimental-styles-within relative flex w-fit overflow-x-auto rounded-full bg-bg-raised p-1 text-sm font-bold"
|
||||
>
|
||||
<router-link
|
||||
v-for="(link, index) in filteredLinks"
|
||||
v-show="link.shown === undefined ? true : link.shown"
|
||||
:key="index"
|
||||
ref="tabLinkElements"
|
||||
:to="query ? (link.href ? `?${query}=${link.href}` : '?') : link.href"
|
||||
class="button-animation z-[1] flex flex-row items-center gap-2 px-4 py-2 focus:rounded-full"
|
||||
:class="{
|
||||
'text-button-textSelected': activeIndex === index && !subpageSelected,
|
||||
'text-contrast': activeIndex === index && subpageSelected,
|
||||
}"
|
||||
>
|
||||
<component :is="link.icon" v-if="link.icon" class="size-5" />
|
||||
<span class="text-nowrap">{{ link.label }}</span>
|
||||
</router-link>
|
||||
<div
|
||||
:class="`navtabs-transition pointer-events-none absolute h-[calc(100%-0.5rem)] overflow-hidden rounded-full p-1 ${
|
||||
subpageSelected ? 'bg-button-bg' : 'bg-button-bgSelected'
|
||||
}`"
|
||||
:style="{
|
||||
left: sliderLeftPx,
|
||||
top: sliderTopPx,
|
||||
right: sliderRightPx,
|
||||
bottom: sliderBottomPx,
|
||||
opacity: sliderLeft === 4 && sliderLeft === sliderRight ? 0 : activeIndex === -1 ? 0 : 1,
|
||||
}"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Component} from 'vue';
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
interface Tab {
|
||||
label: string
|
||||
href: string
|
||||
shown?: boolean
|
||||
icon?: string | Component
|
||||
subpages?: string[]
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
links: Tab[]
|
||||
query?: string
|
||||
}>()
|
||||
|
||||
const scrollContainer = ref<HTMLElement | null>(null)
|
||||
|
||||
const sliderLeft = ref(4)
|
||||
const sliderTop = ref(4)
|
||||
const sliderRight = ref(4)
|
||||
const sliderBottom = ref(4)
|
||||
const activeIndex = ref(-1)
|
||||
const subpageSelected = ref(false)
|
||||
|
||||
const filteredLinks = computed(() =>
|
||||
props.links.filter((x) => (x.shown === undefined ? true : x.shown)),
|
||||
)
|
||||
const sliderLeftPx = computed(() => `${sliderLeft.value}px`)
|
||||
const sliderTopPx = computed(() => `${sliderTop.value}px`)
|
||||
const sliderRightPx = computed(() => `${sliderRight.value}px`)
|
||||
const sliderBottomPx = computed(() => `${sliderBottom.value}px`)
|
||||
|
||||
const tabLinkElements = ref()
|
||||
|
||||
function pickLink() {
|
||||
let index = -1
|
||||
subpageSelected.value = false
|
||||
for (let i = filteredLinks.value.length - 1; i >= 0; i--) {
|
||||
const link = filteredLinks.value[i]
|
||||
if (props.query) {
|
||||
if (route.query[props.query] === link.href || (!route.query[props.query] && !link.href)) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
} else if (decodeURIComponent(route.path) === link.href) {
|
||||
index = i
|
||||
break
|
||||
} else if (
|
||||
decodeURIComponent(route.path).includes(link.href) ||
|
||||
(link.subpages &&
|
||||
link.subpages.some((subpage) => decodeURIComponent(route.path).includes(subpage)))
|
||||
) {
|
||||
index = i
|
||||
subpageSelected.value = true
|
||||
break
|
||||
}
|
||||
}
|
||||
activeIndex.value = index
|
||||
|
||||
if (activeIndex.value !== -1) {
|
||||
startAnimation()
|
||||
} else {
|
||||
sliderLeft.value = 0
|
||||
sliderRight.value = 0
|
||||
}
|
||||
}
|
||||
|
||||
function startAnimation() {
|
||||
const el = tabLinkElements.value[activeIndex.value]?.$el
|
||||
|
||||
if (!el || !el.offsetParent) return
|
||||
|
||||
const newValues = {
|
||||
left: el.offsetLeft,
|
||||
top: el.offsetTop,
|
||||
right: el.offsetParent.offsetWidth - el.offsetLeft - el.offsetWidth,
|
||||
bottom: el.offsetParent.offsetHeight - el.offsetTop - el.offsetHeight,
|
||||
}
|
||||
|
||||
if (sliderLeft.value === 4 && sliderRight.value === 4) {
|
||||
sliderLeft.value = newValues.left
|
||||
sliderRight.value = newValues.right
|
||||
sliderTop.value = newValues.top
|
||||
sliderBottom.value = newValues.bottom
|
||||
} else {
|
||||
const delay = 200
|
||||
|
||||
if (newValues.left < sliderLeft.value) {
|
||||
sliderLeft.value = newValues.left
|
||||
setTimeout(() => {
|
||||
sliderRight.value = newValues.right
|
||||
}, delay)
|
||||
} else {
|
||||
sliderRight.value = newValues.right
|
||||
setTimeout(() => {
|
||||
sliderLeft.value = newValues.left
|
||||
}, delay)
|
||||
}
|
||||
|
||||
if (newValues.top < sliderTop.value) {
|
||||
sliderTop.value = newValues.top
|
||||
setTimeout(() => {
|
||||
sliderBottom.value = newValues.bottom
|
||||
}, delay)
|
||||
} else {
|
||||
sliderBottom.value = newValues.bottom
|
||||
setTimeout(() => {
|
||||
sliderTop.value = newValues.top
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
pickLink()
|
||||
})
|
||||
|
||||
watch(
|
||||
() => [route.path, route.query],
|
||||
() => pickLink(),
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.navtabs-transition {
|
||||
transition:
|
||||
all 150ms cubic-bezier(0.4, 0, 0.2, 1),
|
||||
opacity 250ms cubic-bezier(0.5, 0, 0.2, 1) 50ms;
|
||||
}
|
||||
|
||||
.card-shadow {
|
||||
box-shadow: var(--shadow-card);
|
||||
}
|
||||
</style>
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as Breadcrumbs } from './Breadcrumbs.vue'
|
||||
export { default as NavTabs } from './NavTabs.vue'
|
||||
export { default as NotificationPanel } from './NotificationPanel.vue'
|
||||
export { default as PagewideBanner } from './PagewideBanner.vue'
|
||||
|
||||
@@ -4,7 +4,7 @@ import { injectPageContext } from '../../providers'
|
||||
const { hierarchicalSidebarAvailable } = injectPageContext()
|
||||
|
||||
defineProps<{
|
||||
title: string
|
||||
title?: string
|
||||
}>()
|
||||
</script>
|
||||
<template>
|
||||
@@ -14,7 +14,7 @@ defineProps<{
|
||||
'card-shadow mb-4 last:mb-0 rounded-2xl bg-bg-raised': !hierarchicalSidebarAvailable,
|
||||
}"
|
||||
>
|
||||
<span class="font-semibold">{{ title }}</span>
|
||||
<span v-if="title" class="font-semibold">{{ title }}</span>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as ServersManageBackupsPage } from './manage/backups.vue'
|
||||
export { default as ServersManagePageIndex } from './manage/index.vue'
|
||||
@@ -1,2 +1,2 @@
|
||||
export { default as ServersManageBackupsPage } from './hosting/manage/backups.vue'
|
||||
export { default as ServersManagePageIndex } from './hosting/manage/index.vue'
|
||||
export * from './hosting'
|
||||
export * from './project'
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ProjectPage } from './index.vue'
|
||||
@@ -0,0 +1,120 @@
|
||||
<script setup lang="ts">
|
||||
import { AsteriskIcon,ImageIcon, InfoIcon } from '@modrinth/assets'
|
||||
import { computed } from 'vue'
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
ContentPageHeader,
|
||||
LargeCopyField,
|
||||
NavTabs,
|
||||
NormalPage,
|
||||
SidebarCard,
|
||||
} from '../../components'
|
||||
|
||||
const project = {
|
||||
id: 'bJuQ1kl5',
|
||||
slug: 'complex-cobblemon',
|
||||
project_types: [
|
||||
'server', // add server type
|
||||
'modpack',
|
||||
],
|
||||
games: ['minecraft-java'],
|
||||
team_id: 'aUYO8LVq',
|
||||
organization: null,
|
||||
name: 'Hypixel',
|
||||
summary: 'A mini-game network, featuring Bed Wars, SkyBlock, SkyWars, Murder Mystery, and more!',
|
||||
description: 'project desc',
|
||||
published: '2025-12-07T20:07:01.351121Z',
|
||||
updated: '2025-12-07T22:31:02.164985Z', // should track when server IP, mc version, or content changes
|
||||
approved: '2025-12-08T03:09:10.517396Z',
|
||||
queued: '2025-12-07T21:50:54.823172Z',
|
||||
status: 'approved',
|
||||
requested_status: 'approved',
|
||||
moderator_message: null,
|
||||
license: null, // becomes nullable
|
||||
downloads: 1, // dunno if we should show? could count total server pack downloads or just always 0/null
|
||||
followers: 0,
|
||||
categories: ['game-mechanics', 'utility'], // tbd, but will be server-specific categories
|
||||
additional_categories: [],
|
||||
loaders: null, // nullable or empty, depending on what is possible
|
||||
versions: ['dA2xTlv'], // only show the current active release
|
||||
icon_url: 'https://avatars.githubusercontent.com/u/3840546?s=280&v=4',
|
||||
link_urls: {}, // same as projects, maybe allow some extra types? can figure out later
|
||||
gallery: [],
|
||||
color: 12553338,
|
||||
thread_id: 'ML721Sj1',
|
||||
monetization_status: null, // nullable or empty, depending on what is possible
|
||||
side_types_migration_review_status: null, // nullable or empty, depending on what is possible
|
||||
game_versions: null, // nullable or empty, depending on what is possible
|
||||
environment: null, // nullable or empty, depending on what is possible
|
||||
server_metadata: {
|
||||
// always exists when project_types contains "server", otherwise does not
|
||||
java_address: 'mc.hypixel.net',
|
||||
bedrock_address: null,
|
||||
players_online: 1234,
|
||||
max_players: 20000, // nullable, allow creators to show no max
|
||||
plays: 0, // number of times people have joined the server via Modrinth App
|
||||
total_players: 2304, // number of unique players who have played via Modrinth App
|
||||
content: {
|
||||
// IF vanilla
|
||||
type: 'vanilla',
|
||||
suggested_minecraft_version: '1.21.10',
|
||||
supported_minecraft_versions: ['1.21.9', '1.21.10'],
|
||||
},
|
||||
// content: {
|
||||
// // ELSE IF modpack
|
||||
// type: 'modpack',
|
||||
// modpack_version_id: 'dA2xTlv',
|
||||
// },
|
||||
},
|
||||
}
|
||||
|
||||
const server = computed(() =>
|
||||
project.project_types.includes('server') ? project.server_metadata : false,
|
||||
)
|
||||
|
||||
const tabs = computed(() => {})
|
||||
</script>
|
||||
<template>
|
||||
<NormalPage sidebar="right">
|
||||
<template #header>
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
<Avatar :src="project.icon_url" size="104px" />
|
||||
</template>
|
||||
<template #title>
|
||||
{{ project.name }}
|
||||
</template>
|
||||
<template #summary>
|
||||
{{ project.summary }}
|
||||
</template>
|
||||
</ContentPageHeader>
|
||||
<NavTabs
|
||||
:links="[
|
||||
{
|
||||
label: 'Description',
|
||||
href: '',
|
||||
icon: InfoIcon,
|
||||
},
|
||||
{
|
||||
label: 'Gallery',
|
||||
href: '',
|
||||
icon: ImageIcon,
|
||||
},
|
||||
{
|
||||
label: 'Required content',
|
||||
href: '',
|
||||
icon: AsteriskIcon,
|
||||
},
|
||||
]"
|
||||
class="mt-6"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="server" #sidebar>
|
||||
<SidebarCard>
|
||||
<LargeCopyField :text="server.java_address" />
|
||||
</SidebarCard>
|
||||
</template>
|
||||
Project page content
|
||||
</NormalPage>
|
||||
</template>
|
||||
Reference in New Issue
Block a user