Onboarding changes (#347)
* modal * Finish tutorial phase * Finish onboarding, tutorial, and url front end handlng * Run lint * Update pnpm-lock.yaml * Fixed bad refactor * Fixed #341 * lint * Fixes #315 * Update ModInstallModal.vue * Initial onboarding changes * importing card * Run lint * Update ImportingCard.vue * Fixed home page errors * Fixes * Linter * Login page * Tweaks * Update ImportingCard.vue * Onboarding finishing changes * Linter * update to new auth * bump version * backend for linking --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me> Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
No files matched your search
@@ -20,30 +20,39 @@ import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||
import { useNotifications } from '@/store/notifications.js'
|
||||
import { warning_listener } from '@/helpers/events.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import { type } from '@tauri-apps/api/os'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { isDev } from '@/helpers/utils.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import OnboardingModal from '@/components/OnboardingModal.vue'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { window as TauriWindow } from '@tauri-apps/api'
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
// import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue'
|
||||
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
|
||||
import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const urlModal = ref(null)
|
||||
const isLoading = ref(true)
|
||||
const videoPlaying = ref(true)
|
||||
const showOnboarding = ref(false)
|
||||
|
||||
const onboardingVideo = ref()
|
||||
|
||||
defineExpose({
|
||||
initialize: async () => {
|
||||
isLoading.value = false
|
||||
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering, onboarded } =
|
||||
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering, onboarded_new } =
|
||||
await get()
|
||||
const dev = await isDev()
|
||||
const version = await getVersion()
|
||||
showOnboarding.value = !onboarded_new
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
@@ -53,7 +62,7 @@ defineExpose({
|
||||
if (opt_out_analytics) {
|
||||
mixpanel.opt_out_tracking()
|
||||
}
|
||||
mixpanel.track('Launched', { version, dev, onboarded })
|
||||
mixpanel.track('Launched', { version, dev, onboarded_new })
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
@@ -70,6 +79,10 @@ defineExpose({
|
||||
type: 'warn',
|
||||
})
|
||||
)
|
||||
|
||||
if (showOnboarding.value) {
|
||||
onboardingVideo.value.play()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
@@ -145,14 +158,26 @@ document.querySelector('body').addEventListener('click', function (e) {
|
||||
})
|
||||
|
||||
const accounts = ref(null)
|
||||
|
||||
command_listener((e) => {
|
||||
console.log(e)
|
||||
urlModal.value.show(e)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<SplashScreen v-if="isLoading" app-loading />
|
||||
<StickyTitleBar v-if="videoPlaying" />
|
||||
<video
|
||||
v-if="videoPlaying"
|
||||
ref="onboardingVideo"
|
||||
class="video"
|
||||
src="@/assets/video.mp4"
|
||||
autoplay
|
||||
@ended="videoPlaying = false"
|
||||
/>
|
||||
<SplashScreen v-else-if="!videoPlaying && isLoading" app-loading />
|
||||
<OnboardingScreen v-else-if="showOnboarding" :finish="() => (showOnboarding = false)" />
|
||||
<div v-else class="container">
|
||||
<suspense>
|
||||
<OnboardingModal ref="testModal" :accounts="accounts" />
|
||||
</suspense>
|
||||
<div class="nav-container">
|
||||
<div class="nav-section">
|
||||
<suspense>
|
||||
@@ -181,7 +206,8 @@ const accounts = ref(null)
|
||||
</div>
|
||||
<div class="settings pages-list">
|
||||
<Button
|
||||
class="sleek-primary icon-only collapsed-button"
|
||||
class="sleek-primary collapsed-button"
|
||||
icon-only
|
||||
@click="() => $refs.installationModal.show()"
|
||||
>
|
||||
<PlusIcon />
|
||||
@@ -229,7 +255,6 @@ const accounts = ref(null)
|
||||
offset-height="var(--appbar-height)"
|
||||
offset-width="var(--sidebar-width)"
|
||||
/>
|
||||
<Notifications ref="notificationsWrapper" />
|
||||
<RouterView v-slot="{ Component }" class="main-view">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
||||
@@ -240,6 +265,8 @@ const accounts = ref(null)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<URLConfirmModal ref="urlModal" />
|
||||
<Notifications ref="notificationsWrapper" />
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -449,4 +476,12 @@ const accounts = ref(null)
|
||||
height: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.video {
|
||||
margin-top: 2.25rem;
|
||||
width: 100vw;
|
||||
height: calc(100vh - 2.25rem);
|
||||
object-fit: cover;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1 @@
|
||||
<svg viewBox="0 0 2084 2084" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2"><g fill-rule="nonzero"><path d="M1041.67 81.38l272.437 159.032-825.246 478.685-272.438-157.971L1041.67 81.38zm87.28 371.074l274.024-159.032 463.937 271.945-276.14 153.73-461.821-266.643z" fill="#3b3b3b"/><path d="M216.42 561.126v961.081l825.247 479.746V1684.95l-551.222-321.774-1.587-644.079L216.42 561.126z" fill="#2e2e2e"/><path d="M1866.91 1517.97l-825.246 483.986v-317.003l550.164-320.714-1.058-645.139 276.14-153.73v952.6z" fill="#333"/><path d="M1590.77 719.097l-549.106 310.112v165.393l214.246-122.984v488.757l138.599-81.106V989.451l196.261-115.563V719.097z" fill="#89c236"/><path d="M488.858 719.097l1.587 644.079 152.353 90.118v-198.79l230.645 132.527v199.319l168.753 98.6v-655.741L488.858 719.097zm383.527 531.166l-227.471-131.466v-150.02l227.471 127.225v154.261z" fill="#7baf31"/></g></svg>
|
||||
|
After Width: | Height: | Size: 952 B |
|
After Width: | Height: | Size: 26 KiB |
@@ -1 +1,4 @@
|
||||
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg"><path d="m16 .396c-8.839 0-16 7.167-16 16 0 7.073 4.584 13.068 10.937 15.183.803.151 1.093-.344 1.093-.772 0-.38-.009-1.385-.015-2.719-4.453.964-5.391-2.151-5.391-2.151-.729-1.844-1.781-2.339-1.781-2.339-1.448-.989.115-.968.115-.968 1.604.109 2.448 1.645 2.448 1.645 1.427 2.448 3.744 1.74 4.661 1.328.14-1.031.557-1.74 1.011-2.135-3.552-.401-7.287-1.776-7.287-7.907 0-1.751.62-3.177 1.645-4.297-.177-.401-.719-2.031.141-4.235 0 0 1.339-.427 4.4 1.641 1.281-.355 2.641-.532 4-.541 1.36.009 2.719.187 4 .541 3.043-2.068 4.381-1.641 4.381-1.641.859 2.204.317 3.833.161 4.235 1.015 1.12 1.635 2.547 1.635 4.297 0 6.145-3.74 7.5-7.296 7.891.556.479 1.077 1.464 1.077 2.959 0 2.14-.02 3.864-.02 4.385 0 .416.28.916 1.104.755 6.4-2.093 10.979-8.093 10.979-15.156 0-8.833-7.161-16-16-16z"/></svg>
|
||||
<svg height="32" viewBox="0 0 32 32" width="32" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
|
||||
<path
|
||||
d="m16 .396c-8.839 0-16 7.167-16 16 0 7.073 4.584 13.068 10.937 15.183.803.151 1.093-.344 1.093-.772 0-.38-.009-1.385-.015-2.719-4.453.964-5.391-2.151-5.391-2.151-.729-1.844-1.781-2.339-1.781-2.339-1.448-.989.115-.968.115-.968 1.604.109 2.448 1.645 2.448 1.645 1.427 2.448 3.744 1.74 4.661 1.328.14-1.031.557-1.74 1.011-2.135-3.552-.401-7.287-1.776-7.287-7.907 0-1.751.62-3.177 1.645-4.297-.177-.401-.719-2.031.141-4.235 0 0 1.339-.427 4.4 1.641 1.281-.355 2.641-.532 4-.541 1.36.009 2.719.187 4 .541 3.043-2.068 4.381-1.641 4.381-1.641.859 2.204.317 3.833.161 4.235 1.015 1.12 1.635 2.547 1.635 4.297 0 6.145-3.74 7.5-7.296 7.891.556.479 1.077 1.464 1.077 2.959 0 2.14-.02 3.864-.02 4.385 0 .416.28.916 1.104.755 6.4-2.093 10.979-8.093 10.979-15.156 0-8.833-7.161-16-16-16z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 901 B |
@@ -0,0 +1,10 @@
|
||||
<svg
|
||||
data-v-8c2610d6=""
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
fill="currentColor"
|
||||
viewBox="0 0 380 380"
|
||||
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2;"
|
||||
>
|
||||
<path d="m282.83 170.73-.27-.69-26.14-68.22a6.815 6.815 0 0 0-2.69-3.24 7.013 7.013 0 0 0-8 .43 6.996 6.996 0 0 0-2.32 3.52l-17.65 54h-71.47l-17.65-54a6.864 6.864 0 0 0-2.32-3.53 7.013 7.013 0 0 0-8-.43 6.867 6.867 0 0 0-2.69 3.24L97.44 170l-.26.69c-7.708 20.139-1.115 43.113 16.1 56.1l.09.07.24.17 39.82 29.82 19.7 14.91 12 9.06a8.088 8.088 0 0 0 9.76 0l12-9.06 19.7-14.91 40.06-30 .1-.08c17.175-12.988 23.755-35.921 16.08-56.04Z" transform="translate(-186.013 -186.006) scale(1.97904)" style="fill-rule: nonzero;"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 757 B |
@@ -0,0 +1,21 @@
|
||||
<svg
|
||||
data-v-8c2610d6=""
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xml:space="preserve"
|
||||
viewBox="0 0 100 100"
|
||||
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2;"><circle cx="50" cy="50" r="50" style="fill:#fff;"
|
||||
/>
|
||||
<g transform="translate(14.39 14.302) scale(.09916)"><clipPath id="a"><path d="M0 0h705.6v720H0z"/></clipPath>
|
||||
<g clip-path="url(#a)"><path d="M-4117.16-2597.44v139.42h193.74c-8.51 44.84-34.04 82.8-72.33 108.33l116.84 90.66c68.07-62.84 107.35-155.13 107.35-264.77 0-25.53-2.29-50.07-6.55-73.63l-339.05-.01Z" style="fill:#4285f4;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="m-4318.92-2463.46-26.35 20.17-93.28 72.65c59.24 117.49 180.65 198.66 321.38 198.66 97.2 0 178.69-32.07 238.25-87.05l-116.83-90.66c-32.08 21.6-72.99 34.69-121.42 34.69-93.6 0-173.13-63.16-201.6-148.25l-.15-.21Z"
|
||||
style="fill:#34a853;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="M-4438.55-2693.33c-24.54 48.44-38.61 103.09-38.61 161.34 0 58.26 14.07 112.91 38.61 161.35 0 .32 119.79-92.95 119.79-92.95-7.2-21.6-11.46-44.5-11.46-68.4 0-23.89 4.26-46.8 11.46-68.4l-119.79-92.94Z"
|
||||
style="fill:#fbbc05;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
<path
|
||||
d="M-4117.16-2748.64c53.02 0 100.14 18.33 137.78 53.67l103.09-103.09c-62.51-58.25-143.67-93.93-240.87-93.93-140.73 0-262.15 80.84-321.39 198.66l119.79 92.95c28.47-85.09 108-148.26 201.6-148.26Z"
|
||||
style="fill:#ea4335;fill-rule:nonzero;" transform="translate(4477.16 2891.98)"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -8,3 +8,11 @@ export { default as TwitterIcon } from './twitter.svg'
|
||||
export { default as GithubIcon } from './github.svg'
|
||||
export { default as MastodonIcon } from './mastodon.svg'
|
||||
export { default as RedditIcon } from './reddit.svg'
|
||||
export { default as GoogleIcon } from './google.svg'
|
||||
export { default as MicrosoftIcon } from './microsoft.svg'
|
||||
export { default as SteamIcon } from './steam.svg'
|
||||
export { default as GitLabIcon } from './gitlab.svg'
|
||||
export { default as ATLauncherIcon } from './atlauncher.svg'
|
||||
export { default as GDLauncherIcon } from './gdlauncher.png'
|
||||
export { default as MultiMCIcon } from './multimc.webp'
|
||||
export { default as PrismIcon } from './prism.svg'
|
||||
@@ -0,0 +1,6 @@
|
||||
<svg data-v-8c2610d6="" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21">
|
||||
<path fill="#f25022" d="M1 1h9v9H1z"/>
|
||||
<path fill="#00a4ef" d="M1 11h9v9H1z"/>
|
||||
<path fill="#7fba00" d="M11 1h9v9h-9z"/>
|
||||
<path fill="#ffb900" d="M11 11h9v9h-9z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 257 B |
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,80 @@
|
||||
<svg
|
||||
viewBox="0 0 12.7 12.7"
|
||||
version="1.1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<title id="title261">Prism Launcher Logo</title>
|
||||
<defs id="defs3603" />
|
||||
<g id="layer1">
|
||||
<g
|
||||
id="g531"
|
||||
transform="matrix(0.1353646,0,0,0.1353646,15.301582,0.52916663)" />
|
||||
<g
|
||||
id="g397">
|
||||
<path
|
||||
style="fill:#99cd61;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.3500002,6.350001 Z"
|
||||
id="path7899" />
|
||||
<path
|
||||
id="path3228"
|
||||
style="fill:#df6277;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.35 0.52916667 L 3.8292236 4.8947917 L 6.35 6.35 L 8.8702596 4.8947917 L 8.9798136 1.7952393 C 7.828708 1.1306481 6.6410414 0.52916667 6.35 0.52916667 z " />
|
||||
<path
|
||||
id="path2659"
|
||||
style="fill:#fb9168;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 8.9798136 1.7952393 L 6.35 6.35 L 8.8702596 7.8052083 L 11.391036 3.4395833 C 11.245515 3.1875341 10.130919 2.4598305 8.9798136 1.7952393 z " />
|
||||
<path
|
||||
id="path2708"
|
||||
style="fill:#f3db6c;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 11.391036 3.4395833 L 6.35 6.35 L 8.8702596 7.8052083 L 11.609111 6.35 C 11.609111 5.0208177 11.536557 3.6916326 11.391036 3.4395833 z " />
|
||||
<path
|
||||
id="path1737"
|
||||
style="fill:#7ab392;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.35 6.35 L 6.35 9.2604167 L 11.391036 9.2604167 C 11.536557 9.0083674 11.60911 7.6791823 11.609111 6.35 L 6.35 6.35 z " />
|
||||
<path
|
||||
id="path2937"
|
||||
style="fill:#4b7cbc;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.35 6.35 L 6.35 9.2604167 L 8.9798136 10.904761 C 10.130919 10.24017 11.245515 9.5124659 11.391036 9.2604167 L 6.35 6.35 z " />
|
||||
<path
|
||||
id="path3117"
|
||||
style="fill:#6f488c;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.35 6.35 L 3.8292236 7.8052083 L 6.35 12.170833 C 6.6410414 12.170833 7.8287079 11.569352 8.9798136 10.904761 L 6.35 6.35 z " />
|
||||
<path
|
||||
id="path2010"
|
||||
style="fill:#4d3f33;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 3.8292236 4.8947917 L 1.308964 9.2604167 C 1.6000054 9.7645152 5.7679172 12.170833 6.35 12.170833 L 6.35 6.35 L 3.8292236 4.8947917 z " />
|
||||
<path
|
||||
id="path1744"
|
||||
style="fill:#7a573b;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 1.308964 3.4395833 C 1.0179226 3.9436818 1.0179227 8.7563182 1.308964 9.2604167 L 6.35 6.35 L 6.35 3.4395833 L 1.308964 3.4395833 z " />
|
||||
<path
|
||||
id="path1739"
|
||||
style="fill:#99cd61;fill-opacity:1;stroke-width:0.264583"
|
||||
d="M 6.35 0.52916667 C 5.7679172 0.52916665 1.6000054 2.9354849 1.308964 3.4395833 L 6.35 6.35 L 6.35 0.52916667 z " />
|
||||
<g
|
||||
id="g379">
|
||||
<g
|
||||
id="g1657"
|
||||
transform="matrix(0.87999988,0,0,0.87999988,-10.906495,-1.242093)">
|
||||
<g
|
||||
id="g7651"
|
||||
transform="translate(13.259961,2.2775894)">
|
||||
<path
|
||||
id="path6659"
|
||||
style="fill:#ffffff;stroke-width:0.264583"
|
||||
d="m 6.3498163,2.9393223 c -0.3410461,0 -2.782726,1.4098777 -2.9532491,1.7052323 L 6.3498163,9.7602513 9.3035983,4.6445546 C 9.1330753,4.3492 6.6908624,2.9393223 6.3498163,2.9393223 Z"
|
||||
transform="matrix(0.96974817,0,0,0.96974817,0.19209885,0.19209792)" />
|
||||
</g>
|
||||
<path
|
||||
id="path461"
|
||||
style="fill:#dfdfdf;fill-opacity:1;stroke-width:0.264583"
|
||||
d="m 16.745875,6.9737355 2.863908,4.9609385 c 0.330729,0 2.69906,-1.367226 2.864424,-1.653646 0.165365,-0.2864204 0.165365,-3.0208729 0,-3.3072925 l -2.864424,1.6536459 z" />
|
||||
</g>
|
||||
<path
|
||||
id="path5065"
|
||||
style="fill:#d6d2d2;fill-opacity:1;stroke-width:0.264583"
|
||||
d="m 3.8298625,4.8947933 c -0.1455111,0.2520549 -0.1455304,2.6583729 0,2.9104166 0.1455304,0.2520438 2.2292181,1.4552195 2.5202596,1.4552084 V 6.3500016 Z" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg
|
||||
data-v-8c2610d6=""
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="16"
|
||||
height="16"
|
||||
fill="currentColor"
|
||||
class="bi bi-steam"
|
||||
viewBox="0 0 16 16"
|
||||
>
|
||||
<path
|
||||
d="M.329 10.333A8.01 8.01 0 0 0 7.99 16C12.414 16 16 12.418 16 8s-3.586-8-8.009-8A8.006 8.006 0 0 0 0 7.468l.003.006 4.304 1.769A2.198 2.198 0 0 1 5.62 8.88l1.96-2.844-.001-.04a3.046 3.046 0 0 1 3.042-3.043 3.046 3.046 0 0 1 3.042 3.043 3.047 3.047 0 0 1-3.111 3.044l-2.804 2a2.223 2.223 0 0 1-3.075 2.11 2.217 2.217 0 0 1-1.312-1.568L.33 10.333Z"/>
|
||||
<path
|
||||
d="M4.868 12.683a1.715 1.715 0 0 0 1.318-3.165 1.705 1.705 0 0 0-1.263-.02l1.023.424a1.261 1.261 0 1 1-.97 2.33l-.99-.41a1.7 1.7 0 0 0 .882.84Zm3.726-6.687a2.03 2.03 0 0 0 2.027 2.029 2.03 2.03 0 0 0 2.027-2.029 2.03 2.03 0 0 0-2.027-2.027 2.03 2.03 0 0 0-2.027 2.027Zm2.03-1.527a1.524 1.524 0 1 1-.002 3.048 1.524 1.524 0 0 1 .002-3.048Z"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 881 B |
|
After Width: | Height: | Size: 9.0 MiB |
@@ -74,12 +74,17 @@ input {
|
||||
padding-top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
|
||||
.account-card {
|
||||
.account-card,
|
||||
.card-section {
|
||||
top: calc(var(--gap-md) + 1.75rem);
|
||||
}
|
||||
}
|
||||
|
||||
.windows {
|
||||
.fake-appbar {
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: flex !important;
|
||||
}
|
||||
@@ -114,3 +119,12 @@ input {
|
||||
border-radius: var(--radius-lg);
|
||||
border: 3px solid var(--color-bg);
|
||||
}
|
||||
|
||||
.highlighted {
|
||||
box-shadow: 0 0 1rem var(--color-brand) !important;
|
||||
}
|
||||
|
||||
.gecko {
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: none !important;
|
||||
}
|
||||
@@ -18,7 +18,7 @@ import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
@@ -269,7 +269,7 @@ onUnmounted(() => {
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||
</ContextMenu>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
|
||||
@@ -8,7 +8,11 @@
|
||||
>
|
||||
<Avatar
|
||||
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
||||
:src="selectedAccount ? `https://mc-heads.net/avatar/${selectedAccount.id}/128` : ''"
|
||||
:src="
|
||||
selectedAccount
|
||||
? `https://mc-heads.net/avatar/${selectedAccount.id}/128`
|
||||
: 'https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png'
|
||||
"
|
||||
/>
|
||||
<div v-show="mode === 'expanded'" class="avatar-text">
|
||||
<div class="text no-select">
|
||||
|
||||
@@ -233,5 +233,6 @@ const exportPack = async () => {
|
||||
.button-row {
|
||||
display: flex;
|
||||
gap: var(--gap-sm);
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -15,7 +15,7 @@ import { process_listener } from '@/helpers/events'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -227,7 +227,7 @@ onUnmounted(() => unlisten())
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<template>
|
||||
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
||||
<div class="modal-header">
|
||||
<Chips v-model="creationType" :items="['custom', 'from file']" />
|
||||
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
<div v-if="creationType === 'custom'" class="modal-body">
|
||||
<div class="image-upload">
|
||||
<Avatar :src="display_icon" size="md" :rounded="true" />
|
||||
@@ -82,10 +83,106 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="modal-body">
|
||||
<div v-else-if="creationType === 'from file'" class="modal-body">
|
||||
<Button @click="openFile"> <FolderOpenIcon /> Import from file </Button>
|
||||
<div class="info"><InfoIcon /> Or drag and drop your .mrpack file</div>
|
||||
</div>
|
||||
<div v-else class="modal-body">
|
||||
<Chips
|
||||
v-model="selectedProfileType"
|
||||
:items="profileOptions"
|
||||
:format-label="(profile) => profile?.name"
|
||||
/>
|
||||
<div class="path-selection">
|
||||
<h3>{{ selectedProfileType.name }} path</h3>
|
||||
<div class="path-input">
|
||||
<div class="iconified-input">
|
||||
<FolderOpenIcon />
|
||||
<input
|
||||
v-model="selectedProfileType.path"
|
||||
type="text"
|
||||
placeholder="Path to launcher"
|
||||
@change="setPath"
|
||||
/>
|
||||
<Button @click="() => (selectedLauncherPath = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Button icon-only @click="selectLauncherPath">
|
||||
<FolderSearchIcon />
|
||||
</Button>
|
||||
<Button icon-only @click="reload">
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="table-head table-row">
|
||||
<div class="toggle-all table-cell">
|
||||
<Checkbox
|
||||
class="select-checkbox"
|
||||
:model-value="
|
||||
profiles.get(selectedProfileType.name)?.every((child) => child.selected)
|
||||
"
|
||||
@update:model-value="
|
||||
(newValue) =>
|
||||
profiles
|
||||
.get(selectedProfileType.name)
|
||||
?.forEach((child) => (child.selected = newValue))
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="name-cell table-cell">Profile name</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
profiles.get(selectedProfileType.name) &&
|
||||
profiles.get(selectedProfileType.name).length > 0
|
||||
"
|
||||
class="table-content"
|
||||
>
|
||||
<div
|
||||
v-for="(profile, index) in profiles.get(selectedProfileType.name)"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<div class="checkbox-cell table-cell">
|
||||
<Checkbox v-model="profile.selected" class="select-checkbox" />
|
||||
</div>
|
||||
<div class="name-cell table-cell">
|
||||
{{ profile.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="table-content empty">No profiles found</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button
|
||||
:disabled="
|
||||
loading ||
|
||||
!Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.some((e) => e.selected)
|
||||
"
|
||||
color="primary"
|
||||
@click="next"
|
||||
>
|
||||
{{
|
||||
loading
|
||||
? 'Importing...'
|
||||
: Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.some((e) => e.selected)
|
||||
? `Import ${
|
||||
Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.filter((e) => e.selected).length
|
||||
} profiles`
|
||||
: 'Select profiles to import'
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -102,6 +199,8 @@ import {
|
||||
Checkbox,
|
||||
FolderOpenIcon,
|
||||
InfoIcon,
|
||||
FolderSearchIcon,
|
||||
UpdatedIcon,
|
||||
} from 'omorphia'
|
||||
import { computed, ref, shallowRef } from 'vue'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
@@ -120,6 +219,7 @@ import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
import { get_importable_instances, import_instance } from '@/helpers/import.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -284,6 +384,68 @@ listen('tauri://file-drop', async (event) => {
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const profiles = ref(
|
||||
new Map([
|
||||
['MultiMC', []],
|
||||
['GDLauncher', []],
|
||||
['ATLauncher', []],
|
||||
['Curseforge', []],
|
||||
['PrismLauncher', []],
|
||||
])
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const selectedProfileType = ref('MultiMC')
|
||||
const profileOptions = ref([
|
||||
{ name: 'MultiMC', path: '' },
|
||||
{ name: 'GDLauncher', path: '' },
|
||||
{ name: 'ATLauncher', path: '' },
|
||||
{ name: 'Curseforge', path: '' },
|
||||
{ name: 'PrismLauncher', path: '' },
|
||||
])
|
||||
|
||||
const selectLauncherPath = async () => {
|
||||
selectedProfileType.value.path = await open({ multiple: false, directory: true })
|
||||
|
||||
if (selectedProfileType.value.path) {
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async () => {
|
||||
const instances = await get_importable_instances(
|
||||
selectedProfileType.value.name,
|
||||
selectedProfileType.value.path
|
||||
).catch(handleError)
|
||||
profiles.value.set(
|
||||
selectedProfileType.value.name,
|
||||
instances.map((name) => ({ name, selected: false }))
|
||||
)
|
||||
}
|
||||
|
||||
const setPath = () => {
|
||||
profileOptions.value.find((profile) => profile.name === selectedProfileType.value.name).path =
|
||||
selectedProfileType.value.path
|
||||
}
|
||||
|
||||
const next = async () => {
|
||||
loading.value = true
|
||||
for (const launcher of Array.from(profiles.value.entries()).map(([launcher, profiles]) => ({
|
||||
launcher,
|
||||
path: profileOptions.value.find((option) => option.name === launcher).path,
|
||||
profiles,
|
||||
}))) {
|
||||
for (const profile of launcher.profiles.filter((profile) => profile.selected)) {
|
||||
await import_instance(launcher.launcher, launcher.path, profile.name)
|
||||
.catch(handleError)
|
||||
.then(() => console.log(`Successfully Imported ${profile.name} from ${launcher.launcher}`))
|
||||
profile.selected = false
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -363,4 +525,77 @@ listen('tauri://file-drop', async (event) => {
|
||||
padding: var(--gap-lg);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.path-selection {
|
||||
padding: var(--gap-xl);
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-md);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
:deep(input) {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 1px solid var(--color-bg);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
grid-template-columns: min-content auto;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
max-height: calc(5 * (18px + 2rem));
|
||||
height: calc(5 * (18px + 2rem));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select-checkbox {
|
||||
button.checkbox {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-sm) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bolder;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
margin: var(--gap-md) var(--gap-lg) 0 var(--gap-lg);
|
||||
}
|
||||
</style>
|
||||
@@ -24,9 +24,11 @@ import { installVersionDependencies } from '@/helpers/utils'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref([])
|
||||
const project = ref('')
|
||||
@@ -63,13 +65,23 @@ const profiles = ref([])
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
console.log(versions.value)
|
||||
const version = versions.value.find((v) => {
|
||||
return (
|
||||
v.game_versions.includes(instance.metadata.game_version) &&
|
||||
(v.loaders.includes(instance.metadata.loader) || v.loaders.includes('minecraft'))
|
||||
(v.loaders.includes(instance.metadata.loader) ||
|
||||
v.loaders.includes('minecraft') ||
|
||||
v.loaders.includes('iris') ||
|
||||
v.loaders.includes('optifine'))
|
||||
)
|
||||
})
|
||||
|
||||
if (!version) {
|
||||
instance.installing = false
|
||||
handleError('No compatible version found')
|
||||
return
|
||||
}
|
||||
|
||||
await installMod(instance.path, version.id).catch(handleError)
|
||||
await installVersionDependencies(instance, version)
|
||||
|
||||
@@ -153,11 +165,11 @@ const createInstance = async () => {
|
||||
creatingInstance.value = true
|
||||
|
||||
const loader =
|
||||
versions.value[0].loaders[0] !== 'forge' ||
|
||||
versions.value[0].loaders[0] !== 'fabric' ||
|
||||
versions.value[0].loaders[0] !== 'forge' &&
|
||||
versions.value[0].loaders[0] !== 'fabric' &&
|
||||
versions.value[0].loaders[0] !== 'quilt'
|
||||
? versions.value[0].loaders[0]
|
||||
: 'vanilla'
|
||||
? 'vanilla'
|
||||
: versions.value[0].loaders[0]
|
||||
|
||||
const id = await create(
|
||||
name.value,
|
||||
@@ -169,6 +181,8 @@ const createInstance = async () => {
|
||||
|
||||
await installMod(id, versions.value[0].id).catch(handleError)
|
||||
|
||||
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
||||
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value)
|
||||
|
||||
@@ -2,10 +2,13 @@
|
||||
<Card
|
||||
class="card button-base"
|
||||
@click="
|
||||
$router.push({
|
||||
path: `/project/${project.project_id}/`,
|
||||
query: { i: props.instance ? props.instance.path : undefined },
|
||||
})
|
||||
() => {
|
||||
emits('open')
|
||||
$router.push({
|
||||
path: `/project/${project.project_id ?? project.id}/`,
|
||||
query: { i: props.instance ? props.instance.path : undefined },
|
||||
})
|
||||
}
|
||||
"
|
||||
>
|
||||
<div class="icon">
|
||||
@@ -14,7 +17,7 @@
|
||||
<div class="content-wrapper">
|
||||
<div class="title joined-text">
|
||||
<h2>{{ project.title }}</h2>
|
||||
<span>by {{ project.author }}</span>
|
||||
<span v-if="project.author">by {{ project.author }}</span>
|
||||
</div>
|
||||
<div class="description">
|
||||
{{ project.description }}
|
||||
@@ -42,14 +45,14 @@
|
||||
</div>
|
||||
<div class="badge">
|
||||
<HeartIcon />
|
||||
{{ formatNumber(project.follows) }}
|
||||
{{ formatNumber(project.follows ?? project.followers) }}
|
||||
</div>
|
||||
<div class="badge">
|
||||
<CalendarIcon />
|
||||
{{ formatCategory(dayjs(project.date_modified).fromNow()) }}
|
||||
{{ formatCategory(dayjs(project.date_modified ?? project.updated).fromNow()) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="install">
|
||||
<div v-if="project.author" class="install">
|
||||
<Button color="primary" :disabled="installed || installing" @click.stop="install()">
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<CheckIcon v-else />
|
||||
@@ -124,6 +127,8 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const emits = defineEmits(['open'])
|
||||
|
||||
const installing = ref(false)
|
||||
const installed = ref(props.installed)
|
||||
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
<script setup>
|
||||
import { Modal, Button } from 'omorphia'
|
||||
import { ref } from 'vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
const version = ref(null)
|
||||
const categories = ref(null)
|
||||
const installing = ref(false)
|
||||
const modInstallModal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
async show(event) {
|
||||
if (event.event === 'InstallVersion') {
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(event.id)}`,
|
||||
'version'
|
||||
)
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
|
||||
'project'
|
||||
)
|
||||
} else {
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(event.id)}`,
|
||||
'project'
|
||||
)
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
|
||||
'version'
|
||||
)
|
||||
}
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||
)
|
||||
confirmModal.value.show()
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||
)
|
||||
confirmModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
async function install() {
|
||||
confirmModal.value.hide()
|
||||
if (project.value.project_type === 'modpack') {
|
||||
await packInstall(
|
||||
project.value.id,
|
||||
version.value.id,
|
||||
project.value.title,
|
||||
project.value.icon_url
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: project.value.id,
|
||||
version_id: version.value.id,
|
||||
title: project.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
modInstallModal.value.show(
|
||||
project.value.id,
|
||||
[version.value],
|
||||
project.value.title,
|
||||
project.value.project_type
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
|
||||
<div class="modal-body">
|
||||
<SearchCard
|
||||
:project="project"
|
||||
class="project-card"
|
||||
:categories="categories"
|
||||
@open="confirmModal.hide()"
|
||||
/>
|
||||
<div class="button-row">
|
||||
<div class="markdown-body">
|
||||
<p>
|
||||
Installing <code>{{ version.id }}</code> from Modrinth
|
||||
</p>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button :loading="installing" color="primary" @click="install">Install</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--gap-md);
|
||||
padding: var(--gap-lg);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
gap: var(--gap-md);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.project-card {
|
||||
background-color: var(--color-bg);
|
||||
width: 100%;
|
||||
|
||||
:deep(.badge) {
|
||||
border: 1px solid var(--color-raised-bg);
|
||||
background-color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<div ref="button" class="button-base avatar-button" :class="{ highlighted: showDemo }">
|
||||
<Avatar
|
||||
src="https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png"
|
||||
/>
|
||||
</div>
|
||||
<transition name="fade">
|
||||
<div v-if="showDemo" class="card-section">
|
||||
<Card ref="card" class="fake-account-card expanded highlighted">
|
||||
<div class="selected account">
|
||||
<Avatar
|
||||
size="xs"
|
||||
src="https://cdn.discordapp.com/attachments/817413688771608587/1129829843425570867/unnamed.png"
|
||||
/>
|
||||
<div>
|
||||
<h4>Modrinth</h4>
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button v-tooltip="'Log out'" icon-only color="raised">
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Button>
|
||||
<PlusIcon />
|
||||
Add account
|
||||
</Button>
|
||||
</Card>
|
||||
<slot />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Avatar, Button, Card, PlusIcon, TrashIcon } from 'omorphia'
|
||||
|
||||
defineProps({
|
||||
showDemo: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.selected {
|
||||
background: var(--color-brand-highlight);
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--color-contrast);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.logged-out {
|
||||
background: var(--color-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.account {
|
||||
width: max-content;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
padding: 0.5rem 1rem;
|
||||
|
||||
h4,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-section {
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
left: 5.5rem;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.fake-account-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
border: 1px solid var(--color-button-bg);
|
||||
width: max-content;
|
||||
user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.isolated {
|
||||
position: relative;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.accounts-title {
|
||||
font-size: 1.2rem;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.account-group {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.option {
|
||||
width: calc(100% - 2.25rem);
|
||||
background: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
box-shadow: none;
|
||||
|
||||
img {
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
--size: 1.5rem !important;
|
||||
}
|
||||
|
||||
.account-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
vertical-align: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 1rem;
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.avatar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--color-base);
|
||||
background-color: var(--color-raised-bg);
|
||||
border-radius: var(--radius-md);
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
&.expanded {
|
||||
border: 1px solid var(--color-button-bg);
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-text {
|
||||
margin: auto 0 auto 0.25rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.text {
|
||||
width: 6rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.accounts-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<div class="action-groups">
|
||||
<Button v-if="showDownload" ref="infoButton" icon-only class="icon-button show-card-icon">
|
||||
<DownloadIcon />
|
||||
</Button>
|
||||
<div v-if="showRunning" class="status highlighted">
|
||||
<span class="circle running" />
|
||||
<div ref="profileButton" class="running-text">Example Modpack</div>
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop">
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button">
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="status">
|
||||
<span class="circle stopped" />
|
||||
<span class="running-text"> No running instances </span>
|
||||
</div>
|
||||
</div>
|
||||
<transition name="download">
|
||||
<div v-if="showDownload" class="info-section">
|
||||
<Card ref="card" class="highlighted info-card">
|
||||
<h3 class="info-title">New Modpack</h3>
|
||||
<ProgressBar :progress="50" />
|
||||
<div class="row">50% Downloading modpack</div>
|
||||
</Card>
|
||||
<slot name="download" />
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="running">
|
||||
<div v-if="showRunning" class="info-section">
|
||||
<slot name="running" />
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { Button, DownloadIcon, Card, StopCircleIcon, TerminalSquareIcon } from 'omorphia'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
|
||||
defineProps({
|
||||
showDownload: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showRunning: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.action-groups {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
|
||||
.arrow {
|
||||
transition: transform 0.2s ease-in-out;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
&.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.status {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: var(--radius-md);
|
||||
border: 1px solid var(--color-button-bg);
|
||||
padding: var(--gap-sm) var(--gap-lg);
|
||||
}
|
||||
|
||||
.running-text {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-xs);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-ms-user-select: none; /* IE 10 and IE 11 */
|
||||
user-select: none;
|
||||
|
||||
&.clickable:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.circle {
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 0.25rem;
|
||||
|
||||
&.running {
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
|
||||
&.stopped {
|
||||
background-color: var(--color-base);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
background-color: rgba(0, 0, 0, 0);
|
||||
box-shadow: none;
|
||||
width: 1.25rem !important;
|
||||
height: 1.25rem !important;
|
||||
|
||||
&.stop {
|
||||
--text-color: var(--color-red) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
right: 0.75rem;
|
||||
z-index: 9;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
width: 20rem;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-raised);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
overflow: auto;
|
||||
transition: all 0.2s ease-in-out;
|
||||
border: 1px solid var(--color-button-bg);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
:hover {
|
||||
background-color: var(--color-raised-bg-hover);
|
||||
}
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-icon {
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
display: block;
|
||||
|
||||
:deep(svg) {
|
||||
left: 1rem;
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
.show-card-icon {
|
||||
color: var(--color-brand);
|
||||
}
|
||||
|
||||
.download-enter-active,
|
||||
.download-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.download-enter-from,
|
||||
.download-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.profile-button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--gap-sm);
|
||||
width: 100%;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: none;
|
||||
|
||||
.text {
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.profile-card {
|
||||
position: absolute;
|
||||
top: 3.5rem;
|
||||
right: 0.5rem;
|
||||
z-index: 9;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-raised);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
transition: all 0.2s ease-in-out;
|
||||
border: 1px solid var(--color-button-bg);
|
||||
padding: var(--gap-md);
|
||||
|
||||
&.hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { Card, DropdownSelect, SearchIcon, XIcon, Button, Avatar } from 'omorphia'
|
||||
|
||||
const search = ref('')
|
||||
const group = ref('Category')
|
||||
const filters = ref('All profiles')
|
||||
const sortBy = ref('Name')
|
||||
|
||||
defineProps({
|
||||
showFilters: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showInstances: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<Card class="header" :class="{ highlighted: showFilters }">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon />
|
||||
<input v-model="search" type="text" placeholder="Search" class="search-input" />
|
||||
<Button @click="() => (search = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="labeled_button">
|
||||
<span>Sort by</span>
|
||||
<DropdownSelect
|
||||
v-model="sortBy"
|
||||
class="sort-dropdown"
|
||||
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
||||
placeholder="Select..."
|
||||
/>
|
||||
</div>
|
||||
<div class="labeled_button">
|
||||
<span>Filter by</span>
|
||||
<DropdownSelect
|
||||
v-model="filters"
|
||||
class="filter-dropdown"
|
||||
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
|
||||
placeholder="Select..."
|
||||
/>
|
||||
</div>
|
||||
<div class="labeled_button">
|
||||
<span>Group by</span>
|
||||
<DropdownSelect
|
||||
v-model="group"
|
||||
class="group-dropdown"
|
||||
:options="['Category', 'Loader', 'Game version', 'None']"
|
||||
placeholder="Select..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<div class="row">
|
||||
<section class="instances">
|
||||
<Card
|
||||
v-for="project in 20"
|
||||
:key="project"
|
||||
class="instance-card-item button-base"
|
||||
:class="{ highlighted: project === 1 && showInstance }"
|
||||
>
|
||||
<Avatar
|
||||
size="sm"
|
||||
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">Example Profile</p>
|
||||
<p class="description">Forge/Fabric 1.20.1</p>
|
||||
</div>
|
||||
</Card>
|
||||
<slot />
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
|
||||
.divider {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
hr {
|
||||
background-color: var(--color-gray);
|
||||
height: 1px;
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
align-items: inherit;
|
||||
margin: 1rem 1rem 0 !important;
|
||||
padding: 1rem;
|
||||
width: calc(100% - 2rem);
|
||||
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
|
||||
input {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-dropdown {
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.filter-dropdown {
|
||||
width: 15rem;
|
||||
}
|
||||
|
||||
.group-dropdown {
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
.labeled_button {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.instances {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
width: 100%;
|
||||
gap: 1rem;
|
||||
margin-right: auto;
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
|
||||
.instance {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: var(--gap-md);
|
||||
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||
margin-bottom: 0;
|
||||
|
||||
.mod-image {
|
||||
--size: 100%;
|
||||
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
aspect-ratio: 1 / 1 !important;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 110%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-base);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
font-size: 0.775rem;
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,376 @@
|
||||
<script setup>
|
||||
import {
|
||||
DownloadIcon,
|
||||
ChevronRightIcon,
|
||||
formatNumber,
|
||||
CalendarIcon,
|
||||
HeartIcon,
|
||||
Avatar,
|
||||
Card,
|
||||
} from 'omorphia'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
|
||||
const modsRow = ref(null)
|
||||
const rows = ref(null)
|
||||
const maxInstancesPerRow = ref(0)
|
||||
const maxProjectsPerRow = ref(0)
|
||||
|
||||
const calculateCardsPerRow = () => {
|
||||
// Calculate how many cards fit in one row
|
||||
const containerWidth = rows.value[0].clientWidth
|
||||
// Convert container width from pixels to rem
|
||||
const containerWidthInRem =
|
||||
containerWidth / parseFloat(getComputedStyle(document.documentElement).fontSize)
|
||||
maxInstancesPerRow.value = Math.floor((containerWidthInRem + 1) / 11)
|
||||
maxProjectsPerRow.value = Math.floor((containerWidthInRem + 1) / 17)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
calculateCardsPerRow()
|
||||
window.addEventListener('resize', calculateCardsPerRow)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', calculateCardsPerRow)
|
||||
})
|
||||
|
||||
defineProps({
|
||||
showInstance: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<div
|
||||
v-for="(row, index) in ['Jump back in', 'Popular modpacks', 'Popular mods']"
|
||||
ref="rows"
|
||||
:key="row"
|
||||
class="row"
|
||||
>
|
||||
<div class="header">
|
||||
<p>{{ row }}</p>
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
<section v-if="index < 1" ref="modsRow" class="instances">
|
||||
<Card
|
||||
v-for="project in maxInstancesPerRow"
|
||||
:key="project"
|
||||
class="instance-card-item button-base"
|
||||
:class="{ highlighted: showInstance }"
|
||||
>
|
||||
<Avatar
|
||||
size="sm"
|
||||
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">Example Profile</p>
|
||||
<p class="description">Forge/Fabric 1.20.1</p>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
<section v-else ref="modsRow" class="projects">
|
||||
<div v-for="project in maxProjectsPerRow" :key="project" class="wrapper">
|
||||
<Card class="project-card button-base" :class="{ highlighted: showInstance }">
|
||||
<div
|
||||
class="banner no-image"
|
||||
:style="{
|
||||
'background-image': `url(https://cdn.discordapp.com/attachments/817413688771608587/1119143634319724564/image.png)`,
|
||||
}"
|
||||
>
|
||||
<div class="badges">
|
||||
<div class="badge">
|
||||
<DownloadIcon />
|
||||
{{ formatNumber(69420) }}
|
||||
</div>
|
||||
<div class="badge">
|
||||
<HeartIcon />
|
||||
{{ formatNumber(69) }}
|
||||
</div>
|
||||
<div class="badge">
|
||||
<CalendarIcon />
|
||||
Today
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="badges-wrapper no-image"
|
||||
:style="{
|
||||
background:
|
||||
'linear-gradient(rgba(' +
|
||||
[27, 217, 106, 0.03].join(',') +
|
||||
'), 65%, rgba(' +
|
||||
[27, 217, 106, 0.3].join(',') +
|
||||
'))',
|
||||
}"
|
||||
></div>
|
||||
</div>
|
||||
<Avatar
|
||||
class="icon"
|
||||
size="sm"
|
||||
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png"
|
||||
/>
|
||||
<div class="title">
|
||||
<div class="title-text">Example Project</div>
|
||||
<div class="author">by Modrinth</div>
|
||||
</div>
|
||||
<div class="description">
|
||||
An example project hangin on the Rinth. Very cool project, its probably on Forge and
|
||||
Fabric. Probably has a 401k and a family.
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
gap: 1rem;
|
||||
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
width: 100%;
|
||||
min-width: 100%;
|
||||
|
||||
&:nth-child(even) {
|
||||
background: var(--color-bg);
|
||||
}
|
||||
|
||||
.header {
|
||||
width: 100%;
|
||||
margin-bottom: 1rem;
|
||||
gap: var(--gap-xs);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: var(--font-size-lg);
|
||||
font-weight: bolder;
|
||||
white-space: nowrap;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
svg {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.instances {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
grid-gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.projects {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
||||
grid-gap: 1rem;
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
|
||||
svg {
|
||||
width: 2.5rem !important;
|
||||
height: 2.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
.instance {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
padding: var(--gap-md);
|
||||
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
|
||||
margin-bottom: 0;
|
||||
|
||||
.mod-image {
|
||||
--size: 100%;
|
||||
|
||||
width: 100% !important;
|
||||
height: auto !important;
|
||||
max-width: unset !important;
|
||||
max-height: unset !important;
|
||||
aspect-ratio: 1 / 1 !important;
|
||||
}
|
||||
|
||||
.project-info {
|
||||
margin-top: 1rem;
|
||||
width: 100%;
|
||||
|
||||
.title {
|
||||
color: var(--color-contrast);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
font-size: 1rem;
|
||||
line-height: 110%;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: var(--color-base);
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
font-weight: 500;
|
||||
font-size: 0.775rem;
|
||||
line-height: 125%;
|
||||
margin: 0.25rem 0 0;
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
position: relative;
|
||||
aspect-ratio: 1;
|
||||
|
||||
&:hover {
|
||||
.install:enabled {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.project-card {
|
||||
display: grid;
|
||||
grid-gap: 1rem;
|
||||
grid-template:
|
||||
'. . . .' 0
|
||||
'. icon title .' 3rem
|
||||
'banner banner banner banner' auto
|
||||
'. description description .' 3.5rem
|
||||
'. . . .' 0 / 0 3rem minmax(0, 1fr) 0;
|
||||
max-width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.icon {
|
||||
grid-area: icon;
|
||||
}
|
||||
|
||||
.title {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
grid-area: title;
|
||||
white-space: nowrap;
|
||||
|
||||
.title-text {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.author {
|
||||
font-size: var(--font-size-sm);
|
||||
grid-area: author;
|
||||
}
|
||||
|
||||
.banner {
|
||||
grid-area: banner;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
position: relative;
|
||||
|
||||
.badges-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
mix-blend-mode: hard-light;
|
||||
}
|
||||
|
||||
.badges {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--gap-sm);
|
||||
gap: var(--gap-xs);
|
||||
display: flex;
|
||||
z-index: 1;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: flex-end;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
grid-area: description;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
}
|
||||
|
||||
.badge {
|
||||
background-color: var(--color-raised-bg);
|
||||
font-size: var(--font-size-xs);
|
||||
padding: var(--gap-xs) var(--gap-sm);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-right: var(--gap-xs);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,496 @@
|
||||
<script setup>
|
||||
import { computed, readonly, ref } from 'vue'
|
||||
import {
|
||||
Avatar,
|
||||
Button,
|
||||
CalendarIcon,
|
||||
Card,
|
||||
Categories,
|
||||
Checkbox,
|
||||
ClearIcon,
|
||||
ClientIcon,
|
||||
DownloadIcon,
|
||||
DropdownSelect,
|
||||
EnvironmentIndicator,
|
||||
formatCategory,
|
||||
formatCategoryHeader,
|
||||
formatNumber,
|
||||
HeartIcon,
|
||||
NavRow,
|
||||
Pagination,
|
||||
Promotion,
|
||||
SearchFilter,
|
||||
SearchIcon,
|
||||
ServerIcon,
|
||||
StarIcon,
|
||||
XIcon,
|
||||
} from 'omorphia'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { handleError } from '@/store/state'
|
||||
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const query = ref('')
|
||||
const facets = ref([])
|
||||
const orFacets = ref([])
|
||||
const selectedVersions = ref([])
|
||||
const onlyOpenSource = ref(false)
|
||||
const showSnapshots = ref(false)
|
||||
const selectedEnvironments = ref([])
|
||||
const sortTypes = readonly([
|
||||
{ display: 'Relevance', name: 'relevance' },
|
||||
{ display: 'Download count', name: 'downloads' },
|
||||
{ display: 'Follow count', name: 'follows' },
|
||||
{ display: 'Recently published', name: 'newest' },
|
||||
{ display: 'Recently updated', name: 'updated' },
|
||||
])
|
||||
const sortType = ref(sortTypes[0])
|
||||
const maxResults = ref(20)
|
||||
const currentPage = ref(1)
|
||||
const projectType = ref('modpack')
|
||||
|
||||
const searchWrapper = ref(null)
|
||||
|
||||
const sortedCategories = computed(() => {
|
||||
const values = new Map()
|
||||
for (const category of categories.value.filter((cat) => cat.project_type === 'mod')) {
|
||||
if (!values.has(category.header)) {
|
||||
values.set(category.header, [])
|
||||
}
|
||||
values.get(category.header).push(category)
|
||||
}
|
||||
return values
|
||||
})
|
||||
|
||||
const [categories, loaders, availableGameVersions] = await Promise.all([
|
||||
get_categories().catch(handleError).then(ref),
|
||||
get_loaders().catch(handleError).then(ref),
|
||||
get_game_versions().catch(handleError).then(ref),
|
||||
])
|
||||
|
||||
const pageCount = ref(1)
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
return [
|
||||
{ label: 'Shaders', href: `` },
|
||||
{ label: 'Resource Packs', href: `` },
|
||||
{ label: 'Data Packs', href: `` },
|
||||
{ label: 'Mods', href: '' },
|
||||
{ label: 'Modpacks', href: '' },
|
||||
]
|
||||
})
|
||||
|
||||
defineProps({
|
||||
showSearch: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<Card class="search-panel-card" :class="{ highlighted: showSearch }">
|
||||
<Button role="button" disabled> <ClearIcon /> Clear Filters </Button>
|
||||
<div class="loaders">
|
||||
<h2>Loaders</h2>
|
||||
<div
|
||||
v-for="loader in loaders.filter(
|
||||
(l) =>
|
||||
(projectType !== 'mod' && l.supported_project_types?.includes(projectType)) ||
|
||||
(projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
)"
|
||||
:key="loader"
|
||||
>
|
||||
<SearchFilter
|
||||
:active-filters="orFacets"
|
||||
:icon="loader.icon"
|
||||
:display-name="formatCategory(loader.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||
class="filter-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="versions">
|
||||
<h2>Minecraft versions</h2>
|
||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
||||
<multiselect
|
||||
v-model="selectedVersions"
|
||||
:options="
|
||||
showSnapshots
|
||||
? availableGameVersions.map((x) => x.version)
|
||||
: availableGameVersions
|
||||
.filter((it) => it.version_type === 'release')
|
||||
.map((x) => x.version)
|
||||
"
|
||||
:multiple="true"
|
||||
:searchable="true"
|
||||
:show-no-results="false"
|
||||
:close-on-select="false"
|
||||
:clear-search-on-select="false"
|
||||
:show-labels="false"
|
||||
placeholder="Choose versions..."
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-for="categoryList in Array.from(sortedCategories)"
|
||||
:key="categoryList[0]"
|
||||
class="categories"
|
||||
>
|
||||
<h2>{{ formatCategoryHeader(categoryList[0]) }}</h2>
|
||||
<div v-for="category in categoryList[1]" :key="category.name">
|
||||
<SearchFilter
|
||||
:active-filters="facets"
|
||||
:icon="category.icon"
|
||||
:display-name="formatCategory(category.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
||||
class="filter-checkbox"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="projectType !== 'datapack'" class="environment">
|
||||
<h2>Environments</h2>
|
||||
<SearchFilter
|
||||
:active-filters="selectedEnvironments"
|
||||
display-name="Client"
|
||||
facet-name="client"
|
||||
class="filter-checkbox"
|
||||
>
|
||||
<ClientIcon aria-hidden="true" />
|
||||
</SearchFilter>
|
||||
<SearchFilter
|
||||
:active-filters="selectedEnvironments"
|
||||
display-name="Server"
|
||||
facet-name="server"
|
||||
class="filter-checkbox"
|
||||
>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
</SearchFilter>
|
||||
</div>
|
||||
<div class="open-source">
|
||||
<h2>Open source</h2>
|
||||
<Checkbox v-model="onlyOpenSource" label="Open source only" class="filter-checkbox" />
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div ref="searchWrapper" class="search">
|
||||
<Promotion class="promotion" />
|
||||
<Card class="project-type-container">
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
</Card>
|
||||
<Card class="search-panel-container" :class="{ highlighted: showSearch }">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="query"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
:placeholder="`Search ${projectType}s...`"
|
||||
/>
|
||||
<Button @click="() => (query = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Sort by</span>
|
||||
<DropdownSelect
|
||||
v-model="sortType"
|
||||
name="Sort by"
|
||||
:options="sortTypes"
|
||||
:display-name="(option) => option?.display"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Show per page</span>
|
||||
<DropdownSelect
|
||||
v-model="maxResults"
|
||||
name="Max results"
|
||||
:options="[5, 10, 15, 20, 50, 100]"
|
||||
:default-value="maxResults"
|
||||
:model-value="maxResults"
|
||||
class="limit-dropdown"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Pagination :page="currentPage" :count="pageCount" class="pagination-before" />
|
||||
<SplashScreen v-if="loading" />
|
||||
<section v-else class="project-list display-mode--list instance-results" role="list">
|
||||
<Card v-for="project in 20" :key="project" class="search-card button-base">
|
||||
<div class="icon">
|
||||
<Avatar
|
||||
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png"
|
||||
size="md"
|
||||
class="search-icon"
|
||||
/>
|
||||
</div>
|
||||
<div class="content-wrapper">
|
||||
<div class="title joined-text">
|
||||
<h2>Example Modpack</h2>
|
||||
<span>by Modrinth</span>
|
||||
</div>
|
||||
<div class="description">
|
||||
A very cool project that does cool project things that you can your friends can do.
|
||||
</div>
|
||||
<div class="tags">
|
||||
<Categories
|
||||
:categories="
|
||||
categories
|
||||
.filter((cat) => cat.project_type === projectType)
|
||||
.slice(project / 2, project / 2 + 3)
|
||||
"
|
||||
:type="modpack"
|
||||
>
|
||||
<EnvironmentIndicator
|
||||
:type-only="true"
|
||||
:client-side="true"
|
||||
:server-side="true"
|
||||
type="modpack"
|
||||
:search="true"
|
||||
/>
|
||||
</Categories>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stats button-group">
|
||||
<div v-if="featured" class="badge">
|
||||
<StarIcon />
|
||||
Featured
|
||||
</div>
|
||||
<div class="badge">
|
||||
<DownloadIcon />
|
||||
{{ formatNumber(420) }}
|
||||
</div>
|
||||
<div class="badge">
|
||||
<HeartIcon />
|
||||
{{ formatNumber(69) }}
|
||||
</div>
|
||||
<div class="badge">
|
||||
<CalendarIcon />
|
||||
A minute ago
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</section>
|
||||
<pagination :page="currentPage" :count="pageCount" class="pagination-after" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<style lang="scss">
|
||||
.small-instance {
|
||||
min-height: unset !important;
|
||||
|
||||
.instance {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.small-instance_info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
justify-content: space-between;
|
||||
padding: 0.25rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-checkbox {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1rem;
|
||||
|
||||
svg {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
button.checkbox {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style lang="scss" scoped>
|
||||
.project-type-dropdown {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.promotion {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.project-type-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-panel-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 0 !important;
|
||||
min-height: min-content !important;
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
input {
|
||||
max-width: none !important;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.search-panel-container {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
padding: 1rem !important;
|
||||
white-space: nowrap;
|
||||
gap: 1rem;
|
||||
|
||||
.inline-option {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
|
||||
.sort-dropdown {
|
||||
max-width: 12.25rem;
|
||||
}
|
||||
|
||||
.limit-dropdown {
|
||||
width: 5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter-panel {
|
||||
button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-evenly;
|
||||
|
||||
svg {
|
||||
margin-right: 0.4rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
|
||||
.filter-panel {
|
||||
position: fixed;
|
||||
width: 20rem;
|
||||
padding: 1rem 0.5rem 1rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
overflow-y: auto;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
width: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--color-contrast);
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.16rem;
|
||||
}
|
||||
}
|
||||
|
||||
.search {
|
||||
scroll-behavior: smooth;
|
||||
margin: 0 1rem 0.5rem 20.5rem;
|
||||
width: calc(100% - 20.5rem);
|
||||
|
||||
.loading {
|
||||
margin: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-card {
|
||||
margin-bottom: 0;
|
||||
display: grid;
|
||||
grid-template-columns: 6rem auto 7rem;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
|
||||
&:active:not(&:disabled) {
|
||||
scale: 0.98 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.joined-text {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
flex-direction: row;
|
||||
column-gap: 0.5rem;
|
||||
align-items: baseline;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
h2 {
|
||||
margin-bottom: 0 !important;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.button-group {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.icon {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
align-self: center;
|
||||
height: 6rem;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
grid-column: 2 / 4;
|
||||
flex-direction: column;
|
||||
grid-row: 1;
|
||||
gap: 0.5rem;
|
||||
|
||||
.description {
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
}
|
||||
|
||||
.stats {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 2;
|
||||
justify-self: stretch;
|
||||
align-self: start;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,247 @@
|
||||
<script setup>
|
||||
import { Card, Slider, DropdownSelect, Toggle } from 'omorphia'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
|
||||
const pageOptions = ['Home', 'Library']
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="settings-page">
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Display</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="theme">
|
||||
<span class="label__title">Color theme</span>
|
||||
<span class="label__description">Change the global launcher color theme.</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
id="theme"
|
||||
name="Theme dropdown"
|
||||
:options="['Dark']"
|
||||
:default-value="'dark'"
|
||||
class="theme-dropdown"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="collapsed-nav">
|
||||
<span class="label__title">Collapsed navigation mode</span>
|
||||
<span class="label__description"
|
||||
>Change the style of the side navigation bar to a compact version.</span
|
||||
>
|
||||
</label>
|
||||
<Toggle id="collapsed-nav" :checked="false" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="advanced-rendering">
|
||||
<span class="label__title">Advanced rendering</span>
|
||||
<span class="label__description">
|
||||
Enables advanced rendering such as blur effects that may cause performance issues
|
||||
without hardware-accelerated rendering.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="advanced-rendering" :checked="true" />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="minimize-launcher">
|
||||
<span class="label__title">Minimize launcher</span>
|
||||
<span class="label__description"
|
||||
>Minimize the launcher when a Minecraft process starts.</span
|
||||
>
|
||||
</label>
|
||||
<Toggle id="minimize-launcher" :checked="false" />
|
||||
</div>
|
||||
<div class="opening-page">
|
||||
<label for="opening-page">
|
||||
<span class="label__title">Default landing page</span>
|
||||
<span class="label__description">Change the page to which the launcher opens on.</span>
|
||||
</label>
|
||||
<DropdownSelect
|
||||
id="opening-page"
|
||||
name="Opening page dropdown"
|
||||
:options="pageOptions"
|
||||
default-value="Home"
|
||||
class="opening-page"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Resource management</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="max-downloads">
|
||||
<span class="label__title">Maximum concurrent downloads</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can download at the same time. Set this to a
|
||||
lower value if you have a poor internet connection.</span
|
||||
>
|
||||
</label>
|
||||
<Slider id="max-downloads" :min="1" :max="10" :step="1" />
|
||||
</div>
|
||||
|
||||
<div class="adjacent-input">
|
||||
<label for="max-writes">
|
||||
<span class="label__title">Maximum concurrent writes</span>
|
||||
<span class="label__description"
|
||||
>The maximum amount of files the launcher can write to the disk at once. Set this to a
|
||||
lower value if you are frequently getting I/O errors.</span
|
||||
>
|
||||
</label>
|
||||
<Slider id="max-writes" :min="1" :max="50" :step="1" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Privacy</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="opt-out-analytics">
|
||||
<span class="label__title">Disable analytics</span>
|
||||
<span class="label__description">
|
||||
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||
customize your experience. Opting out will disable this data collection.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="opt-out-analytics" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Java settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<label for="java-17">
|
||||
<span class="label__title">Java 17 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-17" :version="17" model-value="" />
|
||||
<label for="java-8">
|
||||
<span class="label__title">Java 8 location</span>
|
||||
</label>
|
||||
<JavaSelector id="java-8" :version="8" model-value="" />
|
||||
<hr class="card-divider" />
|
||||
<label for="java-args">
|
||||
<span class="label__title">Java arguments</span>
|
||||
</label>
|
||||
<input
|
||||
id="java-args"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter java arguments..."
|
||||
/>
|
||||
<label for="env-vars">
|
||||
<span class="label__title">Environmental variables</span>
|
||||
</label>
|
||||
<input
|
||||
id="env-vars"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
class="installation-input"
|
||||
placeholder="Enter environmental variables..."
|
||||
/>
|
||||
<hr class="card-divider" />
|
||||
<div class="adjacent-input">
|
||||
<label for="max-memory">
|
||||
<span class="label__title">Java memory</span>
|
||||
<span class="label__description">
|
||||
The memory allocated to each instance when it is ran.
|
||||
</span>
|
||||
</label>
|
||||
<Slider id="max-memory" :min="256" :max="10256" :step="1" unit="mb" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Hooks</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="pre-launch">
|
||||
<span class="label__title">Pre launch</span>
|
||||
<span class="label__description"> Ran before the instance is launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="pre-launch"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter pre-launch command..."
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="wrapper">
|
||||
<span class="label__title">Wrapper</span>
|
||||
<span class="label__description"> Wrapper command for launching Minecraft. </span>
|
||||
</label>
|
||||
<input id="wrapper" autocomplete="off" type="text" placeholder="Enter wrapper command..." />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="post-exit">
|
||||
<span class="label__title">Post exit</span>
|
||||
<span class="label__description"> Ran after the game closes. </span>
|
||||
</label>
|
||||
<input
|
||||
id="post-exit"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
placeholder="Enter post-exit command..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Window size</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="width">
|
||||
<span class="label__title">Width</span>
|
||||
<span class="label__description"> The width of the game window when launched. </span>
|
||||
</label>
|
||||
<input id="width" autocomplete="off" type="number" placeholder="Enter width..." />
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="height">
|
||||
<span class="label__title">Height</span>
|
||||
<span class="label__description"> The height of the game window when launched. </span>
|
||||
</label>
|
||||
<input
|
||||
id="height"
|
||||
autocomplete="off"
|
||||
type="number"
|
||||
class="input"
|
||||
placeholder="Enter height..."
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.settings-page {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.installation-input {
|
||||
width: 100% !important;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.card-divider {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,197 @@
|
||||
<script setup>
|
||||
// the objects should look like this
|
||||
/*
|
||||
* {
|
||||
* "title": "Just Enough Items (JEI)",
|
||||
* "url": "https://modrinth.com/mod/jei",
|
||||
* }
|
||||
*/
|
||||
import { computed, ref } from 'vue'
|
||||
import { Button, ChevronRightIcon, ChevronLeftIcon } from 'omorphia'
|
||||
|
||||
const props = defineProps({
|
||||
logo: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
gallery: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const page = ref(1)
|
||||
const image = computed(() => props.gallery[page.value - 1])
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="gallery-image">
|
||||
<svg
|
||||
v-if="logo"
|
||||
class="app-logo"
|
||||
viewBox="0 0 1215 175"
|
||||
xml:space="preserve"
|
||||
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
|
||||
color="var(--color-contrast)"
|
||||
>
|
||||
<g transform="matrix(1,0,0,1,-3395.45,-1175)">
|
||||
<g transform="matrix(0.632704,0,0,0.161619,3395.45,1175)">
|
||||
<rect id="modrinth-app-logo" x="0" y="0" width="1920" height="1080" style="fill: none" />
|
||||
<g id="modrinth-app-logo1">
|
||||
<g transform="matrix(1.26353,0,0,3.23025,-514.757,-837.171)">
|
||||
<g transform="matrix(1.25087,0,0,1.91545,-240.84,-608.008)">
|
||||
<g transform="matrix(0.273005,0,0,0.273005,523.782,459.389)">
|
||||
<path
|
||||
d="M1053.02,205.51C1088.61,205.51 1117.29,215.61 1138,236.32C1158.72,257.57 1169.34,288.37 1169.34,329.8L1169.34,492.33L1102.94,492.33L1102.94,338.3C1102.94,313.34 1097.64,294.75 1086.48,282C1075.33,269.78 1059.93,263.4 1039.21,263.4C1016.91,263.4 998.84,270.85 985.56,285.19C972.29,300.06 965.38,321.3 965.38,348.39L965.38,492.33L898.98,492.33L898.98,338.3C898.98,313.34 893.68,294.75 882.52,282C871.37,269.78 855.96,263.4 835.25,263.4C812.41,263.4 794.88,270.85 781.6,285.19C768.33,299.53 761.42,320.77 761.42,348.39L761.42,492.33L695.02,492.33L695.02,208.7L758.23,208.7L758.23,244.82C768.86,232.07 782.13,222.52 798.07,215.61C814,208.71 831.53,205.51 851.18,205.51C872.43,205.51 891.55,209.23 908.02,217.2C924.48,225.7 937.23,237.38 946.79,252.79C958.48,237.91 973.35,226.23 991.94,217.73C1010.53,209.76 1030.71,205.51 1053.02,205.51ZM1382.86,496.05C1354.18,496.05 1328.16,489.68 1305.32,477.46C1282.84,465.588 1264.22,447.523 1251.67,425.41C1238.39,403.63 1232.02,378.67 1232.02,350.51C1232.02,322.37 1238.39,297.41 1251.67,275.63C1264.39,253.783 1282.98,235.93 1305.32,224.1C1328.16,211.89 1354.18,205.51 1382.86,205.51C1412.08,205.51 1438.1,211.89 1460.94,224.11C1483.78,236.32 1501.84,253.85 1515.12,275.63C1527.87,297.4 1534.24,322.37 1534.24,350.52C1534.24,378.67 1527.87,403.63 1515.12,425.41C1501.84,447.71 1483.78,465.24 1460.94,477.46C1438.1,489.68 1412.08,496.06 1382.86,496.06L1382.86,496.05ZM1382.86,439.22C1407.3,439.22 1427.48,431.25 1443.41,414.79C1459.35,398.32 1467.31,377.07 1467.31,350.52C1467.31,323.96 1459.35,302.72 1443.41,286.25C1427.48,269.78 1407.3,261.82 1382.86,261.82C1358.43,261.82 1338.25,269.78 1322.84,286.25C1306.91,302.71 1298.94,323.96 1298.94,350.52C1298.94,377.07 1306.91,398.32 1322.84,414.79C1338.24,431.25 1358.44,439.22 1382.86,439.22ZM1874.18,98.22L1874.18,492.33L1810.44,492.33L1810.44,455.68C1799.56,468.819 1785.73,479.196 1770.07,485.96C1753.61,492.86 1736.07,496.06 1716.42,496.06C1689.34,496.06 1664.9,490.21 1643.12,477.99C1621.35,465.78 1603.82,448.78 1591.6,426.47C1579.39,404.69 1573.01,379.2 1573.01,350.52C1573.01,321.84 1579.39,296.34 1591.61,274.56C1603.82,252.79 1621.35,235.79 1643.13,223.57C1664.9,211.36 1689.33,205.51 1716.43,205.51C1735.02,205.51 1752.54,208.71 1767.95,215.07C1783.28,221.396 1796.92,231.222 1807.78,243.76L1807.78,98.22L1874.18,98.22ZM1724.39,439.22C1740.33,439.22 1754.67,435.5 1767.42,428.06C1780.16,421.16 1790.25,410.54 1797.69,397.26C1805.13,383.98 1808.84,368.05 1808.84,350.52C1808.84,332.99 1805.13,317.06 1797.69,303.78C1790.25,290.5 1780.16,279.88 1767.42,272.44C1754.67,265.54 1740.32,261.82 1724.39,261.82C1708.46,261.82 1694.12,265.53 1681.37,272.44C1668.62,279.87 1658.53,290.5 1651.09,303.78C1643.66,317.06 1639.94,332.98 1639.94,350.52C1639.94,368.05 1643.66,383.98 1651.09,397.26C1658.53,410.54 1668.62,421.16 1681.37,428.06C1694.12,435.5 1708.46,439.22 1724.39,439.22ZM2022.9,250.13C2042.02,220.39 2075.48,205.51 2123.82,205.51L2123.82,268.72C2118.75,267.715 2113.59,267.18 2108.42,267.12C2082.39,267.12 2062.2,274.56 2047.86,289.44C2033.52,304.84 2026.08,326.62 2026.08,354.77L2026.08,492.33L1959.69,492.33L1959.69,208.7L2022.89,208.7L2022.89,250.13L2022.9,250.13ZM2178.52,208.7L2244.92,208.7L2244.92,492.33L2178.52,492.33L2178.52,208.7ZM2211.99,161.96C2199.77,161.96 2189.68,158.24 2181.71,150.28C2173.8,143.08 2169.35,132.817 2169.5,122.12C2169.5,110.97 2173.75,101.41 2181.71,93.44C2189.68,86.01 2199.77,82.29 2211.99,82.29C2224.2,82.29 2234.29,86.01 2242.26,92.91C2250.23,100.35 2254.48,109.38 2254.48,120.53C2254.48,132.22 2250.76,141.78 2242.79,149.74C2234.83,157.71 2224.2,161.96 2211.99,161.96ZM2491.37,205.51C2526.96,205.51 2555.64,216.14 2577.42,236.85C2599.2,257.57 2609.82,288.9 2609.82,329.8L2609.82,492.33L2543.42,492.33L2543.42,338.3C2543.42,313.34 2537.58,294.75 2525.9,282C2514.21,269.78 2497.75,263.4 2475.97,263.4C2451.54,263.4 2432.42,270.85 2418.07,285.19C2403.73,300.06 2396.83,321.3 2396.83,348.92L2396.83,492.33L2330.43,492.33L2330.43,208.7L2393.64,208.7L2393.64,245.35C2404.8,232.07 2418.61,222.51 2435.07,215.61C2451.54,208.71 2470.66,205.51 2491.37,205.51ZM2863.18,476.93C2854.87,483.572 2845.29,488.451 2835.03,491.27C2823.44,494.492 2811.46,496.101 2799.43,496.05C2768.1,496.05 2744.2,488.08 2727.2,471.62C2710.2,455.15 2701.7,431.78 2701.7,400.45L2701.7,263.94L2654.97,263.94L2654.97,210.83L2701.71,210.83L2701.71,146.03L2768.11,146.03L2768.11,210.83L2844.06,210.83L2844.06,263.94L2768.1,263.94L2768.1,398.85C2768.1,412.66 2771.29,423.28 2778.2,430.19C2785.1,437.63 2794.66,441.34 2807.4,441.34C2822.28,441.34 2834.5,437.63 2844.59,429.66L2863.18,476.93ZM3077.23,205.51C3112.82,205.51 3141.5,216.14 3163.28,236.85C3185.05,257.57 3195.68,288.9 3195.68,329.8L3195.68,492.33L3129.28,492.33L3129.28,338.3C3129.28,313.34 3123.44,294.75 3111.75,282C3100.07,269.78 3083.6,263.4 3061.83,263.4C3037.39,263.4 3018.27,270.85 3003.93,285.19C2989.59,300.06 2982.69,321.3 2982.69,348.92L2982.69,492.33L2916.29,492.33L2916.29,98.23L2982.69,98.23L2982.69,241.63Line truncated
|
||||
style="fill: currentColor; fill-rule: nonzero"
|
||||
/>
|
||||
</g>
|
||||
<g transform="matrix(0.273005,0,0,0.273005,523.782,459.389)">
|
||||
<path
|
||||
d="M29,424.4L217.2,311.45L200.05,265.97L253.8,210.76L321.73,196.12L341.4,220.33L310.08,252.05L282.78,260.65L263.26,280.7L272.82,307.3L292.22,327.9L319.58,320.62L339.05,299.24L381.56,285.77L394.23,314.27L350.36,368.05L276.86,391.32L243.89,354.62L55.06,467.94C46.1,456.41 35.67,440.08 29,424.4ZM572.03,194.15L422.53,234.47C430.77,256.39 433.48,269.27 435.76,283.47L584.99,243.21C582.61,227.27 578.34,211.04 572.03,194.15Z"
|
||||
style="fill: var(--color-brand)"
|
||||
/>
|
||||
<path
|
||||
d="M51.28,316.13C61.87,441.13 166.82,539.43 294.55,539.43C391.06,539.43 474.57,483.31 514.18,401.97L562.79,418.8C516.01,520.14 413.44,590.55 294.55,590.55C138.6,590.55 10.71,469.38 0,316.13L51.28,316.13ZM0.78,265.24C15.86,116.36 141.73,0 294.56,0C457.53,0 589.84,132.31 589.84,295.28C589.84,321.42 586.44,346.77 580.04,370.91L531.56,354.13C536.324,334.876 538.725,315.114 538.71,295.28C538.71,160.53 429.31,51.13 294.56,51.13C169.98,51.13 67.07,144.63 52.24,265.24L0.78,265.24Z"
|
||||
style="fill: var(--color-brand)"
|
||||
/>
|
||||
<path
|
||||
d="M293.77,153.17C215.28,153.24 151.57,217 151.57,295.51C151.57,374.07 215.36,437.85 293.92,437.85C297.9,437.85 301.85,437.69 305.75,437.36L319.97,487.12C311.335,488.282 302.632,488.864 293.92,488.86C187.2,488.86 100.56,402.22 100.56,295.51C100.56,188.79 187.2,102.16 293.92,102.16C296.56,102.16 299.2,102.21 301.82,102.32L293.77,153.17ZM351.97,111.04C430.36,135.71 487.27,209.02 487.27,295.51C487.27,375.58 438.5,444.34 369.07,473.69L354.9,424.14C402.98,401.29 436.26,352.25 436.26,295.51C436.26,234.52 397.82,182.44 343.87,162.19L351.97,111.04Z"
|
||||
style="fill: var(--color-brand)"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g transform="matrix(1.25498,0,0,1.92174,1126.78,-2077.58)">
|
||||
<path
|
||||
d="M411.597,1355.36L411.597,1339.8L410.589,1336.49L410.589,1309.28C410.589,1304 409.005,1299.89 405.837,1296.96C402.669,1294.04 397.869,1292.57 391.437,1292.57C387.117,1292.57 382.869,1293.24 378.693,1294.59C374.517,1295.93 370.989,1297.8 368.109,1300.2L361.053,1287.1C365.181,1283.93 370.101,1281.56 375.813,1279.97C381.525,1278.39 387.453,1277.6 393.597,1277.6C404.733,1277.6 413.349,1280.26 419.445,1285.59C425.541,1290.91 428.589,1299.15 428.589,1310.28L428.589,1355.36L411.597,1355.36ZM387.405,1356.36C381.645,1356.36 376.605,1355.38 372.285,1353.41C367.965,1351.44 364.629,1348.71 362.277,1345.2C359.925,1341.7 358.749,1337.74 358.749,1333.32C358.749,1329 359.781,1325.12 361.845,1321.66C363.909,1318.2 367.293,1315.47 371.997,1313.45C376.701,1311.43 382.941,1310.43 390.717,1310.43L413.037,1310.43L413.037,1322.38L392.013,1322.38C385.869,1322.38 381.741,1323.36 379.629,1325.33C377.517,1327.3 376.461,1329.72 376.461,1332.6C376.461,1335.87 377.757,1338.46 380.349,1340.38C382.941,1342.3 386.541,1343.26 391.149,1343.26C395.565,1343.26 399.525,1342.25 403.029,1340.24C406.533,1338.22 409.053,1335.24 410.589,1331.31L413.613,1342.11C411.885,1346.62 408.789,1350.12 404.325,1352.62C399.861,1355.12 394.221,1356.36 387.405,1356.36Z"
|
||||
style="fill: var(--color-brand); fill-rule: nonzero"
|
||||
/>
|
||||
<path
|
||||
d="M494.253,1356.36C488.013,1356.36 482.325,1354.92 477.189,1352.04C472.053,1349.16 467.973,1344.82 464.949,1339.01C461.925,1333.2 460.413,1325.84 460.413,1316.91C460.413,1307.88 461.877,1300.49 464.805,1294.73C467.733,1288.97 471.741,1284.67 476.829,1281.84C481.917,1279.01 487.725,1277.6 494.253,1277.6C501.837,1277.6 508.533,1279.23 514.341,1282.49C520.149,1285.76 524.757,1290.32 528.165,1296.17C531.573,1302.03 533.277,1308.94 533.277,1316.91C533.277,1324.88 531.573,1331.81 528.165,1337.71C524.757,1343.62 520.149,1348.2 514.341,1351.47C508.533,1354.73 501.837,1356.36 494.253,1356.36ZM451.629,1383.29L451.629,1278.46L468.765,1278.46L468.765,1296.6L468.189,1317.05L469.629,1337.5L469.629,1383.29L451.629,1383.29ZM492.237,1340.95C496.557,1340.95 500.421,1340 503.829,1338.08C507.237,1336.15 509.949,1333.37 511.965,1329.72C513.981,1326.08 514.989,1321.8 514.989,1316.91C514.989,1311.91 513.981,1307.62 511.965,1304.02C509.949,1300.42 507.237,1297.66 503.829,1295.74C500.421,1293.82 496.557,1292.86 492.237,1292.86C487.917,1292.86 484.029,1293.82 480.573,1295.74C477.117,1297.66 474.381,1300.42 472.365,1304.02C470.349,1307.62 469.341,1311.91 469.341,1316.91C469.341,1321.8 470.349,1326.08 472.365,1329.72C474.381,1333.37 477.117,1336.15 480.573,1338.08C484.029,1340 487.917,1340.95 492.237,1340.95Z"
|
||||
style="fill: var(--color-brand); fill-rule: nonzero"
|
||||
/>
|
||||
<path
|
||||
d="M593.037,1356.36C586.797,1356.36 581.109,1354.92 575.973,1352.04C570.837,1349.16 566.757,1344.82 563.733,1339.01C560.709,1333.2 559.197,1325.84 559.197,1316.91C559.197,1307.88 560.661,1300.49 563.589,1294.73C566.517,1288.97 570.525,1284.67 575.613,1281.84C580.701,1279.01 586.509,1277.6 593.037,1277.6C600.621,1277.6 607.317,1279.23 613.125,1282.49C618.933,1285.76 623.541,1290.32 626.949,1296.17C630.357,1302.03 632.061,1308.94 632.061,1316.91C632.061,1324.88 630.357,1331.81 626.949,1337.71C623.541,1343.62 618.933,1348.2 613.125,1351.47C607.317,1354.73 600.621,1356.36 593.037,1356.36ZM550.413,1383.29L550.413,1278.46L567.549,1278.46L567.549,1296.6L566.973,1317.05L568.413,1337.5L568.413,1383.29L550.413,1383.29ZM591.021,1340.95C595.341,1340.95 599.205,1340 602.613,1338.08C606.021,1336.15 608.733,1333.37 610.749,1329.72C612.765,1326.08 613.773,1321.8 613.773,1316.91C613.773,1311.91 612.765,1307.62 610.749,1304.02C608.733,1300.42 606.021,1297.66 602.613,1295.74C599.205,1293.82 595.341,1292.86 591.021,1292.86C586.701,1292.86 582.813,1293.82 579.357,1295.74C575.901,1297.66 573.165,1300.42 571.149,1304.02C569.133,1307.62 568.125,1311.91 568.125,1316.91C568.125,1321.8 569.133,1326.08 571.149,1329.72C573.165,1333.37 575.901,1336.15 579.357,1338.08C582.813,1340 586.701,1340.95 591.021,1340.95Z"
|
||||
style="fill: var(--color-brand); fill-rule: nonzero"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="image-wrapper">
|
||||
<img :src="image.url" :alt="image.title" />
|
||||
<div class="text">
|
||||
<h3>{{ image.title }}</h3>
|
||||
<p>
|
||||
{{ image.subtitle }}
|
||||
</p>
|
||||
</div>
|
||||
<Button :disabled="page === 1" class="left-button" icon-only @click="page--">
|
||||
<ChevronLeftIcon />
|
||||
</Button>
|
||||
<Button :disabled="page === 2" class="right-button" icon-only @click="page++">
|
||||
<ChevronRightIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<slot />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.gallery-image {
|
||||
display: flex;
|
||||
position: relative;
|
||||
gap: var(--gap-md);
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: var(--gap-xl);
|
||||
max-height: 100vh;
|
||||
max-width: 100vh;
|
||||
margin: auto;
|
||||
|
||||
.image-wrapper {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.text {
|
||||
position: absolute;
|
||||
bottom: calc(var(--gap-md) * 3);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
text-align: center;
|
||||
border-radius: var(--gap-lg);
|
||||
padding: var(--gap-sm) var(--gap-md);
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
//add blur
|
||||
backdrop-filter: blur(5px);
|
||||
-webkit-backdrop-filter: blur(5px);
|
||||
color: var(--color-base);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.left-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 0;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.right-button {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
transform: translateY(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.page-indicator {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
.circle {
|
||||
width: 0.75rem;
|
||||
height: 0.75rem;
|
||||
border-radius: 50%;
|
||||
display: inline-block;
|
||||
margin-right: 0.5rem;
|
||||
background-color: var(--color-base);
|
||||
transition: all 0.3s ease-in-out;
|
||||
cursor: pointer !important;
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-brand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
width: 50rem;
|
||||
height: auto;
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,268 @@
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Checkbox,
|
||||
Chips,
|
||||
XIcon,
|
||||
FolderOpenIcon,
|
||||
FolderSearchIcon,
|
||||
UpdatedIcon,
|
||||
} from 'omorphia'
|
||||
import { ref } from 'vue'
|
||||
import { get_importable_instances, import_instance } from '@/helpers/import.js'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { handleError } from '@/store/state.js'
|
||||
|
||||
const props = defineProps({
|
||||
nextPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
prevPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const profiles = ref(
|
||||
new Map([
|
||||
['MultiMC', []],
|
||||
['GDLauncher', []],
|
||||
['ATLauncher', []],
|
||||
['Curseforge', []],
|
||||
['PrismLauncher', []],
|
||||
])
|
||||
)
|
||||
|
||||
const loading = ref(false)
|
||||
|
||||
const selectedProfileType = ref('MultiMC')
|
||||
const profileOptions = ref([
|
||||
{ name: 'MultiMC', path: '' },
|
||||
{ name: 'GDLauncher', path: '' },
|
||||
{ name: 'ATLauncher', path: '' },
|
||||
{ name: 'Curseforge', path: '' },
|
||||
{ name: 'PrismLauncher', path: '' },
|
||||
])
|
||||
|
||||
const selectLauncherPath = async () => {
|
||||
selectedProfileType.value.path = await open({ multiple: false, directory: true })
|
||||
|
||||
if (selectedProfileType.value.path) {
|
||||
await reload()
|
||||
}
|
||||
}
|
||||
|
||||
const reload = async () => {
|
||||
const instances = await get_importable_instances(
|
||||
selectedProfileType.value.name,
|
||||
selectedProfileType.value.path
|
||||
).catch(handleError)
|
||||
profiles.value.set(
|
||||
selectedProfileType.value.name,
|
||||
instances.map((name) => ({ name, selected: false }))
|
||||
)
|
||||
}
|
||||
|
||||
const setPath = () => {
|
||||
profileOptions.value.find((profile) => profile.name === selectedProfileType.value.name).path =
|
||||
selectedProfileType.value.path
|
||||
}
|
||||
|
||||
const next = async () => {
|
||||
loading.value = true
|
||||
for (const launcher of Array.from(profiles.value.entries()).map(([launcher, profiles]) => ({
|
||||
launcher,
|
||||
path: profileOptions.value.find((option) => option.name === launcher).path,
|
||||
profiles,
|
||||
}))) {
|
||||
for (const profile of launcher.profiles.filter((profile) => profile.selected)) {
|
||||
await import_instance(launcher.launcher, launcher.path, profile.name)
|
||||
.catch(handleError)
|
||||
.then(() => console.log(`Successfully Imported ${profile.name} from ${launcher.launcher}`))
|
||||
}
|
||||
}
|
||||
loading.value = false
|
||||
props.nextPage()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<h2>Importing external profiles</h2>
|
||||
<Chips
|
||||
v-model="selectedProfileType"
|
||||
:items="profileOptions"
|
||||
:format-label="(profile) => profile?.name"
|
||||
/>
|
||||
<div class="path-selection">
|
||||
<h3>{{ selectedProfileType.name }} path</h3>
|
||||
<div class="path-input">
|
||||
<div class="iconified-input">
|
||||
<FolderOpenIcon />
|
||||
<input
|
||||
v-model="selectedProfileType.path"
|
||||
type="text"
|
||||
placeholder="Path to launcher"
|
||||
@change="setPath"
|
||||
/>
|
||||
<Button @click="() => (selectedLauncherPath = '')">
|
||||
<XIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<Button icon-only @click="selectLauncherPath">
|
||||
<FolderSearchIcon />
|
||||
</Button>
|
||||
<Button icon-only @click="reload">
|
||||
<UpdatedIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="table">
|
||||
<div class="table-head table-row">
|
||||
<div class="toggle-all table-cell">
|
||||
<Checkbox
|
||||
class="select-checkbox"
|
||||
:model-value="profiles.get(selectedProfileType.name)?.every((child) => child.selected)"
|
||||
@update:model-value="
|
||||
(newValue) =>
|
||||
profiles
|
||||
.get(selectedProfileType.name)
|
||||
?.forEach((child) => (child.selected = newValue))
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="name-cell table-cell">Profile name</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
profiles.get(selectedProfileType.name) &&
|
||||
profiles.get(selectedProfileType.name).length > 0
|
||||
"
|
||||
class="table-content"
|
||||
>
|
||||
<div
|
||||
v-for="(profile, index) in profiles.get(selectedProfileType.name)"
|
||||
:key="index"
|
||||
class="table-row"
|
||||
>
|
||||
<div class="checkbox-cell table-cell">
|
||||
<Checkbox v-model="profile.selected" class="select-checkbox" />
|
||||
</div>
|
||||
<div class="name-cell table-cell">
|
||||
{{ profile.name }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="table-content empty">No profiles found</div>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" @click="prevPage"> Back </Button>
|
||||
<Button
|
||||
:disabled="
|
||||
loading ||
|
||||
!Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.some((e) => e.selected)
|
||||
"
|
||||
color="primary"
|
||||
@click="next"
|
||||
>
|
||||
{{
|
||||
loading
|
||||
? 'Importing...'
|
||||
: Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.some((e) => e.selected)
|
||||
? `Import ${
|
||||
Array.from(profiles.values())
|
||||
.flatMap((e) => e)
|
||||
.filter((e) => e.selected).length
|
||||
} profiles`
|
||||
: 'Select profiles to import'
|
||||
}}
|
||||
</Button>
|
||||
<Button class="transparent" @click="nextPage"> Next </Button>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
padding: var(--gap-xl);
|
||||
min-height: unset;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.path-selection {
|
||||
padding: var(--gap-xl);
|
||||
background-color: var(--color-bg);
|
||||
border-radius: var(--radius-lg);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: var(--gap-md);
|
||||
gap: var(--gap-md);
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.path-input {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
:deep(input) {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table {
|
||||
border: 1px solid var(--color-bg);
|
||||
margin-bottom: var(--gap-md);
|
||||
}
|
||||
|
||||
.table-row {
|
||||
grid-template-columns: min-content auto;
|
||||
}
|
||||
|
||||
.table-content {
|
||||
max-height: calc(5 * (18px + 2rem));
|
||||
height: calc(5 * (18px + 2rem));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.select-checkbox {
|
||||
button.checkbox {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-sm) 0;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
font-weight: bolder;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,219 @@
|
||||
<script setup>
|
||||
import { Button, LogInIcon, Modal, ClipboardCopyIcon, GlobeIcon, Card } from 'omorphia'
|
||||
import { authenticate_await_completion, authenticate_begin_flow } from '@/helpers/auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { get, set } from '@/helpers/settings.js'
|
||||
import { ref } from 'vue'
|
||||
import QrcodeVue from 'qrcode.vue'
|
||||
|
||||
const loginUrl = ref(null)
|
||||
const loginModal = ref()
|
||||
|
||||
const props = defineProps({
|
||||
nextPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
prevPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
async function login() {
|
||||
const url = await authenticate_begin_flow().catch(handleError)
|
||||
loginUrl.value = url
|
||||
|
||||
await window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: url,
|
||||
},
|
||||
})
|
||||
|
||||
const loggedIn = await authenticate_await_completion().catch(handleError)
|
||||
loginModal.value.hide()
|
||||
props.nextPage()
|
||||
const settings = await get().catch(handleError)
|
||||
settings.default_user = loggedIn.id
|
||||
await set(settings).catch(handleError)
|
||||
await mixpanel.track('AccountLogIn')
|
||||
}
|
||||
|
||||
const openUrl = async () => {
|
||||
await window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: loginUrl.value,
|
||||
},
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-card">
|
||||
<img
|
||||
src="https://cdn.discordapp.com/attachments/1115781524047020123/1119319322028949544/Modrinth_icon.png"
|
||||
class="logo"
|
||||
alt="Minecraft art"
|
||||
/>
|
||||
<Card class="logging-in">
|
||||
<h2>Sign into Minecraft</h2>
|
||||
<p>
|
||||
Sign in with your Microsoft account to launch Minecraft with your mods and modpacks. If you
|
||||
don't have a Minecraft account, you can purchase the game on the
|
||||
<a
|
||||
href="https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc"
|
||||
class="link"
|
||||
>
|
||||
Minecraft website
|
||||
</a>
|
||||
</p>
|
||||
<div class="action-row">
|
||||
<Button class="transparent" large @click="prevPage"> Back </Button>
|
||||
<div class="sign-in-pair">
|
||||
<Button color="primary" large @click="login">
|
||||
<LogInIcon v-if="!finalizedLogin" />
|
||||
{{ finalizedLogin ? 'Next' : 'Sign in' }}
|
||||
</Button>
|
||||
<Button v-if="loginUrl" class="transparent" @click="loginModal.show()">
|
||||
Browser didn't open?
|
||||
</Button>
|
||||
</div>
|
||||
<Button class="transparent" large @click="nextPage"> Next </Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
<Modal ref="loginModal" header="Signing in">
|
||||
<div class="modal-body">
|
||||
<QrcodeVue :value="loginUrl" class="qr-code" margin="3" size="160" />
|
||||
<div class="modal-text">
|
||||
<p>
|
||||
Sign into Microsoft with your browser. If your browser didn't open, you can copy and open
|
||||
the link below, or scan the QR code with your device.
|
||||
</p>
|
||||
<div class="iconified-input">
|
||||
<LogInIcon />
|
||||
<input type="text" :value="loginUrl" readonly />
|
||||
<Button
|
||||
v-tooltip="'Copy link'"
|
||||
icon-only
|
||||
color="raised"
|
||||
@click="() => navigator.clipboard.writeText(loginUrl)"
|
||||
>
|
||||
<ClipboardCopyIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button @click="openUrl">
|
||||
<GlobeIcon />
|
||||
Open link
|
||||
</Button>
|
||||
<Button class="transparent" @click="loginModal.hide"> Cancel </Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: auto;
|
||||
padding: var(--gap-lg);
|
||||
width: 30rem;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logging-in {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
vertical-align: center;
|
||||
gap: var(--gap-md);
|
||||
background-color: var(--color-raised-bg);
|
||||
width: 100%;
|
||||
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||||
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
color: var(--color-blue);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.action-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
margin-top: var(--gap-md);
|
||||
|
||||
.transparent {
|
||||
padding: 0 var(--gap-md);
|
||||
}
|
||||
}
|
||||
|
||||
.qr-code {
|
||||
background-color: white !important;
|
||||
border-radius: var(--radius-md);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: var(--gap-lg);
|
||||
align-items: center;
|
||||
padding: var(--gap-lg);
|
||||
|
||||
.modal-text {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sticker {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
height: auto;
|
||||
margin-bottom: var(--gap-lg);
|
||||
}
|
||||
|
||||
.sign-in-pair {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-sm);
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,182 @@
|
||||
<script setup>
|
||||
import { Button, Card, UserIcon, LockIcon } from 'omorphia'
|
||||
import {
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
MicrosoftIcon,
|
||||
GoogleIcon,
|
||||
SteamIcon,
|
||||
GitLabIcon,
|
||||
} from '@/assets/external'
|
||||
|
||||
defineProps({
|
||||
nextPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
prevPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<h1>Login to Modrinth</h1>
|
||||
<div class="button-grid">
|
||||
<Button class="discord" large>
|
||||
<DiscordIcon />
|
||||
Discord
|
||||
</Button>
|
||||
<Button class="github" large>
|
||||
<GithubIcon />
|
||||
Github
|
||||
</Button>
|
||||
<Button class="white" large>
|
||||
<MicrosoftIcon />
|
||||
Microsoft
|
||||
</Button>
|
||||
<Button class="google" large>
|
||||
<GoogleIcon />
|
||||
Google
|
||||
</Button>
|
||||
<Button class="white" large>
|
||||
<SteamIcon />
|
||||
Steam
|
||||
</Button>
|
||||
<Button class="gitlab" large>
|
||||
<GitLabIcon />
|
||||
GitLab
|
||||
</Button>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<hr />
|
||||
<p>Or</p>
|
||||
</div>
|
||||
<div class="iconified-input username">
|
||||
<UserIcon />
|
||||
<input type="text" placeholder="Email or username" />
|
||||
</div>
|
||||
<div class="iconified-input">
|
||||
<LockIcon />
|
||||
<input type="password" placeholder="Password" />
|
||||
</div>
|
||||
<div class="link-row">
|
||||
<a class="button-base"> Create account </a>
|
||||
<a class="button-base"> Forgot password? </a>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" large @click="prevPage"> Back </Button>
|
||||
<Button color="primary" large> Login </Button>
|
||||
<Button class="transparent" large @click="nextPage"> Next </Button>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
width: 25rem;
|
||||
}
|
||||
|
||||
.button-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: var(--gap-md);
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.discord {
|
||||
background-color: #5865f2;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.github {
|
||||
background-color: #8740f1;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.white {
|
||||
background-color: var(--color-contrast);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.google {
|
||||
background-color: #4285f4;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
.gitlab {
|
||||
background-color: #fc6d26;
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
.divider {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
p {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: var(--color-raised-bg);
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
width: 100%;
|
||||
border-top: 2px solid var(--color-button-bg);
|
||||
}
|
||||
}
|
||||
|
||||
.iconified-input {
|
||||
width: 100%;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
flex-basis: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.username {
|
||||
margin-bottom: var(--gap-sm);
|
||||
}
|
||||
|
||||
.link-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: var(--gap-md) 0;
|
||||
|
||||
a {
|
||||
color: var(--color-blue);
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.btn {
|
||||
flex-basis: auto;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-md) 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<Modal
|
||||
ref="onboardingModal"
|
||||
:header="['Getting started', 'Sign into Minecraft', 'Install java'][page - 1]"
|
||||
:closable="false"
|
||||
>
|
||||
<Modal ref="onboardingModal" :closable="false">
|
||||
<div class="modal-body">
|
||||
<div v-if="page === 1" key="1" class="content">
|
||||
<svg
|
||||
@@ -174,6 +170,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
finish: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
async function fetchSettings() {
|
||||
@@ -210,16 +210,13 @@ watch([settings, settings.value], async () => {
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
if (!settings.value.onboarded) {
|
||||
onboardingModal.value.show()
|
||||
}
|
||||
onboardingModal.value.show()
|
||||
})
|
||||
|
||||
async function pageTurn() {
|
||||
if (page.value === 3) {
|
||||
settings.value.onboarded = true
|
||||
onboardingModal.value.hide()
|
||||
mixpanel.track('OnboardingFinish')
|
||||
props.finish()
|
||||
return
|
||||
}
|
||||
page.value++
|
||||
@@ -267,7 +264,7 @@ onBeforeUnmount(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
padding: var(--gap-lg);
|
||||
padding: var(--gap-xl);
|
||||
|
||||
height: min(70vh, 450px);
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
<script setup>
|
||||
import {
|
||||
Button,
|
||||
HomeIcon,
|
||||
SearchIcon,
|
||||
LibraryIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
Notifications,
|
||||
LogOutIcon,
|
||||
} from 'omorphia'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
|
||||
import FakeAppBar from '@/components/ui/tutorial/FakeAppBar.vue'
|
||||
import FakeAccountsCard from '@/components/ui/tutorial/FakeAccountsCard.vue'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator.js'
|
||||
import FakeSearch from '@/components/ui/tutorial/FakeSearch.vue'
|
||||
import FakeGridDisplay from '@/components/ui/tutorial/FakeGridDisplay.vue'
|
||||
import FakeRowDisplay from '@/components/ui/tutorial/FakeRowDisplay.vue'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { window } from '@tauri-apps/api'
|
||||
import TutorialTip from '@/components/ui/tutorial/TutorialTip.vue'
|
||||
import FakeSettings from '@/components/ui/tutorial/FakeSettings.vue'
|
||||
import { get, set } from '@/helpers/settings.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import GalleryImage from '@/components/ui/tutorial/GalleryImage.vue'
|
||||
import LoginCard from '@/components/ui/tutorial/LoginCard.vue'
|
||||
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
|
||||
import { auto_install_java, get_jre } from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import ImportingCard from '@/components/ui/tutorial/ImportingCard.vue'
|
||||
// import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
|
||||
import PreImportScreen from '@/components/ui/tutorial/PreImportScreen.vue'
|
||||
|
||||
const phase = ref(0)
|
||||
const page = ref(1)
|
||||
|
||||
const props = defineProps({
|
||||
finish: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
})
|
||||
|
||||
const nextPhase = () => {
|
||||
phase.value++
|
||||
mixpanel.track('TutorialPhase', { page: phase.value })
|
||||
}
|
||||
|
||||
const prevPhase = () => {
|
||||
phase.value--
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
page.value++
|
||||
mixpanel.track('OnboardingPage', { page: page.value })
|
||||
}
|
||||
|
||||
const endOnboarding = () => {
|
||||
nextPhase()
|
||||
}
|
||||
|
||||
const prevPage = () => {
|
||||
page.value--
|
||||
}
|
||||
|
||||
const finishOnboarding = async () => {
|
||||
mixpanel.track('OnboardingFinish')
|
||||
const settings = await get()
|
||||
settings.onboarded_new = true
|
||||
await set(settings)
|
||||
props.finish()
|
||||
}
|
||||
|
||||
async function fetchSettings() {
|
||||
const fetchSettings = await get().catch(handleError)
|
||||
|
||||
if (!fetchSettings.java_globals.JAVA_17) {
|
||||
const path = await auto_install_java(17).catch(handleError)
|
||||
fetchSettings.java_globals.JAVA_17 = await get_jre(path).catch(handleError)
|
||||
}
|
||||
|
||||
if (!fetchSettings.java_globals.JAVA_8) {
|
||||
const path = await auto_install_java(8).catch(handleError)
|
||||
fetchSettings.java_globals.JAVA_8 = await get_jre(path).catch(handleError)
|
||||
}
|
||||
|
||||
await set(fetchSettings).catch(handleError)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchSettings()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="phase === 0" class="onboarding">
|
||||
<StickyTitleBar />
|
||||
<GalleryImage
|
||||
v-if="page === 1"
|
||||
:gallery="[
|
||||
{
|
||||
url: 'https://cdn.discordapp.com/attachments/817413688771608587/1131109353928265809/Screenshot_2023-07-15_at_4.16.18_PM.png',
|
||||
title: 'Discovery',
|
||||
subtitle: 'See the latest and greatest mods and modpacks to play with from Modrinth',
|
||||
},
|
||||
{
|
||||
url: 'https://cdn.discordapp.com/attachments/817413688771608587/1131109354238640238/Screenshot_2023-07-15_at_4.17.43_PM.png',
|
||||
title: 'Profile Management',
|
||||
subtitle:
|
||||
'Play, manage and search through all the amazing profiles downloaded on your computer at any time, even offline!',
|
||||
},
|
||||
]"
|
||||
logo
|
||||
>
|
||||
<Button color="primary" @click="nextPage"> Get started </Button>
|
||||
</GalleryImage>
|
||||
<LoginCard v-else-if="page === 2" :next-page="nextPage" :prev-page="prevPage" />
|
||||
<!-- <ModrinthLoginScreen v-else-if="page === 3" :next-page="nextPage" :prev-page="prevPage" />-->
|
||||
<PreImportScreen
|
||||
v-else-if="page === 3"
|
||||
:next-page="endOnboarding"
|
||||
:prev-page="prevPage"
|
||||
:import-page="nextPage"
|
||||
/>
|
||||
<ImportingCard v-else-if="page === 4" :next-page="endOnboarding" :prev-page="prevPage" />
|
||||
</div>
|
||||
<div v-else class="container">
|
||||
<StickyTitleBar v-if="phase === 9" />
|
||||
<div v-if="phase < 9" class="nav-container">
|
||||
<div class="nav-section">
|
||||
<FakeAccountsCard :show-demo="phase === 3">
|
||||
<TutorialTip
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Signing in"
|
||||
description="The Modrinth App uses your Microsoft account to allow you to launch Minecraft. You can sign in with your Microsoft account here, and switch between multiple accounts."
|
||||
/>
|
||||
</FakeAccountsCard>
|
||||
<div class="pages-list">
|
||||
<div class="btn icon-only" :class="{ active: phase < 4 }">
|
||||
<HomeIcon />
|
||||
</div>
|
||||
<div class="btn icon-only" :class="{ active: phase === 4 || phase === 5 }">
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<div
|
||||
class="btn icon-only"
|
||||
:class="{
|
||||
active: phase === 6 || phase === 7,
|
||||
highlighted: phase === 6,
|
||||
}"
|
||||
>
|
||||
<LibraryIcon />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings pages-list">
|
||||
<Button class="active" icon-only @click="finishOnboarding">
|
||||
<LogOutIcon />
|
||||
</Button>
|
||||
<Button class="sleek-primary" icon-only>
|
||||
<PlusIcon />
|
||||
</Button>
|
||||
<Button icon-only :class="{ active: phase === 8, highlighted: phase === 8 }">
|
||||
<SettingsIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="phase < 9" class="view">
|
||||
<div data-tauri-drag-region class="appbar">
|
||||
<section class="navigation-controls">
|
||||
<Breadcrumbs data-tauri-drag-region />
|
||||
</section>
|
||||
<section class="mod-stats">
|
||||
<FakeAppBar :show-running="phase === 7" :show-download="phase === 5">
|
||||
<template #running>
|
||||
<TutorialTip
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Playing modpacks"
|
||||
description="When you launch a modpack, you can manage it directly in the title bar here. You can stop the modpack, view the logs, and see all currently running packs."
|
||||
/>
|
||||
</template>
|
||||
<template #download>
|
||||
<TutorialTip
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Installing modpacks"
|
||||
description="When you download a modpack, Modrinth App will automatically install it for you. You can view the progress of the installation here."
|
||||
/>
|
||||
</template>
|
||||
</FakeAppBar>
|
||||
</section>
|
||||
<section class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.minimize()">
|
||||
<MinimizeIcon />
|
||||
</Button>
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
||||
<MaximizeIcon />
|
||||
</Button>
|
||||
<Button
|
||||
class="titlebar-button close"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
saveWindowState(StateFlags.ALL)
|
||||
window.getCurrent().close()
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
<div class="router-view">
|
||||
<ModrinthLoadingIndicator
|
||||
offset-height="var(--appbar-height)"
|
||||
offset-width="var(--sidebar-width)"
|
||||
/>
|
||||
<Notifications ref="notificationsWrapper" />
|
||||
<FakeRowDisplay v-if="phase < 4 || phase > 8" :show-instance="phase === 2" />
|
||||
<FakeGridDisplay v-if="phase === 6 || phase === 7" :show-instances="phase === 6" />
|
||||
<suspense>
|
||||
<FakeSearch v-if="phase === 4 || phase === 5" :show-search="phase === 4" />
|
||||
</suspense>
|
||||
<FakeSettings v-if="phase === 8" />
|
||||
</div>
|
||||
</div>
|
||||
<TutorialTip
|
||||
v-if="phase === 1"
|
||||
class="first-tip highlighted"
|
||||
:progress-function="nextPhase"
|
||||
:progress="phase"
|
||||
title="Enter the Modrinth App!"
|
||||
description="This is the Modrinth App guide. Key parts are marked with a green shadow. Click 'Next' to
|
||||
proceed. You can leave the tutorial anytime using the Exit button above the plus button on the bottom left."
|
||||
/>
|
||||
<div v-if="phase === 1" class="whole-page-shadow" />
|
||||
<TutorialTip
|
||||
v-if="phase === 2"
|
||||
class="sticky-tip"
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Home page"
|
||||
description="This is the home page. Here you can see all the latest modpacks, mods, and other content on Modrinth. You can also see a few of your installed modpacks here."
|
||||
/>
|
||||
<TutorialTip
|
||||
v-if="phase === 4"
|
||||
class="sticky-tip"
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Searching for content"
|
||||
description="You can search for content on Modrinth by navigating to the search page. You can search for mods, modpacks, and more, and install them directly from here."
|
||||
/>
|
||||
<TutorialTip
|
||||
v-if="phase === 6"
|
||||
class="sticky-tip"
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Modpack library"
|
||||
description="You can view all your installed modpacks in the library. You can launch any modpack from here, or click the card to view more information about it."
|
||||
/>
|
||||
<TutorialTip
|
||||
v-if="phase === 8"
|
||||
class="sticky-tip"
|
||||
:progress-function="nextPhase"
|
||||
:previous-function="prevPhase"
|
||||
:progress="phase"
|
||||
title="Settings"
|
||||
description="You can view and change the settings for the Modrinth App here. You can change the appearance, set and download new Java versions, and more."
|
||||
/>
|
||||
<TutorialTip
|
||||
v-if="phase === 9"
|
||||
class="final-tip highlighted"
|
||||
:progress-function="finishOnboarding"
|
||||
:progress="phase"
|
||||
title="Enter the Modrinth App!"
|
||||
description="That's it! You're ready to use the Modrinth App. If you need help, you can always join our discord server!"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.sleek-primary {
|
||||
background-color: var(--color-brand-highlight);
|
||||
transition: all ease-in-out 0.1s;
|
||||
}
|
||||
|
||||
.navigation-controls {
|
||||
flex-grow: 1;
|
||||
width: min-content;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
z-index: 20;
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
|
||||
.titlebar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all ease-in-out 0.1s;
|
||||
background-color: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
|
||||
&.close {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.container {
|
||||
--appbar-height: 3.25rem;
|
||||
--sidebar-width: 4.5rem;
|
||||
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
|
||||
.view {
|
||||
width: calc(100% - var(--sidebar-width));
|
||||
|
||||
.appbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
padding: var(--gap-md);
|
||||
height: 3.25rem;
|
||||
gap: var(--gap-sm);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
}
|
||||
|
||||
.router-view {
|
||||
width: 100%;
|
||||
height: calc(100% - 3.125rem);
|
||||
overflow: auto;
|
||||
overflow-x: hidden;
|
||||
background-color: var(--color-bg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
background-color: var(--color-raised-bg);
|
||||
box-shadow: var(--shadow-inset-sm), var(--shadow-floating);
|
||||
padding: var(--gap-md);
|
||||
width: var(--sidebar-width);
|
||||
max-width: var(--sidebar-width);
|
||||
min-width: var(--sidebar-width);
|
||||
|
||||
--sidebar-width: 4.5rem;
|
||||
}
|
||||
|
||||
.pages-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
gap: 0.5rem;
|
||||
|
||||
.btn {
|
||||
background-color: var(--color-raised-bg);
|
||||
height: 3rem !important;
|
||||
width: 3rem !important;
|
||||
padding: 0.75rem;
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: none;
|
||||
|
||||
svg {
|
||||
width: 1.5rem !important;
|
||||
height: 1.5rem !important;
|
||||
max-width: 1.5rem !important;
|
||||
max-height: 1.5rem !important;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background-color: var(--color-button-bg);
|
||||
box-shadow: var(--shadow-floating);
|
||||
}
|
||||
|
||||
&.sleek-primary {
|
||||
background-color: var(--color-brand-highlight);
|
||||
transition: all ease-in-out 0.1s;
|
||||
}
|
||||
|
||||
&.sleek-exit {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
transition: all ease-in-out 0.1s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.sticky-tip {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.intro-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: var(--gap-xl);
|
||||
|
||||
.app-logo {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--color-contrast);
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: var(--gap-sm);
|
||||
}
|
||||
}
|
||||
|
||||
.final-tip {
|
||||
position: absolute;
|
||||
bottom: 50%;
|
||||
right: 50%;
|
||||
transform: translate(50%, 50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.onboarding {
|
||||
background: top linear-gradient(0deg, #31375f, rgba(8, 14, 55, 0)),
|
||||
url(https://cdn.modrinth.com/landing-new/landing-lower.webp);
|
||||
background-size: cover;
|
||||
height: 100vh;
|
||||
min-height: 100vh;
|
||||
max-height: 100vh;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: var(--gap-xl);
|
||||
padding-top: calc(2.5rem + var(--gap-lg));
|
||||
}
|
||||
|
||||
.first-tip {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.whole-page-shadow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100%;
|
||||
backdrop-filter: brightness(0.5);
|
||||
-webkit-backdrop-filter: brightness(0.5);
|
||||
z-index: 9;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,184 @@
|
||||
<script setup>
|
||||
import { Button, Card, ModrinthIcon } from 'omorphia'
|
||||
import { ATLauncherIcon, PrismIcon } from '@/assets/external/index.js'
|
||||
|
||||
defineProps({
|
||||
nextPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
prevPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
importPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card class="import-card">
|
||||
<div class="base-ellipsis ellipsis-1" />
|
||||
<div class="base-ellipsis ellipsis-2" />
|
||||
<div class="base-ellipsis ellipsis-3" />
|
||||
<div class="base-ellipsis ellipsis-4" />
|
||||
<div class="logo">
|
||||
<ModrinthIcon />
|
||||
</div>
|
||||
<div class="launcher-stamp top-left">
|
||||
<ATLauncherIcon />
|
||||
</div>
|
||||
<div class="launcher-stamp top-right">
|
||||
<PrismIcon />
|
||||
</div>
|
||||
<div class="launcher-stamp bottom-left">
|
||||
<img src="@/assets/external/gdlauncher.png" alt="GDLauncher" />
|
||||
</div>
|
||||
<div class="launcher-stamp bottom-right">
|
||||
<img src="@/assets/external/multimc.webp" alt="MultiMC" />
|
||||
</div>
|
||||
<div class="info-section">
|
||||
<h2>Importing</h2>
|
||||
<div class="markdown-body">
|
||||
<p>
|
||||
You can import projects from other launchers by clicking below, or you can skip ahead.
|
||||
</p>
|
||||
</div>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" @click="prevPage"> Back </Button>
|
||||
<Button color="primary" @click="importPage"> Import </Button>
|
||||
<Button class="transparent" @click="nextPage"> Next </Button>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.import-card {
|
||||
width: 40rem;
|
||||
height: 32rem;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.base-ellipsis {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
border-radius: 100%;
|
||||
top: calc(var(--gap-xl) + 5rem);
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100%;
|
||||
background-color: rgba(#1bd96a, 0.1);
|
||||
}
|
||||
|
||||
.ellipsis-1 {
|
||||
width: 15rem;
|
||||
height: 15rem;
|
||||
}
|
||||
|
||||
.ellipsis-2 {
|
||||
width: 30rem;
|
||||
height: 30rem;
|
||||
}
|
||||
|
||||
.ellipsis-3 {
|
||||
width: 45rem;
|
||||
height: 45rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: absolute;
|
||||
top: calc(var(--gap-xl) + 5rem);
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-accent-contrast);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
z-index: 1;
|
||||
width: 7rem;
|
||||
height: 7rem;
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.launcher-stamp {
|
||||
position: absolute;
|
||||
width: 5rem;
|
||||
height: 5rem;
|
||||
background-color: var(--color-accent-contrast);
|
||||
border-radius: 50%;
|
||||
z-index: 1;
|
||||
opacity: 0.65;
|
||||
padding: var(--gap-lg);
|
||||
|
||||
&.top-left {
|
||||
top: var(--gap-xl);
|
||||
left: 3rem;
|
||||
}
|
||||
|
||||
&.top-right {
|
||||
top: var(--gap-xl);
|
||||
right: 3rem;
|
||||
}
|
||||
|
||||
&.bottom-left {
|
||||
top: 12rem;
|
||||
left: 5.5rem;
|
||||
}
|
||||
|
||||
&.bottom-right {
|
||||
top: 12rem;
|
||||
right: 5.5rem;
|
||||
}
|
||||
|
||||
svg,
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
position: absolute;
|
||||
bottom: var(--gap-xl);
|
||||
left: 50%;
|
||||
width: 30rem;
|
||||
transform: translateX(-50%);
|
||||
padding: var(--gap-xl);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--gap-md);
|
||||
backdrop-filter: blur(1rem) brightness(0.4);
|
||||
-webkit-backdrop-filter: blur(1rem) brightness(0.4);
|
||||
border-radius: var(--radius-lg);
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.button-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
gap: var(--gap-md);
|
||||
width: 100%;
|
||||
align-content: center;
|
||||
|
||||
.transparent {
|
||||
padding: var(--gap-sm) 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,80 @@
|
||||
<script setup>
|
||||
import { Button, XIcon } from 'omorphia'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import { window } from '@tauri-apps/api'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div data-tauri-drag-region class="fake-appbar">
|
||||
<section class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.minimize()">
|
||||
<MinimizeIcon />
|
||||
</Button>
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
||||
<MaximizeIcon />
|
||||
</Button>
|
||||
<Button
|
||||
class="titlebar-button close"
|
||||
icon-only
|
||||
@click="
|
||||
() => {
|
||||
saveWindowState(StateFlags.ALL)
|
||||
window.getCurrent().close()
|
||||
}
|
||||
"
|
||||
>
|
||||
<XIcon />
|
||||
</Button>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.fake-appbar {
|
||||
position: absolute;
|
||||
width: 100vw;
|
||||
top: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
height: 2.25rem;
|
||||
background-color: var(--color-raised-bg);
|
||||
-webkit-app-region: drag;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.window-controls {
|
||||
display: none;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.titlebar-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all ease-in-out 0.1s;
|
||||
background-color: var(--color-raised-bg);
|
||||
color: var(--color-base);
|
||||
border-radius: 0;
|
||||
height: 2.25rem;
|
||||
|
||||
&.close {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-red);
|
||||
color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script setup>
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { Button, Card } from 'omorphia'
|
||||
|
||||
defineProps({
|
||||
progress: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'Tutorial',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: 'This is a tutorial',
|
||||
},
|
||||
progressFunction: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
previousFunction: {
|
||||
type: Function,
|
||||
required: false,
|
||||
default: null,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card class="tutorial-card">
|
||||
<h3 class="tutorial-title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<div class="tutorial-body">
|
||||
{{ description }}
|
||||
</div>
|
||||
<div class="tutorial-footer">
|
||||
<Button v-if="previousFunction" class="transparent" @click="previousFunction"> Back </Button>
|
||||
{{ progress }}/9
|
||||
<ProgressBar :progress="(progress / 9) * 100" />
|
||||
<Button color="primary" :action="progressFunction">
|
||||
{{ progress === 9 ? 'Finish' : 'Next' }}
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.tutorial-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--gap-md);
|
||||
border: 1px solid var(--color-button-bg);
|
||||
width: 22rem;
|
||||
}
|
||||
|
||||
.tutorial-title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tutorial-footer {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: var(--gap-sm);
|
||||
|
||||
.transparent {
|
||||
border: 1px solid var(--color-button-bg);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -25,7 +25,7 @@ import { useRoute, useRouter } from 'vue-router'
|
||||
import { Avatar } from 'omorphia'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
@@ -737,7 +737,7 @@ const showLoaders = computed(
|
||||
</div>
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -993,6 +993,7 @@ listen('tauri://file-drop', async (event) => {
|
||||
.select-checkbox {
|
||||
button.checkbox {
|
||||
border: none;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -478,6 +478,7 @@ watch(
|
||||
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.metadata.loader_version,
|
||||
linked_data: props.instance.metadata.linked_data,
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
|
||||
@@ -213,7 +213,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
||||
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
|
||||
<template #install> <DownloadIcon /> Install </template>
|
||||
@@ -269,7 +269,7 @@ import { useRoute } from 'vue-router'
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
|
||||