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>
6
Cargo.lock
generated
@ -4609,7 +4609,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "theseus"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"async-recursion",
|
||||
"async-tungstenite",
|
||||
@ -4654,7 +4654,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "theseus_cli"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"argh",
|
||||
"color-eyre",
|
||||
@ -4681,7 +4681,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "theseus_gui"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"cocoa",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@ -13,9 +13,10 @@ pub mod tags;
|
||||
|
||||
pub mod data {
|
||||
pub use crate::state::{
|
||||
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
|
||||
ModrinthProject, ModrinthTeamMember, ModrinthUser, ModrinthVersion,
|
||||
ProfileMetadata, ProjectMetadata, Settings, Theme, WindowSize,
|
||||
DirectoryInfo, Hooks, JavaSettings, LinkedData, MemorySettings,
|
||||
ModLoader, ModrinthProject, ModrinthTeamMember, ModrinthUser,
|
||||
ModrinthVersion, ProfileMetadata, ProjectMetadata, Settings, Theme,
|
||||
WindowSize,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -9,8 +9,9 @@ use serde::{Deserialize, Serialize};
|
||||
use url::Url;
|
||||
|
||||
lazy_static! {
|
||||
static ref HYDRA_URL: Url = Url::parse("https://hydra.modrinth.com")
|
||||
.expect("Hydra URL parse failed");
|
||||
static ref HYDRA_URL: Url =
|
||||
Url::parse("https://staging-api.modrinth.com/v2/auth/minecraft/")
|
||||
.expect("Hydra URL parse failed");
|
||||
}
|
||||
|
||||
// Socket messages
|
||||
@ -39,6 +40,7 @@ struct TokenJSON {
|
||||
token: String,
|
||||
refresh_token: String,
|
||||
expires_after: u32,
|
||||
flow: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -65,11 +67,10 @@ pub struct HydraAuthFlow<S: AsyncRead + AsyncWrite + Unpin> {
|
||||
|
||||
impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||
pub async fn new() -> crate::Result<Self> {
|
||||
let sock_url = wrap_ref_builder!(
|
||||
it = HYDRA_URL.clone() =>
|
||||
{ it.set_scheme("wss").ok() }
|
||||
);
|
||||
let (socket, _) = ws::tokio::connect_async(sock_url.clone()).await?;
|
||||
let (socket, _) = ws::tokio::connect_async(
|
||||
"wss://staging-api.modrinth.com/v2/auth/minecraft/ws",
|
||||
)
|
||||
.await?;
|
||||
Ok(Self { socket })
|
||||
}
|
||||
|
||||
@ -87,7 +88,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||
.into_data();
|
||||
let code = ErrorJSON::unwrap::<LoginCodeJSON>(&code_resp)?;
|
||||
Ok(wrap_ref_builder!(
|
||||
it = HYDRA_URL.join("login")? =>
|
||||
it = HYDRA_URL.join("init")? =>
|
||||
{ it.query_pairs_mut().append_pair("id", &code.login_code); }
|
||||
))
|
||||
}
|
||||
@ -133,7 +134,7 @@ pub async fn refresh_credentials(
|
||||
) -> crate::Result<()> {
|
||||
let resp = fetch_json::<TokenJSON>(
|
||||
Method::POST,
|
||||
HYDRA_URL.join("/refresh")?.as_str(),
|
||||
"https://staging-api.modrinth.com/v2/auth/minecraft/refresh",
|
||||
None,
|
||||
Some(serde_json::json!({ "refresh_token": credentials.refresh_token })),
|
||||
semaphore,
|
||||
|
||||
@ -41,7 +41,7 @@ pub struct Settings {
|
||||
#[serde(default)]
|
||||
pub advanced_rendering: bool,
|
||||
#[serde(default)]
|
||||
pub onboarded: bool,
|
||||
pub onboarded_new: bool,
|
||||
#[serde(default = "DirectoryInfo::get_initial_settings_dir")]
|
||||
pub loaded_config_dir: Option<PathBuf>,
|
||||
}
|
||||
@ -82,7 +82,7 @@ impl Settings {
|
||||
developer_mode: false,
|
||||
opt_out_analytics: false,
|
||||
advanced_rendering: true,
|
||||
onboarded: false,
|
||||
onboarded_new: false,
|
||||
|
||||
// By default, the config directory is the same as the settings directory
|
||||
loaded_config_dir: DirectoryInfo::get_initial_settings_dir(),
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus_cli"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "theseus_gui",
|
||||
"private": true,
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
@ -20,6 +20,7 @@
|
||||
"ofetch": "^1.0.1",
|
||||
"omorphia": "^0.4.33",
|
||||
"pinia": "^2.1.3",
|
||||
"qrcode.vue": "^3.4.0",
|
||||
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"vue": "^3.3.4",
|
||||
|
||||
9
theseus_gui/pnpm-lock.yaml
generated
@ -1,8 +1,4 @@
|
||||
lockfileVersion: '6.1'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
lockfileVersion: '6.0'
|
||||
|
||||
dependencies:
|
||||
'@tauri-apps/api':
|
||||
@ -26,6 +22,9 @@ dependencies:
|
||||
pinia:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3(vue@3.3.4)
|
||||
qrcode.vue:
|
||||
specifier: ^3.4.0
|
||||
version: 3.4.0(vue@3.3.4)
|
||||
tauri-plugin-window-state-api:
|
||||
specifier: github:tauri-apps/tauri-plugin-window-state#v1
|
||||
version: github.com/tauri-apps/tauri-plugin-window-state/347c792535d2623fc21f66590d06f4c8dadd85ba
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus_gui"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
|
||||
@ -294,6 +294,7 @@ pub struct EditProfileMetadata {
|
||||
pub game_version: Option<String>,
|
||||
pub loader: Option<ModLoader>,
|
||||
pub loader_version: Option<LoaderVersion>,
|
||||
pub linked_data: Option<LinkedData>,
|
||||
pub groups: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
@ -316,6 +317,7 @@ pub async fn profile_edit(
|
||||
prof.metadata.loader = loader;
|
||||
}
|
||||
prof.metadata.loader_version = metadata.loader_version;
|
||||
prof.metadata.linked_data = metadata.linked_data;
|
||||
|
||||
if let Some(groups) = metadata.groups {
|
||||
prof.metadata.groups = groups;
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Modrinth App",
|
||||
"version": "0.3.0"
|
||||
"version": "0.3.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
@ -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>
|
||||
|
||||
1
theseus_gui/src/assets/external/atlauncher.svg
vendored
Normal file
@ -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 |
BIN
theseus_gui/src/assets/external/gdlauncher.png
vendored
Normal file
|
After Width: | Height: | Size: 26 KiB |
5
theseus_gui/src/assets/external/github.svg
vendored
@ -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 |
10
theseus_gui/src/assets/external/gitlab.svg
vendored
Normal file
@ -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 |
21
theseus_gui/src/assets/external/google.svg
vendored
Normal file
@ -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
theseus_gui/src/assets/external/index.js
vendored
@ -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'
|
||||
|
||||
6
theseus_gui/src/assets/external/microsoft.svg
vendored
Normal file
@ -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 |
BIN
theseus_gui/src/assets/external/multimc.webp
vendored
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
80
theseus_gui/src/assets/external/prism.svg
vendored
Normal file
@ -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 |
14
theseus_gui/src/assets/external/steam.svg
vendored
Normal file
@ -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 |
BIN
theseus_gui/src/assets/profile_icon.png
Normal file
|
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;
|
||||
}
|
||||
|
||||
BIN
theseus_gui/src/assets/video.mp4
Normal file
@ -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)
|
||||
|
||||
|
||||
136
theseus_gui/src/components/ui/URLConfirmModal.vue
Normal file
@ -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>
|
||||
183
theseus_gui/src/components/ui/tutorial/FakeAccountsCard.vue
Normal file
@ -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>
|
||||
250
theseus_gui/src/components/ui/tutorial/FakeAppBar.vue
Normal file
@ -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>
|
||||
219
theseus_gui/src/components/ui/tutorial/FakeGridDisplay.vue
Normal file
@ -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>
|
||||
376
theseus_gui/src/components/ui/tutorial/FakeRowDisplay.vue
Normal file
@ -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>
|
||||
496
theseus_gui/src/components/ui/tutorial/FakeSearch.vue
Normal file
@ -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>
|
||||
247
theseus_gui/src/components/ui/tutorial/FakeSettings.vue
Normal file
@ -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>
|
||||
197
theseus_gui/src/components/ui/tutorial/GalleryImage.vue
Normal file
268
theseus_gui/src/components/ui/tutorial/ImportingCard.vue
Normal file
@ -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>
|
||||
219
theseus_gui/src/components/ui/tutorial/LoginCard.vue
Normal file
@ -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>
|
||||
182
theseus_gui/src/components/ui/tutorial/ModrinthLoginScreen.vue
Normal file
@ -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);
|
||||
|
||||
513
theseus_gui/src/components/ui/tutorial/OnboardingScreen.vue
Normal file
@ -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>
|
||||
184
theseus_gui/src/components/ui/tutorial/PreImportScreen.vue
Normal file
@ -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>
|
||||
80
theseus_gui/src/components/ui/tutorial/StickyTitleBar.vue
Normal file
@ -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>
|
||||
72
theseus_gui/src/components/ui/tutorial/TutorialTip.vue
Normal file
@ -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'
|
||||
|
||||