You've already forked AstralRinth
forked from didirus/AstralRinth
Community requested enhancements (#556)
* Make images lazy and fix #198 * Fix console spam * Fix bug with bad pagination impl * Fixes #232 * Finalize more bug fixes * run lint * Improve minecraft sign in, improve onboarding * Linter * Added back button * Implement #530 * run linter * Address changes * Bump version + run fmt --------- Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -110,3 +110,5 @@ fabric.properties
|
|||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
theseus.iml
|
||||||
|
|
||||||
|
|||||||
6
Cargo.lock
generated
6
Cargo.lock
generated
@@ -4609,7 +4609,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.5.0"
|
version = "0.5.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.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"argh",
|
"argh",
|
||||||
"color-eyre",
|
"color-eyre",
|
||||||
@@ -4681,7 +4681,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"cocoa",
|
"cocoa",
|
||||||
|
|||||||
11
theseus.iml
Normal file
11
theseus.iml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="JAVA_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||||
|
<exclude-output />
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<sourceFolder url="file://$MODULE_DIR$/theseus/library" isTestSource="false" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
|||||||
@@ -160,12 +160,11 @@ impl Children {
|
|||||||
.signed_duration_since(last_updated_playtime)
|
.signed_duration_since(last_updated_playtime)
|
||||||
.num_seconds();
|
.num_seconds();
|
||||||
if diff >= 60 {
|
if diff >= 60 {
|
||||||
if let Err(e) =
|
if let Err(e) = profile::edit(&associated_profile, |prof| {
|
||||||
profile::edit(&associated_profile, |mut prof| {
|
prof.metadata.recent_time_played += diff as u64;
|
||||||
prof.metadata.recent_time_played += diff as u64;
|
async { Ok(()) }
|
||||||
async { Ok(()) }
|
})
|
||||||
})
|
.await
|
||||||
.await
|
|
||||||
{
|
{
|
||||||
tracing::warn!(
|
tracing::warn!(
|
||||||
"Failed to update playtime for profile {}: {}",
|
"Failed to update playtime for profile {}: {}",
|
||||||
@@ -181,7 +180,7 @@ impl Children {
|
|||||||
let diff = Utc::now()
|
let diff = Utc::now()
|
||||||
.signed_duration_since(last_updated_playtime)
|
.signed_duration_since(last_updated_playtime)
|
||||||
.num_seconds();
|
.num_seconds();
|
||||||
if let Err(e) = profile::edit(&associated_profile, |mut prof| {
|
if let Err(e) = profile::edit(&associated_profile, |prof| {
|
||||||
prof.metadata.recent_time_played += diff as u64;
|
prof.metadata.recent_time_played += diff as u64;
|
||||||
async { Ok(()) }
|
async { Ok(()) }
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_cli"
|
name = "theseus_cli"
|
||||||
version = "0.5.0"
|
version = "0.5.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.5.0",
|
"version": "0.5.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"floating-vue": "^2.0.0-beta.20",
|
"floating-vue": "^2.0.0-beta.20",
|
||||||
"mixpanel-browser": "^2.47.0",
|
"mixpanel-browser": "^2.47.0",
|
||||||
"ofetch": "^1.0.1",
|
"ofetch": "^1.0.1",
|
||||||
"omorphia": "^0.4.35",
|
"omorphia": "^0.4.38",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
"qrcode.vue": "^3.4.0",
|
"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",
|
||||||
|
|||||||
8
theseus_gui/pnpm-lock.yaml
generated
8
theseus_gui/pnpm-lock.yaml
generated
@@ -21,8 +21,8 @@ dependencies:
|
|||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
omorphia:
|
omorphia:
|
||||||
specifier: ^0.4.35
|
specifier: ^0.4.38
|
||||||
version: 0.4.35
|
version: 0.4.38
|
||||||
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)
|
||||||
@@ -1348,8 +1348,8 @@ packages:
|
|||||||
ufo: 1.1.2
|
ufo: 1.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/omorphia@0.4.35:
|
/omorphia@0.4.38:
|
||||||
resolution: {integrity: sha512-ZxA6sJKWZbiG49l/gTG25cxAvTcIfVSLhuIV2e+LSY0nwkZO4EFvxhzGNz0exR3lVs+OdDCdJyb1U2QYMVbVrA==}
|
resolution: {integrity: sha512-V0vEarmAart6Gf5WuPUZ58TuIiQf7rI5HJpmYU7FVbtdvZ3q08VqyKZflCddbeBSFQ4/N+A+sNr/ELf/jz+Cug==}
|
||||||
dependencies:
|
dependencies:
|
||||||
dayjs: 1.11.7
|
dayjs: 1.11.7
|
||||||
floating-vue: 2.0.0-beta.20(vue@3.3.4)
|
floating-vue: 2.0.0-beta.20(vue@3.3.4)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.5.0"
|
version = "0.5.1"
|
||||||
description = "A Tauri App"
|
description = "A Tauri App"
|
||||||
authors = ["you"]
|
authors = ["you"]
|
||||||
license = ""
|
license = ""
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
},
|
},
|
||||||
"package": {
|
"package": {
|
||||||
"productName": "Modrinth App",
|
"productName": "Modrinth App",
|
||||||
"version": "0.5.0"
|
"version": "0.5.1"
|
||||||
},
|
},
|
||||||
"tauri": {
|
"tauri": {
|
||||||
"allowlist": {
|
"allowlist": {
|
||||||
|
|||||||
@@ -218,10 +218,11 @@ command_listener((e) => {
|
|||||||
<AccountsCard ref="accounts" mode="small" />
|
<AccountsCard ref="accounts" mode="small" />
|
||||||
</suspense>
|
</suspense>
|
||||||
<div class="pages-list">
|
<div class="pages-list">
|
||||||
<RouterLink to="/" class="btn icon-only collapsed-button">
|
<RouterLink v-tooltip="'Home'" to="/" class="btn icon-only collapsed-button">
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
|
v-tooltip="'Browse'"
|
||||||
to="/browse/modpack"
|
to="/browse/modpack"
|
||||||
class="btn icon-only collapsed-button"
|
class="btn icon-only collapsed-button"
|
||||||
:class="{
|
:class="{
|
||||||
@@ -230,7 +231,7 @@ command_listener((e) => {
|
|||||||
>
|
>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<RouterLink to="/library" class="btn icon-only collapsed-button">
|
<RouterLink v-tooltip="'Library'" to="/library" class="btn icon-only collapsed-button">
|
||||||
<LibraryIcon />
|
<LibraryIcon />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<Suspense>
|
<Suspense>
|
||||||
@@ -240,6 +241,7 @@ command_listener((e) => {
|
|||||||
</div>
|
</div>
|
||||||
<div class="settings pages-list">
|
<div class="settings pages-list">
|
||||||
<Button
|
<Button
|
||||||
|
v-tooltip="'Create profile'"
|
||||||
class="sleek-primary collapsed-button"
|
class="sleek-primary collapsed-button"
|
||||||
icon-only
|
icon-only
|
||||||
:disabled="offline"
|
:disabled="offline"
|
||||||
@@ -247,7 +249,7 @@ command_listener((e) => {
|
|||||||
>
|
>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
<RouterLink to="/settings" class="btn icon-only collapsed-button">
|
<RouterLink v-tooltip="'Settings'" to="/settings" class="btn icon-only collapsed-button">
|
||||||
<SettingsIcon />
|
<SettingsIcon />
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
@@ -260,7 +262,7 @@ command_listener((e) => {
|
|||||||
</section>
|
</section>
|
||||||
<section class="mod-stats">
|
<section class="mod-stats">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<RunningAppBar data-tauri-drag-region />
|
<RunningAppBar />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -290,7 +292,7 @@ command_listener((e) => {
|
|||||||
offset-height="var(--appbar-height)"
|
offset-height="var(--appbar-height)"
|
||||||
offset-width="var(--sidebar-width)"
|
offset-width="var(--sidebar-width)"
|
||||||
/>
|
/>
|
||||||
<RouterView v-slot="{ Component }" class="main-view">
|
<RouterView v-slot="{ Component }">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
<Suspense @pending="loading.startLoading()" @resolve="loading.stopLoading()">
|
||||||
<component :is="Component"></component>
|
<component :is="Component"></component>
|
||||||
|
|||||||
1
theseus_gui/src/assets/icons/bug.svg
Normal file
1
theseus_gui/src/assets/icons/bug.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-bug"><rect width="8" height="14" x="8" y="6" rx="4"/><path d="m19 7-3 2"/><path d="m5 7 3 2"/><path d="m19 19-3-2"/><path d="m5 19 3-2"/><path d="M20 13h-4"/><path d="M4 13h4"/><path d="m10 4 1 2"/><path d="m14 4-1 2"/></svg>
|
||||||
|
After Width: | Height: | Size: 427 B |
@@ -10,3 +10,5 @@ export { default as TextInputIcon } from './text-cursor-input.svg'
|
|||||||
export { default as AddProjectImage } from './add-project.svg'
|
export { default as AddProjectImage } from './add-project.svg'
|
||||||
export { default as NewInstanceImage } from './new-instance.svg'
|
export { default as NewInstanceImage } from './new-instance.svg'
|
||||||
export { default as MenuIcon } from './menu.svg'
|
export { default as MenuIcon } from './menu.svg'
|
||||||
|
export { default as BugIcon } from './bug.svg'
|
||||||
|
export { default as ChatIcon } from './messages-square.svg'
|
||||||
|
|||||||
1
theseus_gui/src/assets/icons/messages-square.svg
Normal file
1
theseus_gui/src/assets/icons/messages-square.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-messages-square"><path d="M14 9a2 2 0 0 1-2 2H6l-4 4V4c0-1.1.9-2 2-2h8a2 2 0 0 1 2 2v5Z"/><path d="M18 9h2a2 2 0 0 1 2 2v11l-4-4h-6a2 2 0 0 1-2-2v-1"/></svg>
|
||||||
|
After Width: | Height: | Size: 359 B |
@@ -245,6 +245,7 @@ const filteredResults = computed(() => {
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="sortBy"
|
v-model="sortBy"
|
||||||
class="sort-dropdown"
|
class="sort-dropdown"
|
||||||
|
name="Sort Dropdown"
|
||||||
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
@@ -254,6 +255,7 @@ const filteredResults = computed(() => {
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="filters"
|
v-model="filters"
|
||||||
class="filter-dropdown"
|
class="filter-dropdown"
|
||||||
|
name="Filter Dropdown"
|
||||||
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
|
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
@@ -263,6 +265,7 @@ const filteredResults = computed(() => {
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="group"
|
v-model="group"
|
||||||
class="group-dropdown"
|
class="group-dropdown"
|
||||||
|
name="Group Dropdown"
|
||||||
:options="['Category', 'Loader', 'Game version', 'None']"
|
:options="['Category', 'Loader', 'Game version', 'None']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
<div
|
<div
|
||||||
v-if="mode !== 'isolated'"
|
v-if="mode !== 'isolated'"
|
||||||
ref="button"
|
ref="button"
|
||||||
|
v-tooltip="'Minecraft accounts'"
|
||||||
class="button-base avatar-button"
|
class="button-base avatar-button"
|
||||||
:class="{ expanded: mode === 'expanded' }"
|
:class="{ expanded: mode === 'expanded' }"
|
||||||
@click="showCard = !showCard"
|
@click="showCard = !showCard"
|
||||||
@@ -14,15 +15,6 @@
|
|||||||
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
: 'https://launcher-files.modrinth.com/assets/steve_head.png'
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-show="mode === 'expanded'" class="avatar-text">
|
|
||||||
<div class="text no-select">
|
|
||||||
{{ selectedAccount ? selectedAccount.username : 'Offline' }}
|
|
||||||
</div>
|
|
||||||
<p class="accounts-text no-select">
|
|
||||||
<UsersIcon />
|
|
||||||
Accounts
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<Card
|
<Card
|
||||||
@@ -64,10 +56,50 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</Card>
|
</Card>
|
||||||
</transition>
|
</transition>
|
||||||
|
<Modal ref="loginModal" class="modal" 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>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Avatar, Button, Card, PlusIcon, TrashIcon, UsersIcon, LogInIcon } from 'omorphia'
|
import {
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Card,
|
||||||
|
PlusIcon,
|
||||||
|
TrashIcon,
|
||||||
|
LogInIcon,
|
||||||
|
Modal,
|
||||||
|
GlobeIcon,
|
||||||
|
ClipboardCopyIcon,
|
||||||
|
} from 'omorphia'
|
||||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import {
|
import {
|
||||||
users,
|
users,
|
||||||
@@ -76,10 +108,10 @@ import {
|
|||||||
authenticate_await_completion,
|
authenticate_await_completion,
|
||||||
} from '@/helpers/auth'
|
} from '@/helpers/auth'
|
||||||
import { get, set } from '@/helpers/settings'
|
import { get, set } from '@/helpers/settings'
|
||||||
import { WebviewWindow } from '@tauri-apps/api/window'
|
|
||||||
import { handleError } from '@/store/state.js'
|
import { handleError } from '@/store/state.js'
|
||||||
import { get as getCreds, login_minecraft } from '@/helpers/mr_auth'
|
import { get as getCreds, login_minecraft } from '@/helpers/mr_auth'
|
||||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||||
|
import QrcodeVue from 'qrcode.vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
mode: {
|
mode: {
|
||||||
@@ -93,6 +125,9 @@ const emit = defineEmits(['change'])
|
|||||||
|
|
||||||
const settings = ref({})
|
const settings = ref({})
|
||||||
const accounts = ref([])
|
const accounts = ref([])
|
||||||
|
const loginUrl = ref('')
|
||||||
|
const loginModal = ref(null)
|
||||||
|
|
||||||
async function refreshValues() {
|
async function refreshValues() {
|
||||||
settings.value = await get().catch(handleError)
|
settings.value = await get().catch(handleError)
|
||||||
accounts.value = await users().catch(handleError)
|
accounts.value = await users().catch(handleError)
|
||||||
@@ -118,12 +153,18 @@ async function setAccount(account) {
|
|||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
const url = await authenticate_begin_flow().catch(handleError)
|
const url = await authenticate_begin_flow().catch(handleError)
|
||||||
|
loginUrl.value = url
|
||||||
|
|
||||||
const window = new WebviewWindow('loginWindow', {
|
await window.__TAURI_INVOKE__('tauri', {
|
||||||
title: 'Modrinth App',
|
__tauriModule: 'Shell',
|
||||||
url: url,
|
message: {
|
||||||
|
cmd: 'open',
|
||||||
|
path: url,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
loginModal.value.show()
|
||||||
|
|
||||||
const loggedIn = await authenticate_await_completion().catch(handleError)
|
const loggedIn = await authenticate_await_completion().catch(handleError)
|
||||||
|
|
||||||
if (loggedIn && loggedIn[0]) {
|
if (loggedIn && loggedIn[0]) {
|
||||||
@@ -139,7 +180,8 @@ async function login() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await window.close()
|
|
||||||
|
loginModal.value.hide()
|
||||||
mixpanel_track('AccountLogIn')
|
mixpanel_track('AccountLogIn')
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,4 +371,37 @@ onBeforeUnmount(() => {
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="breadcrumbs">
|
<div class="breadcrumbs">
|
||||||
|
<Button class="breadcrumbs__back transparent" icon-only @click="$router.back()">
|
||||||
|
<ChevronLeftIcon />
|
||||||
|
</Button>
|
||||||
|
<Button class="breadcrumbs__forward transparent" icon-only @click="$router.forward()">
|
||||||
|
<ChevronRightIcon />
|
||||||
|
</Button>
|
||||||
{{ breadcrumbData.resetToNames(breadcrumbs) }}
|
{{ breadcrumbData.resetToNames(breadcrumbs) }}
|
||||||
<div v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name" class="breadcrumbs__item">
|
<div v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name" class="breadcrumbs__item">
|
||||||
<router-link
|
<router-link
|
||||||
@@ -25,7 +31,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ChevronRightIcon } from 'omorphia'
|
import { ChevronRightIcon, Button, ChevronLeftIcon } from 'omorphia'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -60,6 +66,18 @@ const breadcrumbs = computed(() => {
|
|||||||
margin: auto 0;
|
margin: auto 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.breadcrumbs__back,
|
||||||
|
.breadcrumbs__forward {
|
||||||
|
margin: auto 0;
|
||||||
|
color: var(--color-base);
|
||||||
|
height: unset;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumbs__forward {
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
v-model="selectedVersion"
|
v-model="selectedVersion"
|
||||||
:options="versions"
|
:options="versions"
|
||||||
placeholder="Select version"
|
placeholder="Select version"
|
||||||
|
name="Version select"
|
||||||
:display-name="
|
:display-name="
|
||||||
(version) =>
|
(version) =>
|
||||||
`${version?.name} (${version?.loaders
|
`${version?.name} (${version?.loaders
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="action-groups">
|
<div class="action-groups">
|
||||||
|
<a href="https://discord.gg/modrinth" class="link">
|
||||||
|
<ChatIcon />
|
||||||
|
<span> Get support </span>
|
||||||
|
</a>
|
||||||
<Button
|
<Button
|
||||||
v-if="currentLoadingBars.length > 0"
|
v-if="currentLoadingBars.length > 0"
|
||||||
ref="infoButton"
|
ref="infoButton"
|
||||||
@@ -120,6 +124,7 @@ import { refreshOffline, isOffline } from '@/helpers/utils.js'
|
|||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||||
|
import { ChatIcon } from '@/assets/icons'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const card = ref(null)
|
const card = ref(null)
|
||||||
@@ -266,7 +271,7 @@ onBeforeUnmount(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--gap-sm);
|
gap: var(--gap-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
@@ -452,4 +457,14 @@ onBeforeUnmount(() => {
|
|||||||
transform: translateY(-100%);
|
transform: translateY(-100%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.link {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--gap-sm);
|
||||||
|
margin: 0;
|
||||||
|
color: var(--color-text);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="action-groups">
|
<div class="action-groups">
|
||||||
|
<Button color="danger" outline @click="exit">
|
||||||
|
<LogOutIcon />
|
||||||
|
Exit tutorial
|
||||||
|
</Button>
|
||||||
<Button v-if="showDownload" ref="infoButton" icon-only class="icon-button show-card-icon">
|
<Button v-if="showDownload" ref="infoButton" icon-only class="icon-button show-card-icon">
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -36,7 +40,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Button, DownloadIcon, Card, StopCircleIcon, TerminalSquareIcon } from 'omorphia'
|
import {
|
||||||
|
Button,
|
||||||
|
DownloadIcon,
|
||||||
|
Card,
|
||||||
|
StopCircleIcon,
|
||||||
|
TerminalSquareIcon,
|
||||||
|
LogOutIcon,
|
||||||
|
} from 'omorphia'
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
@@ -48,6 +59,10 @@ defineProps({
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
|
exit: {
|
||||||
|
type: Function,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ defineProps({
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="sortBy"
|
v-model="sortBy"
|
||||||
class="sort-dropdown"
|
class="sort-dropdown"
|
||||||
|
name="Sort Dropdown"
|
||||||
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
@@ -41,6 +42,7 @@ defineProps({
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="filters"
|
v-model="filters"
|
||||||
class="filter-dropdown"
|
class="filter-dropdown"
|
||||||
|
name="Filter Dropdown"
|
||||||
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
|
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
@@ -50,6 +52,7 @@ defineProps({
|
|||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="group"
|
v-model="group"
|
||||||
class="group-dropdown"
|
class="group-dropdown"
|
||||||
|
name="Group dropdown"
|
||||||
:options="['Category', 'Loader', 'Game version', 'None']"
|
:options="['Category', 'Loader', 'Game version', 'None']"
|
||||||
placeholder="Select..."
|
placeholder="Select..."
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ defineProps({
|
|||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="sm"
|
size="sm"
|
||||||
src="https://launcher-files.modrinth.com/assets/maze-bg.png"
|
src="https://launcher-files.modrinth.com/assets/default_profile.png"
|
||||||
alt="Mod card"
|
alt="Mod card"
|
||||||
class="mod-image"
|
class="mod-image"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ defineProps({
|
|||||||
</Card>
|
</Card>
|
||||||
</aside>
|
</aside>
|
||||||
<div ref="searchWrapper" class="search">
|
<div ref="searchWrapper" class="search">
|
||||||
<Promotion class="promotion" />
|
<Promotion class="promotion" query-param="?r=launcher" />
|
||||||
<Card class="project-type-container">
|
<Card class="project-type-container">
|
||||||
<NavRow :links="selectableProjectTypes" />
|
<NavRow :links="selectableProjectTypes" />
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import {
|
|||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
Notifications,
|
Notifications,
|
||||||
LogOutIcon,
|
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { appWindow } from '@tauri-apps/api/window'
|
import { appWindow } from '@tauri-apps/api/window'
|
||||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||||
@@ -160,7 +159,10 @@ onMounted(async () => {
|
|||||||
<div class="btn icon-only" :class="{ active: phase < 4 }">
|
<div class="btn icon-only" :class="{ active: phase < 4 }">
|
||||||
<HomeIcon />
|
<HomeIcon />
|
||||||
</div>
|
</div>
|
||||||
<div class="btn icon-only" :class="{ active: phase === 4 || phase === 5 }">
|
<div
|
||||||
|
class="btn icon-only"
|
||||||
|
:class="{ active: phase === 4 || phase === 5, highlighted: phase === 4 }"
|
||||||
|
>
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -175,9 +177,6 @@ onMounted(async () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings pages-list">
|
<div class="settings pages-list">
|
||||||
<Button class="active" icon-only @click="finishOnboarding">
|
|
||||||
<LogOutIcon />
|
|
||||||
</Button>
|
|
||||||
<Button class="sleek-primary" icon-only>
|
<Button class="sleek-primary" icon-only>
|
||||||
<PlusIcon />
|
<PlusIcon />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -192,7 +191,11 @@ onMounted(async () => {
|
|||||||
<Breadcrumbs data-tauri-drag-region />
|
<Breadcrumbs data-tauri-drag-region />
|
||||||
</section>
|
</section>
|
||||||
<section class="mod-stats">
|
<section class="mod-stats">
|
||||||
<FakeAppBar :show-running="phase === 7" :show-download="phase === 5">
|
<FakeAppBar
|
||||||
|
:show-running="phase === 7"
|
||||||
|
:show-download="phase === 5"
|
||||||
|
:exit="finishOnboarding"
|
||||||
|
>
|
||||||
<template #running>
|
<template #running>
|
||||||
<TutorialTip
|
<TutorialTip
|
||||||
:progress-function="nextPhase"
|
:progress-function="nextPhase"
|
||||||
@@ -430,12 +433,6 @@ onMounted(async () => {
|
|||||||
background-color: var(--color-brand-highlight);
|
background-color: var(--color-brand-highlight);
|
||||||
transition: all ease-in-out 0.1s;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ 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'
|
||||||
import { check_installed, get as getInstance } from '@/helpers/profile.js'
|
import { check_installed, get, get as getInstance } from '@/helpers/profile.js'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import { isOffline } from '@/helpers/utils'
|
import { isOffline } from '@/helpers/utils'
|
||||||
import { offline_listener } from '@/helpers/events'
|
import { offline_listener } from '@/helpers/events'
|
||||||
@@ -56,6 +56,7 @@ const orFacets = ref([])
|
|||||||
const selectedVersions = ref([])
|
const selectedVersions = ref([])
|
||||||
const onlyOpenSource = ref(false)
|
const onlyOpenSource = ref(false)
|
||||||
const showSnapshots = ref(false)
|
const showSnapshots = ref(false)
|
||||||
|
const hideAlreadyInstalled = ref(false)
|
||||||
const selectedEnvironments = ref([])
|
const selectedEnvironments = ref([])
|
||||||
const sortTypes = readonly([
|
const sortTypes = readonly([
|
||||||
{ display: 'Relevance', name: 'relevance' },
|
{ display: 'Relevance', name: 'relevance' },
|
||||||
@@ -143,6 +144,9 @@ if (route.query.m) {
|
|||||||
if (route.query.o) {
|
if (route.query.o) {
|
||||||
currentPage.value = Math.ceil(route.query.o / maxResults.value) + 1
|
currentPage.value = Math.ceil(route.query.o / maxResults.value) + 1
|
||||||
}
|
}
|
||||||
|
if (route.query.ai) {
|
||||||
|
hideAlreadyInstalled.value = route.query.ai === 'true'
|
||||||
|
}
|
||||||
|
|
||||||
async function refreshSearch() {
|
async function refreshSearch() {
|
||||||
const base = 'https://api.modrinth.com/v2/'
|
const base = 'https://api.modrinth.com/v2/'
|
||||||
@@ -222,6 +226,16 @@ async function refreshSearch() {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hideAlreadyInstalled.value) {
|
||||||
|
const installedMods = await get(instanceContext.value.path, false).then((x) =>
|
||||||
|
Object.values(x.projects)
|
||||||
|
.filter((x) => x.metadata.project)
|
||||||
|
.map((x) => x.metadata.project.id)
|
||||||
|
)
|
||||||
|
installedMods.map((x) => [`project_id != ${x}`]).forEach((x) => formattedFacets.push(x))
|
||||||
|
console.log(`facets=${JSON.stringify(formattedFacets)}`)
|
||||||
|
}
|
||||||
|
|
||||||
params.push(`facets=${JSON.stringify(formattedFacets)}`)
|
params.push(`facets=${JSON.stringify(formattedFacets)}`)
|
||||||
}
|
}
|
||||||
const offset = (currentPage.value - 1) * maxResults.value
|
const offset = (currentPage.value - 1) * maxResults.value
|
||||||
@@ -339,6 +353,10 @@ function getSearchUrl(offset, useObj) {
|
|||||||
queryItems.push('il=true')
|
queryItems.push('il=true')
|
||||||
obj.il = true
|
obj.il = true
|
||||||
}
|
}
|
||||||
|
if (hideAlreadyInstalled.value) {
|
||||||
|
queryItems.push('ai=true')
|
||||||
|
obj.ai = true
|
||||||
|
}
|
||||||
|
|
||||||
let url = `${route.path}`
|
let url = `${route.path}`
|
||||||
|
|
||||||
@@ -555,6 +573,13 @@ onUnmounted(() => unlistenOffline())
|
|||||||
@update:model-value="onSearchChangeToTop(1)"
|
@update:model-value="onSearchChangeToTop(1)"
|
||||||
@click.prevent.stop
|
@click.prevent.stop
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
v-model="hideAlreadyInstalled"
|
||||||
|
label="Hide already installed"
|
||||||
|
class="filter-checkbox"
|
||||||
|
@update:model-value="onSearchChangeToTop(1)"
|
||||||
|
@click.prevent.stop
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="search-panel-card">
|
<Card class="search-panel-card">
|
||||||
<Button
|
<Button
|
||||||
@@ -662,7 +687,7 @@ onUnmounted(() => unlistenOffline())
|
|||||||
</Card>
|
</Card>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Promotion class="promotion" :external="false" />
|
<Promotion class="promotion" :external="false" query-param="?r=launcher" />
|
||||||
<Card class="project-type-container">
|
<Card class="project-type-container">
|
||||||
<NavRow :links="selectableProjectTypes" />
|
<NavRow :links="selectableProjectTypes" />
|
||||||
</Card>
|
</Card>
|
||||||
@@ -711,7 +736,7 @@ onUnmounted(() => unlistenOffline())
|
|||||||
@switch-page="onSearchChange"
|
@switch-page="onSearchChange"
|
||||||
/>
|
/>
|
||||||
<SplashScreen v-if="loading" />
|
<SplashScreen v-if="loading" />
|
||||||
<section v-else-if="offline && results.total_hits == 0" class="offline">
|
<section v-else-if="offline && results.total_hits === 0" class="offline">
|
||||||
You are currently offline. Connect to the internet to browse Modrinth!
|
You are currently offline. Connect to the internet to browse Modrinth!
|
||||||
</section>
|
</section>
|
||||||
<section v-else class="project-list display-mode--list instance-results" role="list">
|
<section v-else class="project-list display-mode--list instance-results" role="list">
|
||||||
|
|||||||
@@ -35,12 +35,7 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<GridDisplay
|
<GridDisplay v-if="instances.length > 0" label="Instances" :instances="instances" />
|
||||||
v-if="instances.length > 0"
|
|
||||||
label="Instances"
|
|
||||||
:instances="instances"
|
|
||||||
class="display"
|
|
||||||
/>
|
|
||||||
<div v-else class="no-instance">
|
<div v-else class="no-instance">
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<NewInstanceImage />
|
<NewInstanceImage />
|
||||||
@@ -55,11 +50,6 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.display {
|
|
||||||
background-color: rgb(30, 31, 34);
|
|
||||||
min-height: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.no-instance {
|
.no-instance {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -308,7 +308,8 @@ async function refreshDir() {
|
|||||||
<span class="label__title">Disable analytics</span>
|
<span class="label__title">Disable analytics</span>
|
||||||
<span class="label__description">
|
<span class="label__description">
|
||||||
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||||
customize your experience. Opting out will disable this data collection.
|
customize your experience. By enabling this option, you opt out and your data will no
|
||||||
|
longer be collected.
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<Toggle
|
<Toggle
|
||||||
|
|||||||
@@ -72,17 +72,10 @@
|
|||||||
Options
|
Options
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<hr class="card-divider" />
|
|
||||||
<div class="pages-list">
|
|
||||||
<Button class="transparent" @click="exportModal.show()">
|
|
||||||
<PackageIcon />
|
|
||||||
Export modpack
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<Promotion />
|
<Promotion query-param="?r=launcher" />
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
||||||
@@ -118,7 +111,6 @@
|
|||||||
>
|
>
|
||||||
<template #filter_update><UpdatedIcon />Select Updatable</template>
|
<template #filter_update><UpdatedIcon />Select Updatable</template>
|
||||||
</ContextMenu>
|
</ContextMenu>
|
||||||
<ExportModal ref="exportModal" :instance="instance" />
|
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
@@ -156,15 +148,12 @@ import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
|||||||
import { isOffline, showProfileInFolder } from '@/helpers/utils.js'
|
import { isOffline, showProfileInFolder } from '@/helpers/utils.js'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||||
import { PackageIcon } from '@/assets/icons/index.js'
|
|
||||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
const exportModal = ref(null)
|
|
||||||
|
|
||||||
const instance = ref(await get(route.params.id).catch(handleError))
|
const instance = ref(await get(route.params.id).catch(handleError))
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card
|
<Card v-if="projects.length > 0" class="mod-card">
|
||||||
v-if="projects.length > 0"
|
<div class="dropdown-input">
|
||||||
class="mod-card"
|
<DropdownSelect
|
||||||
:class="{ static: instance.metadata.linked_data }"
|
|
||||||
>
|
|
||||||
<div class="second-row">
|
|
||||||
<Chips
|
|
||||||
v-if="Object.keys(selectableProjectTypes).length > 1"
|
|
||||||
v-model="selectedProjectType"
|
v-model="selectedProjectType"
|
||||||
:items="Object.keys(selectableProjectTypes)"
|
:options="Object.keys(selectableProjectTypes)"
|
||||||
|
default-value="All"
|
||||||
|
name="project-type-dropdown"
|
||||||
|
color="primary"
|
||||||
/>
|
/>
|
||||||
<Button
|
|
||||||
v-if="canUpdatePack"
|
|
||||||
:disabled="updatingModpack"
|
|
||||||
color="secondary"
|
|
||||||
@click="updateModpack"
|
|
||||||
>
|
|
||||||
<UpdatedIcon />
|
|
||||||
{{ updatingModpack ? 'Updating' : 'Update modpack' }}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div class="card-row">
|
|
||||||
<div class="iconified-input">
|
<div class="iconified-input">
|
||||||
<SearchIcon />
|
<SearchIcon />
|
||||||
<input
|
<input
|
||||||
@@ -37,228 +24,253 @@
|
|||||||
<XIcon />
|
<XIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<span class="manage">
|
|
||||||
<DropdownButton
|
|
||||||
:options="['search', 'from_file']"
|
|
||||||
default-value="search"
|
|
||||||
name="add-content-dropdown"
|
|
||||||
color="primary"
|
|
||||||
@option-click="handleContentOptionClick"
|
|
||||||
>
|
|
||||||
<template #search>
|
|
||||||
<SearchIcon />
|
|
||||||
<span class="no-wrap"> Add content </span>
|
|
||||||
</template>
|
|
||||||
<template #from_file>
|
|
||||||
<FolderOpenIcon />
|
|
||||||
<span class="no-wrap"> Add from file </span>
|
|
||||||
</template>
|
|
||||||
</DropdownButton>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<Button
|
||||||
<div class="table">
|
v-if="isPackLinked"
|
||||||
<div class="table-row table-head" :class="{ 'show-options': selected.length > 0 }">
|
v-tooltip="'Modpack is up to date'"
|
||||||
<div v-if="!instance.metadata.linked_data" class="table-cell table-text">
|
:disabled="updatingModpack || !canUpdatePack"
|
||||||
<Checkbox v-model="selectAll" class="select-checkbox" />
|
color="secondary"
|
||||||
</div>
|
@click="updateModpack"
|
||||||
<div v-if="selected.length === 0" class="table-cell table-text name-cell actions-cell">
|
>
|
||||||
<Button class="transparent" @click="sortProjects('Name')">
|
<UpdatedIcon />
|
||||||
Name
|
{{ updatingModpack ? 'Updating' : 'Update modpack' }}
|
||||||
<DropdownIcon v-if="sortColumn === 'Name'" :class="{ down: ascending }" />
|
</Button>
|
||||||
</Button>
|
<Button v-else @click="exportModal.show()">
|
||||||
</div>
|
<PackageIcon />
|
||||||
<div v-if="selected.length === 0" class="table-cell table-text version">
|
Export modpack
|
||||||
<Button class="transparent" @click="sortProjects('Version')">
|
</Button>
|
||||||
Version
|
<DropdownButton
|
||||||
<DropdownIcon v-if="sortColumn === 'Version'" :class="{ down: ascending }" />
|
v-if="!isPackLinked"
|
||||||
</Button>
|
:options="['search', 'from_file']"
|
||||||
</div>
|
default-value="search"
|
||||||
<div v-if="selected.length === 0" class="table-cell table-text actions-cell">
|
name="add-content-dropdown"
|
||||||
<Button
|
color="primary"
|
||||||
v-if="!instance.metadata.linked_data"
|
@option-click="handleContentOptionClick"
|
||||||
class="transparent"
|
>
|
||||||
@click="sortProjects('Enabled')"
|
<template #search>
|
||||||
>
|
<SearchIcon />
|
||||||
Actions
|
<span class="no-wrap"> Add content </span>
|
||||||
<DropdownIcon v-if="sortColumn === 'Enabled'" :class="{ down: ascending }" />
|
</template>
|
||||||
</Button>
|
<template #from_file>
|
||||||
</div>
|
<FolderOpenIcon />
|
||||||
<div v-else-if="!instance.metadata.linked_data" class="options table-cell name-cell">
|
<span class="no-wrap"> Add from file </span>
|
||||||
<Button
|
</template>
|
||||||
class="transparent share"
|
</DropdownButton>
|
||||||
@click="() => (showingOptions = !showingOptions)"
|
</Card>
|
||||||
@mouseover="selectedOption = 'Share'"
|
<Pagination
|
||||||
>
|
v-if="projects.length > 0"
|
||||||
<MenuIcon :class="{ open: showingOptions }" />
|
:page="currentPage"
|
||||||
</Button>
|
:count="Math.ceil(search.length / 20)"
|
||||||
<Button
|
class="pagination-before"
|
||||||
class="transparent share"
|
:link-function="(page) => `?page=${page}`"
|
||||||
@click="shareNames()"
|
@switch-page="switchPage"
|
||||||
@mouseover="selectedOption = 'Share'"
|
/>
|
||||||
>
|
<Card
|
||||||
<ShareIcon />
|
v-if="projects.length > 0"
|
||||||
Share
|
class="list-card"
|
||||||
</Button>
|
:class="{ static: instance.metadata.linked_data }"
|
||||||
<Button
|
>
|
||||||
class="transparent trash"
|
<div class="table">
|
||||||
@click="deleteWarning.show()"
|
<div class="table-row table-head" :class="{ 'show-options': selected.length > 0 }">
|
||||||
@mouseover="selectedOption = 'Delete'"
|
<div v-if="!instance.metadata.linked_data" class="table-cell table-text">
|
||||||
>
|
<Checkbox v-model="selectAll" class="select-checkbox" />
|
||||||
<TrashIcon />
|
|
||||||
Delete
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="transparent update"
|
|
||||||
:disabled="offline"
|
|
||||||
@click="updateAll()"
|
|
||||||
@mouseover="selectedOption = 'Update'"
|
|
||||||
>
|
|
||||||
<UpdatedIcon />
|
|
||||||
Update
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
class="transparent"
|
|
||||||
@click="toggleSelected()"
|
|
||||||
@mouseover="selectedOption = 'Toggle'"
|
|
||||||
>
|
|
||||||
<ToggleIcon />
|
|
||||||
Toggle
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="selected.length === 0" class="table-cell table-text name-cell actions-cell">
|
||||||
v-if="showingOptions && selected.length > 0 && !instance.metadata.linked_data"
|
<Button class="transparent" @click="sortProjects('Name')">
|
||||||
class="more-box"
|
Name
|
||||||
>
|
<DropdownIcon v-if="sortColumn === 'Name'" :class="{ down: ascending }" />
|
||||||
<section v-if="selectedOption === 'Share'" class="options">
|
</Button>
|
||||||
<Button class="transparent" @click="shareNames()">
|
|
||||||
<TextInputIcon />
|
|
||||||
Share names
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="shareUrls()">
|
|
||||||
<GlobeIcon />
|
|
||||||
Share URLs
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="shareFileNames()">
|
|
||||||
<FileIcon />
|
|
||||||
Share file names
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="shareMarkdown()">
|
|
||||||
<CodeIcon />
|
|
||||||
Share as markdown
|
|
||||||
</Button>
|
|
||||||
</section>
|
|
||||||
<section v-if="selectedOption === 'Delete'" class="options">
|
|
||||||
<Button class="transparent" @click="deleteWarning.show()">
|
|
||||||
<TrashIcon />
|
|
||||||
Delete selected
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="deleteDisabledWarning.show()">
|
|
||||||
<ToggleIcon />
|
|
||||||
Delete disabled
|
|
||||||
</Button>
|
|
||||||
</section>
|
|
||||||
<section v-if="selectedOption === 'Update'" class="options">
|
|
||||||
<Button class="transparent" :disabled="offline" @click="updateAll()">
|
|
||||||
<UpdatedIcon />
|
|
||||||
Update all
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="selectUpdatable()">
|
|
||||||
<CheckIcon />
|
|
||||||
Select updatable
|
|
||||||
</Button>
|
|
||||||
</section>
|
|
||||||
<section v-if="selectedOption === 'Toggle'" class="options">
|
|
||||||
<Button class="transparent" @click="enableAll()">
|
|
||||||
<CheckIcon />
|
|
||||||
Toggle on
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="disableAll()">
|
|
||||||
<XIcon />
|
|
||||||
Toggle off
|
|
||||||
</Button>
|
|
||||||
<Button class="transparent" @click="hideShowAll()">
|
|
||||||
<EyeIcon v-if="hideNonSelected" />
|
|
||||||
<EyeOffIcon v-else />
|
|
||||||
{{ hideNonSelected ? 'Show' : 'Hide' }} untoggled
|
|
||||||
</Button>
|
|
||||||
</section>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div v-if="selected.length === 0" class="table-cell table-text version">
|
||||||
v-for="mod in search"
|
<Button class="transparent" @click="sortProjects('Version')">
|
||||||
:key="mod.file_name"
|
Version
|
||||||
class="table-row"
|
<DropdownIcon v-if="sortColumn === 'Version'" :class="{ down: ascending }" />
|
||||||
@contextmenu.prevent.stop="(c) => handleRightClick(c, mod)"
|
</Button>
|
||||||
>
|
</div>
|
||||||
<div v-if="!instance.metadata.linked_data" class="table-cell table-text checkbox">
|
<div v-if="selected.length === 0" class="table-cell table-text actions-cell">
|
||||||
<Checkbox
|
<Button
|
||||||
:model-value="selectionMap.get(mod.path)"
|
v-if="!instance.metadata.linked_data"
|
||||||
class="select-checkbox"
|
class="transparent"
|
||||||
@update:model-value="(newValue) => selectionMap.set(mod.path, newValue)"
|
@click="sortProjects('Enabled')"
|
||||||
/>
|
>
|
||||||
</div>
|
Actions
|
||||||
<div class="table-cell table-text name-cell">
|
<DropdownIcon v-if="sortColumn === 'Enabled'" :class="{ down: ascending }" />
|
||||||
<router-link
|
</Button>
|
||||||
v-if="mod.slug"
|
</div>
|
||||||
:to="{ path: `/project/${mod.slug}/`, query: { i: props.instance.path } }"
|
<div v-else-if="!instance.metadata.linked_data" class="options table-cell name-cell">
|
||||||
:disabled="offline"
|
<Button
|
||||||
class="mod-content"
|
class="transparent share"
|
||||||
>
|
@click="() => (showingOptions = !showingOptions)"
|
||||||
<Avatar :src="mod.icon" />
|
@mouseover="selectedOption = 'Share'"
|
||||||
<div v-tooltip="`${mod.name} by ${mod.author}`" class="mod-text">
|
>
|
||||||
<div class="title">{{ mod.name }}</div>
|
<MenuIcon :class="{ open: showingOptions }" />
|
||||||
<span class="no-wrap">by {{ mod.author }}</span>
|
</Button>
|
||||||
</div>
|
<Button
|
||||||
</router-link>
|
class="transparent share"
|
||||||
<div v-else class="mod-content">
|
@click="shareNames()"
|
||||||
<Avatar :src="mod.icon" />
|
@mouseover="selectedOption = 'Share'"
|
||||||
<span v-tooltip="`${mod.name}`" class="title">{{ mod.name }}</span>
|
>
|
||||||
|
<ShareIcon />
|
||||||
|
Share
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="transparent trash"
|
||||||
|
@click="deleteWarning.show()"
|
||||||
|
@mouseover="selectedOption = 'Delete'"
|
||||||
|
>
|
||||||
|
<TrashIcon />
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="transparent update"
|
||||||
|
:disabled="offline"
|
||||||
|
@click="updateAll()"
|
||||||
|
@mouseover="selectedOption = 'Update'"
|
||||||
|
>
|
||||||
|
<UpdatedIcon />
|
||||||
|
Update
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
class="transparent"
|
||||||
|
@click="toggleSelected()"
|
||||||
|
@mouseover="selectedOption = 'Toggle'"
|
||||||
|
>
|
||||||
|
<ToggleIcon />
|
||||||
|
Toggle
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="showingOptions && selected.length > 0 && !instance.metadata.linked_data"
|
||||||
|
class="more-box"
|
||||||
|
>
|
||||||
|
<section v-if="selectedOption === 'Share'" class="options">
|
||||||
|
<Button class="transparent" @click="shareNames()">
|
||||||
|
<TextInputIcon />
|
||||||
|
Share names
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="shareUrls()">
|
||||||
|
<GlobeIcon />
|
||||||
|
Share URLs
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="shareFileNames()">
|
||||||
|
<FileIcon />
|
||||||
|
Share file names
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="shareMarkdown()">
|
||||||
|
<CodeIcon />
|
||||||
|
Share as markdown
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section v-if="selectedOption === 'Delete'" class="options">
|
||||||
|
<Button class="transparent" @click="deleteWarning.show()">
|
||||||
|
<TrashIcon />
|
||||||
|
Delete selected
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="deleteDisabledWarning.show()">
|
||||||
|
<ToggleIcon />
|
||||||
|
Delete disabled
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section v-if="selectedOption === 'Update'" class="options">
|
||||||
|
<Button class="transparent" :disabled="offline" @click="updateAll()">
|
||||||
|
<UpdatedIcon />
|
||||||
|
Update all
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="selectUpdatable()">
|
||||||
|
<CheckIcon />
|
||||||
|
Select updatable
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
<section v-if="selectedOption === 'Toggle'" class="options">
|
||||||
|
<Button class="transparent" @click="enableAll()">
|
||||||
|
<CheckIcon />
|
||||||
|
Toggle on
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="disableAll()">
|
||||||
|
<XIcon />
|
||||||
|
Toggle off
|
||||||
|
</Button>
|
||||||
|
<Button class="transparent" @click="hideShowAll()">
|
||||||
|
<EyeIcon v-if="hideNonSelected" />
|
||||||
|
<EyeOffIcon v-else />
|
||||||
|
{{ hideNonSelected ? 'Show' : 'Hide' }} untoggled
|
||||||
|
</Button>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="mod in search.slice((currentPage - 1) * 20, currentPage * 20)"
|
||||||
|
:key="mod.file_name"
|
||||||
|
class="table-row"
|
||||||
|
@contextmenu.prevent.stop="(c) => handleRightClick(c, mod)"
|
||||||
|
>
|
||||||
|
<div v-if="!instance.metadata.linked_data" class="table-cell table-text checkbox">
|
||||||
|
<Checkbox
|
||||||
|
:model-value="selectionMap.get(mod.path)"
|
||||||
|
class="select-checkbox"
|
||||||
|
@update:model-value="(newValue) => selectionMap.set(mod.path, newValue)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="table-cell table-text name-cell">
|
||||||
|
<router-link
|
||||||
|
v-if="mod.slug"
|
||||||
|
:to="{ path: `/project/${mod.slug}/`, query: { i: props.instance.path } }"
|
||||||
|
:disabled="offline"
|
||||||
|
class="mod-content"
|
||||||
|
>
|
||||||
|
<Avatar :src="mod.icon" />
|
||||||
|
<div v-tooltip="`${mod.name} by ${mod.author}`" class="mod-text">
|
||||||
|
<div class="title">{{ mod.name }}</div>
|
||||||
|
<span class="no-wrap">by {{ mod.author }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</router-link>
|
||||||
|
<div v-else class="mod-content">
|
||||||
|
<Avatar :src="mod.icon" />
|
||||||
|
<span v-tooltip="`${mod.name}`" class="title">{{ mod.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-cell table-text version">
|
</div>
|
||||||
<span v-tooltip="`${mod.version}`">{{ mod.version }}</span>
|
<div class="table-cell table-text version">
|
||||||
</div>
|
<span v-tooltip="`${mod.version}`">{{ mod.version }}</span>
|
||||||
<div class="table-cell table-text manage">
|
</div>
|
||||||
<Button
|
<div class="table-cell table-text manage">
|
||||||
v-if="!instance.metadata.linked_data"
|
<Button
|
||||||
v-tooltip="'Remove project'"
|
v-if="!instance.metadata.linked_data"
|
||||||
icon-only
|
v-tooltip="'Remove project'"
|
||||||
@click="removeMod(mod)"
|
icon-only
|
||||||
>
|
@click="removeMod(mod)"
|
||||||
<TrashIcon />
|
>
|
||||||
</Button>
|
<TrashIcon />
|
||||||
<AnimatedLogo
|
</Button>
|
||||||
v-if="mod.updating && !instance.metadata.linked_data"
|
<AnimatedLogo
|
||||||
class="btn icon-only updating-indicator"
|
v-if="mod.updating && !instance.metadata.linked_data"
|
||||||
></AnimatedLogo>
|
class="btn icon-only updating-indicator"
|
||||||
<Button
|
></AnimatedLogo>
|
||||||
v-else-if="!instance.metadata.linked_data"
|
<Button
|
||||||
v-tooltip="'Update project'"
|
v-else-if="!instance.metadata.linked_data"
|
||||||
:disabled="!mod.outdated || offline"
|
v-tooltip="'Update project'"
|
||||||
icon-only
|
:disabled="!mod.outdated || offline"
|
||||||
@click="updateProject(mod)"
|
icon-only
|
||||||
>
|
@click="updateProject(mod)"
|
||||||
<UpdatedIcon v-if="mod.outdated" />
|
>
|
||||||
<CheckIcon v-else />
|
<UpdatedIcon v-if="mod.outdated" />
|
||||||
</Button>
|
<CheckIcon v-else />
|
||||||
<input
|
</Button>
|
||||||
v-if="!instance.metadata.linked_data"
|
<input
|
||||||
id="switch-1"
|
v-if="!instance.metadata.linked_data"
|
||||||
autocomplete="off"
|
id="switch-1"
|
||||||
type="checkbox"
|
autocomplete="off"
|
||||||
class="switch stylized-toggle"
|
type="checkbox"
|
||||||
:checked="!mod.disabled"
|
class="switch stylized-toggle"
|
||||||
@change="toggleDisableMod(mod)"
|
:checked="!mod.disabled"
|
||||||
/>
|
@change="toggleDisableMod(mod)"
|
||||||
<Button
|
/>
|
||||||
v-tooltip="`Show ${mod.file_name}`"
|
<Button
|
||||||
icon-only
|
v-tooltip="`Show ${mod.file_name}`"
|
||||||
@click="showProfileInFolder(mod.path)"
|
icon-only
|
||||||
>
|
@click="showProfileInFolder(mod.path)"
|
||||||
<FolderOpenIcon />
|
>
|
||||||
</Button>
|
<FolderOpenIcon />
|
||||||
</div>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -335,6 +347,7 @@
|
|||||||
share-title="Sharing modpack content"
|
share-title="Sharing modpack content"
|
||||||
share-text="Check out the projects I'm using in my modpack!"
|
share-text="Check out the projects I'm using in my modpack!"
|
||||||
/>
|
/>
|
||||||
|
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
@@ -346,7 +359,6 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
AnimatedLogo,
|
AnimatedLogo,
|
||||||
Chips,
|
|
||||||
FolderOpenIcon,
|
FolderOpenIcon,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
formatProjectType,
|
formatProjectType,
|
||||||
@@ -361,6 +373,8 @@ import {
|
|||||||
EyeOffIcon,
|
EyeOffIcon,
|
||||||
ShareModal,
|
ShareModal,
|
||||||
CodeIcon,
|
CodeIcon,
|
||||||
|
Pagination,
|
||||||
|
DropdownSelect,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { computed, onUnmounted, ref, watch } from 'vue'
|
import { computed, onUnmounted, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -379,7 +393,8 @@ import { open } from '@tauri-apps/api/dialog'
|
|||||||
import { listen } from '@tauri-apps/api/event'
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage } from '@/assets/icons'
|
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||||
|
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -407,12 +422,13 @@ const props = defineProps({
|
|||||||
const projects = ref([])
|
const projects = ref([])
|
||||||
const selectionMap = ref(new Map())
|
const selectionMap = ref(new Map())
|
||||||
const showingOptions = ref(false)
|
const showingOptions = ref(false)
|
||||||
const canUpdatePack = computed(() => {
|
const isPackLinked = computed(() => {
|
||||||
return (
|
return props.instance.metadata.linked_data
|
||||||
props.instance.metadata.linked_data &&
|
|
||||||
props.instance.metadata.linked_data.version_id !== props.instance.modrinth_update_version
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
const canUpdatePack = computed(() => {
|
||||||
|
return props.instance.metadata.linked_data.version_id !== props.instance.modrinth_update_version
|
||||||
|
})
|
||||||
|
const exportModal = ref(null)
|
||||||
|
|
||||||
console.log(props.instance)
|
console.log(props.instance)
|
||||||
const initProjects = (initInstance) => {
|
const initProjects = (initInstance) => {
|
||||||
@@ -501,6 +517,7 @@ const selectedOption = ref('Share')
|
|||||||
const shareModal = ref(null)
|
const shareModal = ref(null)
|
||||||
const ascending = ref(true)
|
const ascending = ref(true)
|
||||||
const sortColumn = ref('Name')
|
const sortColumn = ref('Name')
|
||||||
|
const currentPage = ref(1)
|
||||||
|
|
||||||
const selected = computed(() =>
|
const selected = computed(() =>
|
||||||
Array.from(selectionMap.value)
|
Array.from(selectionMap.value)
|
||||||
@@ -833,6 +850,10 @@ const unlisten = await listen('tauri://file-drop', async (event) => {
|
|||||||
initProjects(await get(props.instance.path).catch(handleError))
|
initProjects(await get(props.instance.path).catch(handleError))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const switchPage = (page) => {
|
||||||
|
currentPage.value = page
|
||||||
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
unlisten()
|
unlisten()
|
||||||
})
|
})
|
||||||
@@ -903,10 +924,56 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
.mod-card {
|
.mod-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: var(--gap-sm);
|
gap: var(--gap-sm);
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
overflow: hidden;
|
margin-bottom: 0.5rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
:deep(.dropdown-row) {
|
||||||
|
.btn {
|
||||||
|
height: 2.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-input {
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
.iconified-input {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
input {
|
||||||
|
flex-basis: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.animated-dropdown) {
|
||||||
|
.render-down {
|
||||||
|
border-radius: var(--radius-md) 0 0 var(--radius-md) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-wrapper {
|
||||||
|
margin-top: 0.25rem;
|
||||||
|
width: unset;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options {
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
border: 1px solid var(--color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card {
|
||||||
|
margin-top: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-combo {
|
.text-combo {
|
||||||
@@ -1078,4 +1145,10 @@ onUnmounted(() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-input {
|
||||||
|
.selected {
|
||||||
|
height: 2.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -21,7 +21,12 @@
|
|||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<p class="input-label">Game Version</p>
|
<p class="input-label">Game Version</p>
|
||||||
<div class="versions">
|
<div class="versions">
|
||||||
<DropdownSelect v-model="gameVersion" :options="selectableGameVersions" render-up />
|
<DropdownSelect
|
||||||
|
v-model="gameVersion"
|
||||||
|
:options="selectableGameVersions"
|
||||||
|
name="Game Version Dropdown"
|
||||||
|
render-up
|
||||||
|
/>
|
||||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -31,6 +36,7 @@
|
|||||||
:model-value="selectableLoaderVersions[loaderVersionIndex]"
|
:model-value="selectableLoaderVersions[loaderVersionIndex]"
|
||||||
:options="selectableLoaderVersions"
|
:options="selectableLoaderVersions"
|
||||||
:display-name="(option) => option?.id"
|
:display-name="(option) => option?.id"
|
||||||
|
name="Version selector"
|
||||||
render-up
|
render-up
|
||||||
@change="(value) => (loaderVersionIndex = value.index)"
|
@change="(value) => (loaderVersionIndex = value.index)"
|
||||||
/>
|
/>
|
||||||
@@ -426,11 +432,9 @@ const groups = ref(props.instance.metadata.groups)
|
|||||||
|
|
||||||
const instancesList = Object.values(await list(true))
|
const instancesList = Object.values(await list(true))
|
||||||
const availableGroups = ref([
|
const availableGroups = ref([
|
||||||
...new Set(
|
...instancesList.reduce((acc, obj) => {
|
||||||
instancesList.reduce((acc, obj) => {
|
return acc.concat(obj.metadata.groups)
|
||||||
return acc.concat(obj.metadata.groups)
|
}, []),
|
||||||
}, [])
|
|
||||||
),
|
|
||||||
])
|
])
|
||||||
|
|
||||||
async function resetIcon() {
|
async function resetIcon() {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@
|
|||||||
(cat) => data.categories.includes(cat.name) && cat.project_type === 'mod'
|
(cat) => data.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||||
)
|
)
|
||||||
"
|
"
|
||||||
|
type="ignored"
|
||||||
>
|
>
|
||||||
<EnvironmentIndicator
|
<EnvironmentIndicator
|
||||||
:client-side="data.client_side"
|
:client-side="data.client_side"
|
||||||
@@ -167,7 +168,7 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="data" class="content-container">
|
<div v-if="data" class="content-container">
|
||||||
<Promotion />
|
<Promotion query-param="?r=launcher" />
|
||||||
<Card class="tabs">
|
<Card class="tabs">
|
||||||
<NavRow
|
<NavRow
|
||||||
v-if="data.gallery.length > 0"
|
v-if="data.gallery.length > 0"
|
||||||
@@ -205,6 +206,7 @@
|
|||||||
:versions="versions"
|
:versions="versions"
|
||||||
:members="members"
|
:members="members"
|
||||||
:dependencies="dependencies"
|
:dependencies="dependencies"
|
||||||
|
:instance="instance"
|
||||||
:install="install"
|
:install="install"
|
||||||
:installed="installed"
|
:installed="installed"
|
||||||
:installing="installing"
|
:installing="installing"
|
||||||
|
|||||||
@@ -167,8 +167,8 @@ import { computed, ref, watch } from 'vue'
|
|||||||
import { SwapIcon } from '@/assets/icons/index.js'
|
import { SwapIcon } from '@/assets/icons/index.js'
|
||||||
|
|
||||||
const filterVersions = ref([])
|
const filterVersions = ref([])
|
||||||
const filterLoader = ref([])
|
const filterLoader = ref(props.instance ? [props.instance?.metadata?.loader] : [])
|
||||||
const filterGameVersions = ref([])
|
const filterGameVersions = ref(props.instance ? [props.instance?.metadata?.game_version] : [])
|
||||||
|
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user