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]]
|
[[package]]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-recursion",
|
"async-recursion",
|
||||||
"async-tungstenite",
|
"async-tungstenite",
|
||||||
@ -4654,7 +4654,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_cli"
|
name = "theseus_cli"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@ -4681,7 +4681,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@ -13,9 +13,10 @@ pub mod tags;
|
|||||||
|
|
||||||
pub mod data {
|
pub mod data {
|
||||||
pub use crate::state::{
|
pub use crate::state::{
|
||||||
DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader,
|
DirectoryInfo, Hooks, JavaSettings, LinkedData, MemorySettings,
|
||||||
ModrinthProject, ModrinthTeamMember, ModrinthUser, ModrinthVersion,
|
ModLoader, ModrinthProject, ModrinthTeamMember, ModrinthUser,
|
||||||
ProfileMetadata, ProjectMetadata, Settings, Theme, WindowSize,
|
ModrinthVersion, ProfileMetadata, ProjectMetadata, Settings, Theme,
|
||||||
|
WindowSize,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref HYDRA_URL: Url = Url::parse("https://hydra.modrinth.com")
|
static ref HYDRA_URL: Url =
|
||||||
|
Url::parse("https://staging-api.modrinth.com/v2/auth/minecraft/")
|
||||||
.expect("Hydra URL parse failed");
|
.expect("Hydra URL parse failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +40,7 @@ struct TokenJSON {
|
|||||||
token: String,
|
token: String,
|
||||||
refresh_token: String,
|
refresh_token: String,
|
||||||
expires_after: u32,
|
expires_after: u32,
|
||||||
|
flow: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
@ -65,11 +67,10 @@ pub struct HydraAuthFlow<S: AsyncRead + AsyncWrite + Unpin> {
|
|||||||
|
|
||||||
impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
||||||
pub async fn new() -> crate::Result<Self> {
|
pub async fn new() -> crate::Result<Self> {
|
||||||
let sock_url = wrap_ref_builder!(
|
let (socket, _) = ws::tokio::connect_async(
|
||||||
it = HYDRA_URL.clone() =>
|
"wss://staging-api.modrinth.com/v2/auth/minecraft/ws",
|
||||||
{ it.set_scheme("wss").ok() }
|
)
|
||||||
);
|
.await?;
|
||||||
let (socket, _) = ws::tokio::connect_async(sock_url.clone()).await?;
|
|
||||||
Ok(Self { socket })
|
Ok(Self { socket })
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,7 +88,7 @@ impl HydraAuthFlow<ws::tokio::ConnectStream> {
|
|||||||
.into_data();
|
.into_data();
|
||||||
let code = ErrorJSON::unwrap::<LoginCodeJSON>(&code_resp)?;
|
let code = ErrorJSON::unwrap::<LoginCodeJSON>(&code_resp)?;
|
||||||
Ok(wrap_ref_builder!(
|
Ok(wrap_ref_builder!(
|
||||||
it = HYDRA_URL.join("login")? =>
|
it = HYDRA_URL.join("init")? =>
|
||||||
{ it.query_pairs_mut().append_pair("id", &code.login_code); }
|
{ it.query_pairs_mut().append_pair("id", &code.login_code); }
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
@ -133,7 +134,7 @@ pub async fn refresh_credentials(
|
|||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let resp = fetch_json::<TokenJSON>(
|
let resp = fetch_json::<TokenJSON>(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
HYDRA_URL.join("/refresh")?.as_str(),
|
"https://staging-api.modrinth.com/v2/auth/minecraft/refresh",
|
||||||
None,
|
None,
|
||||||
Some(serde_json::json!({ "refresh_token": credentials.refresh_token })),
|
Some(serde_json::json!({ "refresh_token": credentials.refresh_token })),
|
||||||
semaphore,
|
semaphore,
|
||||||
|
|||||||
@ -41,7 +41,7 @@ pub struct Settings {
|
|||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub advanced_rendering: bool,
|
pub advanced_rendering: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub onboarded: bool,
|
pub onboarded_new: bool,
|
||||||
#[serde(default = "DirectoryInfo::get_initial_settings_dir")]
|
#[serde(default = "DirectoryInfo::get_initial_settings_dir")]
|
||||||
pub loaded_config_dir: Option<PathBuf>,
|
pub loaded_config_dir: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
@ -82,7 +82,7 @@ impl Settings {
|
|||||||
developer_mode: false,
|
developer_mode: false,
|
||||||
opt_out_analytics: false,
|
opt_out_analytics: false,
|
||||||
advanced_rendering: true,
|
advanced_rendering: true,
|
||||||
onboarded: false,
|
onboarded_new: false,
|
||||||
|
|
||||||
// By default, the config directory is the same as the settings directory
|
// By default, the config directory is the same as the settings directory
|
||||||
loaded_config_dir: DirectoryInfo::get_initial_settings_dir(),
|
loaded_config_dir: DirectoryInfo::get_initial_settings_dir(),
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_cli"
|
name = "theseus_cli"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "theseus_gui",
|
"name": "theseus_gui",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.2.1",
|
"version": "0.3.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@ -20,6 +20,7 @@
|
|||||||
"ofetch": "^1.0.1",
|
"ofetch": "^1.0.1",
|
||||||
"omorphia": "^0.4.33",
|
"omorphia": "^0.4.33",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
|
"qrcode.vue": "^3.4.0",
|
||||||
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
|
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
|
||||||
"vite-svg-loader": "^4.0.0",
|
"vite-svg-loader": "^4.0.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
|
|||||||
9
theseus_gui/pnpm-lock.yaml
generated
@ -1,8 +1,4 @@
|
|||||||
lockfileVersion: '6.1'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@tauri-apps/api':
|
'@tauri-apps/api':
|
||||||
@ -26,6 +22,9 @@ dependencies:
|
|||||||
pinia:
|
pinia:
|
||||||
specifier: ^2.1.3
|
specifier: ^2.1.3
|
||||||
version: 2.1.3(vue@3.3.4)
|
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:
|
tauri-plugin-window-state-api:
|
||||||
specifier: github:tauri-apps/tauri-plugin-window-state#v1
|
specifier: github:tauri-apps/tauri-plugin-window-state#v1
|
||||||
version: github.com/tauri-apps/tauri-plugin-window-state/347c792535d2623fc21f66590d06f4c8dadd85ba
|
version: github.com/tauri-apps/tauri-plugin-window-state/347c792535d2623fc21f66590d06f4c8dadd85ba
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.3.0"
|
version = "0.3.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
@ -294,6 +294,7 @@ pub struct EditProfileMetadata {
|
|||||||
pub game_version: Option<String>,
|
pub game_version: Option<String>,
|
||||||
pub loader: Option<ModLoader>,
|
pub loader: Option<ModLoader>,
|
||||||
pub loader_version: Option<LoaderVersion>,
|
pub loader_version: Option<LoaderVersion>,
|
||||||
|
pub linked_data: Option<LinkedData>,
|
||||||
pub groups: Option<Vec<String>>,
|
pub groups: Option<Vec<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +317,7 @@ pub async fn profile_edit(
|
|||||||
prof.metadata.loader = loader;
|
prof.metadata.loader = loader;
|
||||||
}
|
}
|
||||||
prof.metadata.loader_version = metadata.loader_version;
|
prof.metadata.loader_version = metadata.loader_version;
|
||||||
|
prof.metadata.linked_data = metadata.linked_data;
|
||||||
|
|
||||||
if let Some(groups) = metadata.groups {
|
if let Some(groups) = metadata.groups {
|
||||||
prof.metadata.groups = groups;
|
prof.metadata.groups = groups;
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Modrinth App",
|
"productName": "Modrinth App",
|
||||||
"version": "0.3.0"
|
"version": "0.3.1"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|||||||
@ -20,30 +20,39 @@ import RunningAppBar from '@/components/ui/RunningAppBar.vue'
|
|||||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||||
import { useNotifications } from '@/store/notifications.js'
|
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 { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||||
import { type } from '@tauri-apps/api/os'
|
import { type } from '@tauri-apps/api/os'
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
import { isDev } from '@/helpers/utils.js'
|
import { isDev } from '@/helpers/utils.js'
|
||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||||
import OnboardingModal from '@/components/OnboardingModal.vue'
|
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { window as TauriWindow } from '@tauri-apps/api'
|
import { window as TauriWindow } from '@tauri-apps/api'
|
||||||
import { TauriEvent } from '@tauri-apps/api/event'
|
import { TauriEvent } from '@tauri-apps/api/event'
|
||||||
import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
|
import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
|
||||||
import { confirm } from '@tauri-apps/api/dialog'
|
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 themeStore = useTheming()
|
||||||
|
const urlModal = ref(null)
|
||||||
const isLoading = ref(true)
|
const isLoading = ref(true)
|
||||||
|
const videoPlaying = ref(true)
|
||||||
|
const showOnboarding = ref(false)
|
||||||
|
|
||||||
|
const onboardingVideo = ref()
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
initialize: async () => {
|
initialize: async () => {
|
||||||
isLoading.value = false
|
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()
|
await get()
|
||||||
const dev = await isDev()
|
const dev = await isDev()
|
||||||
const version = await getVersion()
|
const version = await getVersion()
|
||||||
|
showOnboarding.value = !onboarded_new
|
||||||
|
|
||||||
themeStore.setThemeState(theme)
|
themeStore.setThemeState(theme)
|
||||||
themeStore.collapsedNavigation = collapsed_navigation
|
themeStore.collapsedNavigation = collapsed_navigation
|
||||||
@ -53,7 +62,7 @@ defineExpose({
|
|||||||
if (opt_out_analytics) {
|
if (opt_out_analytics) {
|
||||||
mixpanel.opt_out_tracking()
|
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())
|
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||||
|
|
||||||
@ -70,6 +79,10 @@ defineExpose({
|
|||||||
type: 'warn',
|
type: 'warn',
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (showOnboarding.value) {
|
||||||
|
onboardingVideo.value.play()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -145,14 +158,26 @@ document.querySelector('body').addEventListener('click', function (e) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const accounts = ref(null)
|
const accounts = ref(null)
|
||||||
|
|
||||||
|
command_listener((e) => {
|
||||||
|
console.log(e)
|
||||||
|
urlModal.value.show(e)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<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">
|
<div v-else class="container">
|
||||||
<suspense>
|
|
||||||
<OnboardingModal ref="testModal" :accounts="accounts" />
|
|
||||||
</suspense>
|
|
||||||
<div class="nav-container">
|
<div class="nav-container">
|
||||||
<div class="nav-section">
|
<div class="nav-section">
|
||||||
<suspense>
|
<suspense>
|
||||||
@ -181,7 +206,8 @@ const accounts = ref(null)
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings pages-list">
|
<div class="settings pages-list">
|
||||||
<Button
|
<Button
|
||||||
class="sleek-primary icon-only collapsed-button"
|
class="sleek-primary collapsed-button"
|
||||||
|
icon-only
|
||||||
@click="() => $refs.installationModal.show()"
|
@click="() => $refs.installationModal.show()"
|
||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
@ -229,7 +255,6 @@ const accounts = ref(null)
|
|||||||
offset-height="var(--appbar-height)"
|
offset-height="var(--appbar-height)"
|
||||||
offset-width="var(--sidebar-width)"
|
offset-width="var(--sidebar-width)"
|
||||||
/>
|
/>
|
||||||
<Notifications ref="notificationsWrapper" />
|
|
||||||
<RouterView v-slot="{ Component }" class="main-view">
|
<RouterView v-slot="{ Component }" class="main-view">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
||||||
@ -240,6 +265,8 @@ const accounts = ref(null)
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<URLConfirmModal ref="urlModal" />
|
||||||
|
<Notifications ref="notificationsWrapper" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -449,4 +476,12 @@ const accounts = ref(null)
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.video {
|
||||||
|
margin-top: 2.25rem;
|
||||||
|
width: 100vw;
|
||||||
|
height: calc(100vh - 2.25rem);
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
</style>
|
</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 GithubIcon } from './github.svg'
|
||||||
export { default as MastodonIcon } from './mastodon.svg'
|
export { default as MastodonIcon } from './mastodon.svg'
|
||||||
export { default as RedditIcon } from './reddit.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);
|
padding-top: calc(var(--gap-md) + 1.75rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.account-card {
|
.account-card,
|
||||||
|
.card-section {
|
||||||
top: calc(var(--gap-md) + 1.75rem);
|
top: calc(var(--gap-md) + 1.75rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.windows {
|
.windows {
|
||||||
|
.fake-appbar {
|
||||||
|
height: 2.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.window-controls {
|
.window-controls {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
}
|
}
|
||||||
@ -114,3 +119,12 @@ input {
|
|||||||
border-radius: var(--radius-lg);
|
border-radius: var(--radius-lg);
|
||||||
border: 3px solid var(--color-bg);
|
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 ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||||
import {
|
import {
|
||||||
get_all_running_profile_paths,
|
get_all_running_profile_paths,
|
||||||
get_uuids_by_profile_path,
|
get_uuids_by_profile_path,
|
||||||
@ -269,7 +269,7 @@ onUnmounted(() => {
|
|||||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<ModInstallModal ref="modInstallModal" />
|
||||||
</template>
|
</template>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.content {
|
.content {
|
||||||
|
|||||||
@ -8,7 +8,11 @@
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
: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 v-show="mode === 'expanded'" class="avatar-text">
|
||||||
<div class="text no-select">
|
<div class="text no-select">
|
||||||
|
|||||||
@ -233,5 +233,6 @@ const exportPack = async () => {
|
|||||||
.button-row {
|
.button-row {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: var(--gap-sm);
|
gap: var(--gap-sm);
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import { process_listener } from '@/helpers/events'
|
|||||||
import { useFetch } from '@/helpers/fetch.js'
|
import { useFetch } from '@/helpers/fetch.js'
|
||||||
import { handleError } from '@/store/state.js'
|
import { handleError } from '@/store/state.js'
|
||||||
import { showProfileInFolder } from '@/helpers/utils.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'
|
import mixpanel from 'mixpanel-browser'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@ -227,7 +227,7 @@ onUnmounted(() => unlisten())
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<ModInstallModal ref="modInstallModal" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<Chips v-model="creationType" :items="['custom', 'from file']" />
|
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
|
||||||
</div>
|
</div>
|
||||||
|
<hr class="card-divider" />
|
||||||
<div v-if="creationType === 'custom'" class="modal-body">
|
<div v-if="creationType === 'custom'" class="modal-body">
|
||||||
<div class="image-upload">
|
<div class="image-upload">
|
||||||
<Avatar :src="display_icon" size="md" :rounded="true" />
|
<Avatar :src="display_icon" size="md" :rounded="true" />
|
||||||
@ -82,10 +83,106 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<Button @click="openFile"> <FolderOpenIcon /> Import from file </Button>
|
||||||
<div class="info"><InfoIcon /> Or drag and drop your .mrpack file</div>
|
<div class="info"><InfoIcon /> Or drag and drop your .mrpack file</div>
|
||||||
</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>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -102,6 +199,8 @@ import {
|
|||||||
Checkbox,
|
Checkbox,
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
InfoIcon,
|
InfoIcon,
|
||||||
|
FolderSearchIcon,
|
||||||
|
UpdatedIcon,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { computed, ref, shallowRef } from 'vue'
|
import { computed, ref, shallowRef } from 'vue'
|
||||||
import { get_loaders } from '@/helpers/tags'
|
import { get_loaders } from '@/helpers/tags'
|
||||||
@ -120,6 +219,7 @@ import mixpanel from 'mixpanel-browser'
|
|||||||
import { useTheming } from '@/store/state.js'
|
import { useTheming } from '@/store/state.js'
|
||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import { install_from_file } from '@/helpers/pack.js'
|
import { install_from_file } from '@/helpers/pack.js'
|
||||||
|
import { get_importable_instances, import_instance } from '@/helpers/import.js'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -363,4 +525,77 @@ listen('tauri://file-drop', async (event) => {
|
|||||||
padding: var(--gap-lg);
|
padding: var(--gap-lg);
|
||||||
padding-bottom: 0;
|
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>
|
</style>
|
||||||
|
|||||||
@ -24,9 +24,11 @@ import { installVersionDependencies } from '@/helpers/utils'
|
|||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import mixpanel from 'mixpanel-browser'
|
import mixpanel from 'mixpanel-browser'
|
||||||
import { useTheming } from '@/store/theme.js'
|
import { useTheming } from '@/store/theme.js'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
import { tauri } from '@tauri-apps/api'
|
import { tauri } from '@tauri-apps/api'
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const versions = ref([])
|
const versions = ref([])
|
||||||
const project = ref('')
|
const project = ref('')
|
||||||
@ -63,13 +65,23 @@ const profiles = ref([])
|
|||||||
|
|
||||||
async function install(instance) {
|
async function install(instance) {
|
||||||
instance.installing = true
|
instance.installing = true
|
||||||
|
console.log(versions.value)
|
||||||
const version = versions.value.find((v) => {
|
const version = versions.value.find((v) => {
|
||||||
return (
|
return (
|
||||||
v.game_versions.includes(instance.metadata.game_version) &&
|
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 installMod(instance.path, version.id).catch(handleError)
|
||||||
await installVersionDependencies(instance, version)
|
await installVersionDependencies(instance, version)
|
||||||
|
|
||||||
@ -153,11 +165,11 @@ const createInstance = async () => {
|
|||||||
creatingInstance.value = true
|
creatingInstance.value = true
|
||||||
|
|
||||||
const loader =
|
const loader =
|
||||||
versions.value[0].loaders[0] !== 'forge' ||
|
versions.value[0].loaders[0] !== 'forge' &&
|
||||||
versions.value[0].loaders[0] !== 'fabric' ||
|
versions.value[0].loaders[0] !== 'fabric' &&
|
||||||
versions.value[0].loaders[0] !== 'quilt'
|
versions.value[0].loaders[0] !== 'quilt'
|
||||||
? versions.value[0].loaders[0]
|
? 'vanilla'
|
||||||
: 'vanilla'
|
: versions.value[0].loaders[0]
|
||||||
|
|
||||||
const id = await create(
|
const id = await create(
|
||||||
name.value,
|
name.value,
|
||||||
@ -169,6 +181,8 @@ const createInstance = async () => {
|
|||||||
|
|
||||||
await installMod(id, versions.value[0].id).catch(handleError)
|
await installMod(id, versions.value[0].id).catch(handleError)
|
||||||
|
|
||||||
|
await router.push(`/instance/${encodeURIComponent(id)}/`)
|
||||||
|
|
||||||
const instance = await get(id, true)
|
const instance = await get(id, true)
|
||||||
await installVersionDependencies(instance, versions.value)
|
await installVersionDependencies(instance, versions.value)
|
||||||
|
|
||||||
@ -2,10 +2,13 @@
|
|||||||
<Card
|
<Card
|
||||||
class="card button-base"
|
class="card button-base"
|
||||||
@click="
|
@click="
|
||||||
|
() => {
|
||||||
|
emits('open')
|
||||||
$router.push({
|
$router.push({
|
||||||
path: `/project/${project.project_id}/`,
|
path: `/project/${project.project_id ?? project.id}/`,
|
||||||
query: { i: props.instance ? props.instance.path : undefined },
|
query: { i: props.instance ? props.instance.path : undefined },
|
||||||
})
|
})
|
||||||
|
}
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
@ -14,7 +17,7 @@
|
|||||||
<div class="content-wrapper">
|
<div class="content-wrapper">
|
||||||
<div class="title joined-text">
|
<div class="title joined-text">
|
||||||
<h2>{{ project.title }}</h2>
|
<h2>{{ project.title }}</h2>
|
||||||
<span>by {{ project.author }}</span>
|
<span v-if="project.author">by {{ project.author }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="description">
|
<div class="description">
|
||||||
{{ project.description }}
|
{{ project.description }}
|
||||||
@ -42,14 +45,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<HeartIcon />
|
<HeartIcon />
|
||||||
{{ formatNumber(project.follows) }}
|
{{ formatNumber(project.follows ?? project.followers) }}
|
||||||
</div>
|
</div>
|
||||||
<div class="badge">
|
<div class="badge">
|
||||||
<CalendarIcon />
|
<CalendarIcon />
|
||||||
{{ formatCategory(dayjs(project.date_modified).fromNow()) }}
|
{{ formatCategory(dayjs(project.date_modified ?? project.updated).fromNow()) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="install">
|
<div v-if="project.author" class="install">
|
||||||
<Button color="primary" :disabled="installed || installing" @click.stop="install()">
|
<Button color="primary" :disabled="installed || installing" @click.stop="install()">
|
||||||
<DownloadIcon v-if="!installed" />
|
<DownloadIcon v-if="!installed" />
|
||||||
<CheckIcon v-else />
|
<CheckIcon v-else />
|
||||||
@ -124,6 +127,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const emits = defineEmits(['open'])
|
||||||
|
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
const installed = ref(props.installed)
|
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>
|
<template>
|
||||||
<Modal
|
<Modal ref="onboardingModal" :closable="false">
|
||||||
ref="onboardingModal"
|
|
||||||
:header="['Getting started', 'Sign into Minecraft', 'Install java'][page - 1]"
|
|
||||||
:closable="false"
|
|
||||||
>
|
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div v-if="page === 1" key="1" class="content">
|
<div v-if="page === 1" key="1" class="content">
|
||||||
<svg
|
<svg
|
||||||
@ -174,6 +170,10 @@ const props = defineProps({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: () => {},
|
default: () => {},
|
||||||
},
|
},
|
||||||
|
finish: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
async function fetchSettings() {
|
async function fetchSettings() {
|
||||||
@ -210,16 +210,13 @@ watch([settings, settings.value], async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!settings.value.onboarded) {
|
|
||||||
onboardingModal.value.show()
|
onboardingModal.value.show()
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function pageTurn() {
|
async function pageTurn() {
|
||||||
if (page.value === 3) {
|
if (page.value === 3) {
|
||||||
settings.value.onboarded = true
|
|
||||||
onboardingModal.value.hide()
|
onboardingModal.value.hide()
|
||||||
mixpanel.track('OnboardingFinish')
|
props.finish()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
page.value++
|
page.value++
|
||||||
@ -267,7 +264,7 @@ onBeforeUnmount(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: var(--gap-lg);
|
padding: var(--gap-xl);
|
||||||
|
|
||||||
height: min(70vh, 450px);
|
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 { Avatar } from 'omorphia'
|
||||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.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 SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||||
import { useFetch } from '@/helpers/fetch.js'
|
import { useFetch } from '@/helpers/fetch.js'
|
||||||
@ -737,7 +737,7 @@ const showLoaders = computed(
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<ModInstallModal ref="modInstallModal" />
|
||||||
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@ -993,6 +993,7 @@ listen('tauri://file-drop', async (event) => {
|
|||||||
.select-checkbox {
|
.select-checkbox {
|
||||||
button.checkbox {
|
button.checkbox {
|
||||||
border: none;
|
border: none;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -478,6 +478,7 @@ watch(
|
|||||||
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||||
loader_version: props.instance.metadata.loader_version,
|
loader_version: props.instance.metadata.loader_version,
|
||||||
|
linked_data: props.instance.metadata.linked_data,
|
||||||
},
|
},
|
||||||
java: {},
|
java: {},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -213,7 +213,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<ModInstallModal ref="modInstallModal" />
|
||||||
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
||||||
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
|
<ContextMenu ref="options" @option-clicked="handleOptionsClick">
|
||||||
<template #install> <DownloadIcon /> Install </template>
|
<template #install> <DownloadIcon /> Install </template>
|
||||||
@ -269,7 +269,7 @@ import { useRoute } from 'vue-router'
|
|||||||
import { ref, shallowRef, watch } from 'vue'
|
import { ref, shallowRef, watch } from 'vue'
|
||||||
import { installVersionDependencies } from '@/helpers/utils'
|
import { installVersionDependencies } from '@/helpers/utils'
|
||||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
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 { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||||
import { useFetch } from '@/helpers/fetch.js'
|
import { useFetch } from '@/helpers/fetch.js'
|
||||||
|
|||||||