You've already forked AstralRinth
forked from didirus/AstralRinth
Search fixes (#134)
* Search fixes * Fix small instance ui * fix javaw issue * menu fix * Add confirm modal for deletion * fix build
This commit is contained in:
@@ -156,7 +156,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
|||||||
"Failed to extract java zip".to_string(),
|
"Failed to extract java zip".to_string(),
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
|
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
|
||||||
Ok(path
|
Ok(path
|
||||||
.join(
|
.join(
|
||||||
download
|
download
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ pub async fn emit_loading(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Emit event to tauri
|
//Emit event to tauri
|
||||||
#[cfg(feature = "tauri")]
|
#[cfg(feature = "tauri")]
|
||||||
event_state
|
event_state
|
||||||
.app
|
.app
|
||||||
|
|||||||
@@ -94,51 +94,43 @@ impl Drop for LoadingBarId {
|
|||||||
let _event = LoadingBarType::StateInit;
|
let _event = LoadingBarType::StateInit;
|
||||||
let _message = "finished".to_string();
|
let _message = "finished".to_string();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Ok(event_state) = crate::EventState::get().await {
|
if let Ok(event_state) = EventState::get().await {
|
||||||
{
|
let mut bars = event_state.loading_bars.write().await;
|
||||||
let mut bars = event_state.loading_bars.write().await;
|
|
||||||
bars.remove(&loader_uuid);
|
#[cfg(any(feature = "tauri", feature = "cli"))]
|
||||||
|
if let Some(bar) = bars.remove(&loader_uuid) {
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
|
{
|
||||||
|
let loader_uuid = bar.loading_bar_uuid;
|
||||||
|
let event = bar.bar_type.clone();
|
||||||
|
let fraction = bar.current / bar.total;
|
||||||
|
|
||||||
|
use tauri::Manager;
|
||||||
|
let _ = event_state.app.emit_all(
|
||||||
|
"loading",
|
||||||
|
LoadingPayload {
|
||||||
|
fraction: None,
|
||||||
|
message: "Completed".to_string(),
|
||||||
|
event,
|
||||||
|
loader_uuid,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tracing::debug!(
|
||||||
|
"Exited at {fraction} for loading bar: {:?}",
|
||||||
|
loader_uuid
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Emit event to indicatif progress bar arc
|
||||||
|
#[cfg(feature = "cli")]
|
||||||
|
{
|
||||||
|
let cli_progress_bar = bar.cli_progress_bar.clone();
|
||||||
|
cli_progress_bar.finish();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// When Loading bar is dropped, should attempt to throw out one last event to indicate that the loading bar is done
|
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
||||||
#[cfg(feature = "tauri")]
|
bars.remove(&loader_uuid);
|
||||||
impl Drop for LoadingBar {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
let loader_uuid = self.loading_bar_uuid;
|
|
||||||
let event = self.bar_type.clone();
|
|
||||||
let fraction = self.current / self.total;
|
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
let cli_progress_bar = self.cli_progress_bar.clone();
|
|
||||||
|
|
||||||
tokio::spawn(async move {
|
|
||||||
#[cfg(feature = "tauri")]
|
|
||||||
{
|
|
||||||
use tauri::Manager;
|
|
||||||
if let Ok(event_state) = crate::EventState::get().await {
|
|
||||||
let _ = event_state.app.emit_all(
|
|
||||||
"loading",
|
|
||||||
LoadingPayload {
|
|
||||||
fraction: None,
|
|
||||||
message: "Completed".to_string(),
|
|
||||||
event,
|
|
||||||
loader_uuid,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
tracing::debug!(
|
|
||||||
"Exited at {fraction} for loading bar: {:?}",
|
|
||||||
loader_uuid
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Emit event to indicatif progress bar arc
|
|
||||||
#[cfg(feature = "cli")]
|
|
||||||
{
|
|
||||||
cli_progress_bar.finish();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"floating-vue": "^2.0.0-beta.20",
|
"floating-vue": "^2.0.0-beta.20",
|
||||||
"ofetch": "^1.0.1",
|
"ofetch": "^1.0.1",
|
||||||
"omorphia": "^0.4.22",
|
"omorphia": "^0.4.24",
|
||||||
"pinia": "^2.1.3",
|
"pinia": "^2.1.3",
|
||||||
"vite-svg-loader": "^4.0.0",
|
"vite-svg-loader": "^4.0.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
|
|||||||
8
theseus_gui/pnpm-lock.yaml
generated
8
theseus_gui/pnpm-lock.yaml
generated
@@ -14,8 +14,8 @@ dependencies:
|
|||||||
specifier: ^1.0.1
|
specifier: ^1.0.1
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
omorphia:
|
omorphia:
|
||||||
specifier: ^0.4.22
|
specifier: ^0.4.24
|
||||||
version: 0.4.22
|
version: 0.4.24
|
||||||
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)
|
||||||
@@ -1326,8 +1326,8 @@ packages:
|
|||||||
ufo: 1.1.2
|
ufo: 1.1.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/omorphia@0.4.22:
|
/omorphia@0.4.24:
|
||||||
resolution: {integrity: sha512-UzG/MqOu/q+F65RZ74oCOLHE4dm716cCkX9EtSP30s8RInd8AHuax4riFKK+ANfCCtE9zcDw4AmU7fJlUnX6Xw==}
|
resolution: {integrity: sha512-tYhg88wkv9yfCNF8uVDkt6MIZ5WuCEezWrWJuBpHXP0X1yNGey6ICbH0LSuNuOMtqhGJEIupGEB7uV4Db9b7uQ==}
|
||||||
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)
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.5 KiB |
@@ -40,7 +40,6 @@
|
|||||||
},
|
},
|
||||||
"externalBin": [],
|
"externalBin": [],
|
||||||
"icon": [
|
"icon": [
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
"icons/128x128.png",
|
||||||
"icons/128x128@2x.png",
|
"icons/128x128@2x.png",
|
||||||
"icons/icon.icns",
|
"icons/icon.icns",
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
<p>Selected</p>
|
<p>Selected</p>
|
||||||
</div>
|
</div>
|
||||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||||
<XIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="logged-out account">
|
<div v-else class="logged-out account">
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
<p>{{ account.username }}</p>
|
<p>{{ account.username }}</p>
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||||
<XIcon />
|
<TrashIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Avatar, Button, Card, PlusIcon, XIcon, UsersIcon, LogInIcon } from 'omorphia'
|
import { Avatar, Button, Card, PlusIcon, TrashIcon, UsersIcon, LogInIcon } from 'omorphia'
|
||||||
import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, defineProps, computed, onMounted, onBeforeUnmount } from 'vue'
|
||||||
import {
|
import {
|
||||||
users,
|
users,
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
<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
|
||||||
v-if="breadcrumb.link"
|
v-if="breadcrumb.link"
|
||||||
:to="breadcrumb.link.replace('{id}', encodeURIComponent($route.params.id))"
|
:to="{
|
||||||
|
path: breadcrumb.link.replace('{id}', encodeURIComponent($route.params.id)),
|
||||||
|
query: breadcrumb.query,
|
||||||
|
}"
|
||||||
>{{
|
>{{
|
||||||
breadcrumb.name.charAt(0) === '?'
|
breadcrumb.name.charAt(0) === '?'
|
||||||
? breadcrumbData.getName(breadcrumb.name.slice(1))
|
? breadcrumbData.getName(breadcrumb.name.slice(1))
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onUnmounted, ref, useSlots, watch } from 'vue'
|
import { onUnmounted, ref, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { Card, DownloadIcon, StopCircleIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
|
import { Card, DownloadIcon, StopCircleIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||||
import { install as pack_install } from '@/helpers/pack'
|
import { install as pack_install } from '@/helpers/pack'
|
||||||
import { get, list, remove, run } from '@/helpers/profile'
|
import { list, remove, run } from '@/helpers/profile'
|
||||||
import {
|
import {
|
||||||
get_all_running_profile_paths,
|
get_all_running_profile_paths,
|
||||||
get_uuids_by_profile_path,
|
get_uuids_by_profile_path,
|
||||||
@@ -13,12 +13,10 @@ import {
|
|||||||
} from '@/helpers/process'
|
} from '@/helpers/process'
|
||||||
import { process_listener } from '@/helpers/events'
|
import { process_listener } from '@/helpers/events'
|
||||||
import { useFetch } from '@/helpers/fetch.js'
|
import { useFetch } from '@/helpers/fetch.js'
|
||||||
import { handleError, useSearch } from '@/store/state.js'
|
import { handleError } from '@/store/state.js'
|
||||||
import { showInFolder } from '@/helpers/utils.js'
|
import { showInFolder } from '@/helpers/utils.js'
|
||||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||||
|
|
||||||
const searchStore = useSearch()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
type: Object,
|
type: Object,
|
||||||
@@ -26,10 +24,6 @@ const props = defineProps({
|
|||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
small: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
@@ -41,13 +35,14 @@ const modLoading = ref(
|
|||||||
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false
|
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(props.instance, () => {
|
watch(
|
||||||
modLoading.value = props.instance.install_stage
|
() => props.instance,
|
||||||
? props.instance.install_stage !== 'installed'
|
() => {
|
||||||
: false
|
modLoading.value = props.instance.install_stage
|
||||||
})
|
? props.instance.install_stage !== 'installed'
|
||||||
|
: false
|
||||||
const slots = useSlots()
|
}
|
||||||
|
)
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -143,8 +138,10 @@ const openFolder = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addContent = async () => {
|
const addContent = async () => {
|
||||||
searchStore.instanceContext = await get(props.instance.path).catch(handleError)
|
await router.push({
|
||||||
await router.push({ path: '/browse/mod' })
|
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||||
|
query: { i: props.instance.path },
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -168,41 +165,7 @@ onUnmounted(() => unlisten())
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="instance">
|
<div class="instance">
|
||||||
<Card v-if="props.small" class="instance-small-card" :class="{ 'button-base': !slots.content }">
|
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
|
||||||
<div
|
|
||||||
class="instance-small-card__description"
|
|
||||||
:class="{ 'button-base': slots.content }"
|
|
||||||
@click="seeInstance"
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
:src="
|
|
||||||
!props.instance.metadata.icon ||
|
|
||||||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
|
||||||
? props.instance.metadata.icon
|
|
||||||
: convertFileSrc(instance.metadata?.icon)
|
|
||||||
"
|
|
||||||
:alt="props.instance.metadata.name"
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
<div class="instance-small-card__info">
|
|
||||||
<span class="title">{{ props.instance.metadata.name }}</span>
|
|
||||||
{{
|
|
||||||
props.instance.metadata.loader.charAt(0).toUpperCase() +
|
|
||||||
props.instance.metadata.loader.slice(1)
|
|
||||||
}}
|
|
||||||
{{ props.instance.metadata.game_version }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="slots.content" class="instance-small-card__content">
|
|
||||||
<slot name="content" />
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
<Card
|
|
||||||
v-else
|
|
||||||
class="instance-card-item button-base"
|
|
||||||
@click="seeInstance"
|
|
||||||
@mouseenter="checkProcess"
|
|
||||||
>
|
|
||||||
<Avatar
|
<Avatar
|
||||||
size="sm"
|
size="sm"
|
||||||
:src="
|
:src="
|
||||||
@@ -210,7 +173,7 @@ onUnmounted(() => unlisten())
|
|||||||
? !props.instance.metadata.icon ||
|
? !props.instance.metadata.icon ||
|
||||||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
||||||
? props.instance.metadata.icon
|
? props.instance.metadata.icon
|
||||||
: convertFileSrc(instance.metadata?.icon)
|
: convertFileSrc(props.instance.metadata?.icon)
|
||||||
: props.instance.icon_url
|
: props.instance.icon_url
|
||||||
"
|
"
|
||||||
alt="Mod card"
|
alt="Mod card"
|
||||||
@@ -224,27 +187,25 @@ onUnmounted(() => unlisten())
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<template v-if="!props.small">
|
<div
|
||||||
<div
|
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
class="install cta button-base"
|
||||||
class="install cta button-base"
|
@click="play"
|
||||||
@click="play"
|
>
|
||||||
>
|
<PlayIcon />
|
||||||
<PlayIcon />
|
</div>
|
||||||
</div>
|
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
<AnimatedLogo class="loading-indicator" />
|
||||||
<AnimatedLogo class="loading-indicator" />
|
</div>
|
||||||
</div>
|
<div
|
||||||
<div
|
v-else-if="playing === true"
|
||||||
v-else-if="playing === true"
|
class="stop cta button-base"
|
||||||
class="stop cta button-base"
|
@click="stop"
|
||||||
@click="stop"
|
@mousehover="checkProcess"
|
||||||
@mousehover="checkProcess"
|
>
|
||||||
>
|
<StopCircleIcon />
|
||||||
<StopCircleIcon />
|
</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>
|
|
||||||
</template>
|
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<InstanceInstallModal ref="modInstallModal" />
|
||||||
</div>
|
</div>
|
||||||
@@ -263,58 +224,13 @@ onUnmounted(() => unlisten())
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.instance-small-card {
|
|
||||||
background-color: var(--color-bg) !important;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
min-height: min-content !important;
|
|
||||||
gap: 0.5rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.instance-small-card__description {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 1rem;
|
|
||||||
flex-grow: 1;
|
|
||||||
padding: var(--gap-xl);
|
|
||||||
padding-bottom: 0;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:not(.button-base) {
|
|
||||||
padding-bottom: var(--gap-xl);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.instance-small-card__info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
color: var(--color-contrast);
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.instance-small-card__content {
|
|
||||||
padding: var(--gap-xl);
|
|
||||||
padding-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.instance {
|
.instance {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
.cta {
|
.cta {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
bottom: 5.5rem;
|
bottom: 4.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance-card-item {
|
.instance-card-item {
|
||||||
@@ -329,9 +245,7 @@ onUnmounted(() => unlisten())
|
|||||||
background: hsl(0, 0%, 91%) !important;
|
background: hsl(0, 0%, 91%) !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.light-mode {
|
|
||||||
.instance-card-item {
|
.instance-card-item {
|
||||||
background: hsl(0, 0%, 100%) !important;
|
background: hsl(0, 0%, 100%) !important;
|
||||||
|
|
||||||
@@ -351,7 +265,7 @@ onUnmounted(() => unlisten())
|
|||||||
width: 3rem;
|
width: 3rem;
|
||||||
height: 3rem;
|
height: 3rem;
|
||||||
right: 1.25rem;
|
right: 1.25rem;
|
||||||
bottom: 5rem;
|
bottom: 4.25rem;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
transition: 0.2s ease-in-out bottom, 0.1s ease-in-out opacity, 0.1s ease-in-out filter !important;
|
transition: 0.2s ease-in-out bottom, 0.1s ease-in-out opacity, 0.1s ease-in-out filter !important;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -52,14 +52,12 @@ const currentSelected = ref({})
|
|||||||
defineExpose({
|
defineExpose({
|
||||||
show: async (version, currentSelectedJava) => {
|
show: async (version, currentSelectedJava) => {
|
||||||
if (version <= 8 && !!version) {
|
if (version <= 8 && !!version) {
|
||||||
console.log(version)
|
|
||||||
chosenInstallOptions.value = await find_jre_8_jres().catch(handleError)
|
chosenInstallOptions.value = await find_jre_8_jres().catch(handleError)
|
||||||
} else if (version >= 18) {
|
} else if (version >= 18) {
|
||||||
chosenInstallOptions.value = await find_jre_18plus_jres().catch(handleError)
|
chosenInstallOptions.value = await find_jre_18plus_jres().catch(handleError)
|
||||||
} else if (version) {
|
} else if (version) {
|
||||||
chosenInstallOptions.value = await find_jre_17_jres().catch(handleError)
|
chosenInstallOptions.value = await find_jre_17_jres().catch(handleError)
|
||||||
} else {
|
} else {
|
||||||
console.log('get all')
|
|
||||||
chosenInstallOptions.value = await get_all_jre().catch(handleError)
|
chosenInstallOptions.value = await get_all_jre().catch(handleError)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ const showCard = ref(false)
|
|||||||
|
|
||||||
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
||||||
|
|
||||||
await process_listener(async () => {
|
const unlistenProcess = await process_listener(async () => {
|
||||||
await refresh()
|
await refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ const goToTerminal = () => {
|
|||||||
|
|
||||||
const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
|
const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
|
||||||
|
|
||||||
await loading_listener(async () => {
|
const unlistenLoading = await loading_listener(async () => {
|
||||||
await refreshInfo()
|
await refreshInfo()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -128,6 +128,8 @@ onMounted(() => {
|
|||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
window.removeEventListener('click', handleClickOutside)
|
window.removeEventListener('click', handleClickOutside)
|
||||||
|
unlistenProcess()
|
||||||
|
unlistenLoading()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card class="card button-base" @click="$router.push(`/project/${project.project_id}/`)">
|
<Card
|
||||||
|
class="card button-base"
|
||||||
|
@click="
|
||||||
|
$router.push({
|
||||||
|
path: `/project/${project.project_id}/`,
|
||||||
|
query: { i: props.instance ? props.instance.path : undefined },
|
||||||
|
})
|
||||||
|
"
|
||||||
|
>
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<Avatar :src="project.icon_url" size="md" class="search-icon" />
|
<Avatar :src="project.icon_url" size="md" class="search-icon" />
|
||||||
</div>
|
</div>
|
||||||
@@ -117,14 +125,7 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
const installed = ref(props.project.installed)
|
||||||
const installed = ref(
|
|
||||||
props.instance
|
|
||||||
? Object.values(props.instance.projects).some(
|
|
||||||
(p) => p.metadata?.project?.id === props.project.project_id
|
|
||||||
)
|
|
||||||
: false
|
|
||||||
)
|
|
||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
installing.value = true
|
installing.value = true
|
||||||
|
|||||||
@@ -37,8 +37,8 @@ export async function get_uuids_by_profile_path(profilePath) {
|
|||||||
|
|
||||||
/// Gets all running process IDs with a given profile path
|
/// Gets all running process IDs with a given profile path
|
||||||
/// Returns [u32]
|
/// Returns [u32]
|
||||||
export async function get_all_running_profile_paths(profile_path) {
|
export async function get_all_running_profile_paths(profilePath) {
|
||||||
return await invoke('process_get_all_running_profile_paths', { profile_path })
|
return await invoke('process_get_all_running_profile_paths', { profilePath })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets all running process IDs with a given profile path
|
/// Gets all running process IDs with a given profile path
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, onMounted, ref, watch } from 'vue'
|
import { computed, nextTick, ref, readonly, shallowRef, watch } from 'vue'
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Button,
|
Button,
|
||||||
ClearIcon,
|
ClearIcon,
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
DropdownSelect,
|
|
||||||
SearchFilter,
|
SearchFilter,
|
||||||
Card,
|
Card,
|
||||||
ClientIcon,
|
ClientIcon,
|
||||||
@@ -15,74 +14,337 @@ import {
|
|||||||
formatCategoryHeader,
|
formatCategoryHeader,
|
||||||
formatCategory,
|
formatCategory,
|
||||||
Promotion,
|
Promotion,
|
||||||
|
DropdownSelect,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
import { handleError, useSearch } from '@/store/state'
|
import { handleError } from '@/store/state'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
|
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 InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||||
import Instance from '@/components/ui/Instance.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 { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const searchStore = useSearch()
|
|
||||||
searchStore.projectType = route.params.projectType
|
|
||||||
const showVersions = computed(
|
|
||||||
() => searchStore.instanceContext === null || searchStore.ignoreInstance
|
|
||||||
)
|
|
||||||
const showLoaders = computed(
|
|
||||||
() =>
|
|
||||||
searchStore.projectType !== 'datapack' &&
|
|
||||||
searchStore.projectType !== 'resourcepack' &&
|
|
||||||
searchStore.projectType !== 'shader' &&
|
|
||||||
(searchStore.instanceContext === null || searchStore.ignoreInstance)
|
|
||||||
)
|
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const modInstallModal = ref(null)
|
const modInstallModal = ref(null)
|
||||||
const incompatibilityWarningModal = ref(null)
|
const incompatibilityWarningModal = ref(null)
|
||||||
|
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
breadcrumbs.setContext({ name: 'Browse', link: route.path, query: route.query })
|
||||||
|
|
||||||
|
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 showSnapshots = ref(false)
|
||||||
const loading = ref(true)
|
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(route.params.projectType)
|
||||||
|
const instanceContext = ref(null)
|
||||||
|
const ignoreInstanceLoaders = ref(false)
|
||||||
|
const ignoreInstanceGameVersions = ref(false)
|
||||||
|
|
||||||
const categories = ref([])
|
const results = shallowRef([])
|
||||||
const loaders = ref([])
|
const pageCount = computed(() =>
|
||||||
const availableGameVersions = ref([])
|
results.value ? Math.ceil(results.value.total_hits / results.value.limit) : 1
|
||||||
|
)
|
||||||
|
|
||||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
function getArrayOrString(x) {
|
||||||
|
if (typeof x === 'string' || x instanceof String) {
|
||||||
if (searchStore.projectType === 'modpack') {
|
return [x]
|
||||||
searchStore.instanceContext = null
|
} else {
|
||||||
|
return x
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
if (route.query.iv) {
|
||||||
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
ignoreInstanceGameVersions.value = route.query.iv === 'true'
|
||||||
get_categories().catch(handleError),
|
}
|
||||||
get_loaders().catch(handleError),
|
if (route.query.il) {
|
||||||
get_game_versions().catch(handleError),
|
ignoreInstanceLoaders.value = route.query.il === 'true'
|
||||||
])
|
}
|
||||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
if (route.query.i) {
|
||||||
if (searchStore.projectType === 'modpack') {
|
instanceContext.value = await getInstance(route.query.i, true)
|
||||||
searchStore.instanceContext = null
|
}
|
||||||
|
if (route.query.q) {
|
||||||
|
query.value = route.query.q
|
||||||
|
}
|
||||||
|
if (route.query.f) {
|
||||||
|
facets.value = getArrayOrString(route.query.f)
|
||||||
|
}
|
||||||
|
if (route.query.g) {
|
||||||
|
orFacets.value = getArrayOrString(route.query.g)
|
||||||
|
}
|
||||||
|
if (route.query.v) {
|
||||||
|
selectedVersions.value = getArrayOrString(route.query.v)
|
||||||
|
}
|
||||||
|
if (route.query.l) {
|
||||||
|
onlyOpenSource.value = route.query.l === 'true'
|
||||||
|
}
|
||||||
|
if (route.query.h) {
|
||||||
|
showSnapshots.value = route.query.h === 'true'
|
||||||
|
}
|
||||||
|
if (route.query.e) {
|
||||||
|
selectedEnvironments.value = getArrayOrString(route.query.e)
|
||||||
|
}
|
||||||
|
if (route.query.s) {
|
||||||
|
sortType.value.name = route.query.s
|
||||||
|
|
||||||
|
switch (sortType.value.name) {
|
||||||
|
case 'relevance':
|
||||||
|
sortType.value.display = 'Relevance'
|
||||||
|
break
|
||||||
|
case 'downloads':
|
||||||
|
sortType.value.display = 'Downloads'
|
||||||
|
break
|
||||||
|
case 'newest':
|
||||||
|
sortType.value.display = 'Recently published'
|
||||||
|
break
|
||||||
|
case 'updated':
|
||||||
|
sortType.value.display = 'Recently updated'
|
||||||
|
break
|
||||||
|
case 'follows':
|
||||||
|
sortType.value.display = 'Follow count'
|
||||||
|
break
|
||||||
}
|
}
|
||||||
searchStore.searchInput = ''
|
}
|
||||||
await handleReset()
|
|
||||||
loading.value = false
|
if (route.query.m) {
|
||||||
})
|
maxResults.value = route.query.m
|
||||||
|
}
|
||||||
|
if (route.query.o) {
|
||||||
|
currentPage.value = Math.ceil(route.query.o / maxResults.value) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshSearch() {
|
||||||
|
const base = 'https://api.modrinth.com/v2/'
|
||||||
|
|
||||||
|
const params = [`limit=${maxResults.value}`, `index=${sortType.value.name}`]
|
||||||
|
|
||||||
|
if (query.value.length > 0) {
|
||||||
|
params.push(`query=${query.value.replace(/ /g, '+')}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceContext.value) {
|
||||||
|
if (!ignoreInstanceLoaders.value && projectType.value === 'mod') {
|
||||||
|
orFacets.value = [`categories:${encodeURIComponent(instanceContext.value.metadata.loader)}`]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ignoreInstanceGameVersions.value) {
|
||||||
|
selectedVersions.value = [instanceContext.value.metadata.game_version]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
facets.value.length > 0 ||
|
||||||
|
orFacets.value.length > 0 ||
|
||||||
|
selectedVersions.value.length > 0 ||
|
||||||
|
selectedEnvironments.value.length > 0 ||
|
||||||
|
projectType.value
|
||||||
|
) {
|
||||||
|
let formattedFacets = []
|
||||||
|
for (const facet of facets.value) {
|
||||||
|
formattedFacets.push([facet])
|
||||||
|
}
|
||||||
|
|
||||||
|
// loaders specifier
|
||||||
|
if (orFacets.value.length > 0) {
|
||||||
|
formattedFacets.push(orFacets.value)
|
||||||
|
} else if (projectType.value === 'mod') {
|
||||||
|
formattedFacets.push(
|
||||||
|
['forge', 'fabric', 'quilt'].map((x) => `categories:'${encodeURIComponent(x)}'`)
|
||||||
|
)
|
||||||
|
} else if (projectType.value === 'datapack') {
|
||||||
|
formattedFacets.push(['datapack'].map((x) => `categories:'${encodeURIComponent(x)}'`))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedVersions.value.length > 0) {
|
||||||
|
const versionFacets = []
|
||||||
|
for (const facet of selectedVersions.value) {
|
||||||
|
versionFacets.push('versions:' + facet)
|
||||||
|
}
|
||||||
|
formattedFacets.push(versionFacets)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (onlyOpenSource.value) {
|
||||||
|
formattedFacets.push(['open_source:true'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedEnvironments.value.length > 0) {
|
||||||
|
let environmentFacets = []
|
||||||
|
|
||||||
|
const includesClient = selectedEnvironments.value.includes('client')
|
||||||
|
const includesServer = selectedEnvironments.value.includes('server')
|
||||||
|
if (includesClient && includesServer) {
|
||||||
|
environmentFacets = [['client_side:required'], ['server_side:required']]
|
||||||
|
} else {
|
||||||
|
if (includesClient) {
|
||||||
|
environmentFacets = [
|
||||||
|
['client_side:optional', 'client_side:required'],
|
||||||
|
['server_side:optional', 'server_side:unsupported'],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
if (includesServer) {
|
||||||
|
environmentFacets = [
|
||||||
|
['client_side:optional', 'client_side:unsupported'],
|
||||||
|
['server_side:optional', 'server_side:required'],
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formattedFacets = [...formattedFacets, ...environmentFacets]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (projectType.value) {
|
||||||
|
formattedFacets.push([
|
||||||
|
`project_type:${projectType.value === 'datapack' ? 'mod' : projectType.value}`,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
params.push(`facets=${JSON.stringify(formattedFacets)}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = (currentPage.value - 1) * maxResults.value
|
||||||
|
if (currentPage.value !== 1) {
|
||||||
|
params.push(`offset=${offset}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = 'search'
|
||||||
|
|
||||||
|
if (params.length > 0) {
|
||||||
|
for (let i = 0; i < params.length; i++) {
|
||||||
|
url += i === 0 ? `?${params[i]}` : `&${params[i]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let val = `${base}${url}`
|
||||||
|
|
||||||
|
const rawResults = await useFetch(val, 'search results')
|
||||||
|
if (instanceContext.value) {
|
||||||
|
for (let val of rawResults.hits) {
|
||||||
|
val.installed = await check_installed(instanceContext.value.path, val.project_id).then(
|
||||||
|
(x) => (val.installed = x)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
results.value = rawResults
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSearchChange(newPageNumber) {
|
||||||
|
currentPage.value = newPageNumber
|
||||||
|
|
||||||
|
if (query.value === null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
await refreshSearch()
|
||||||
|
|
||||||
|
const obj = getSearchUrl((currentPage.value - 1) * maxResults.value, true)
|
||||||
|
await router.replace({ path: route.path, query: obj })
|
||||||
|
breadcrumbs.setContext({ name: 'Browse', link: route.path, query: obj })
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchWrapper = ref(null)
|
||||||
|
async function onSearchChangeToTop(newPageNumber) {
|
||||||
|
await onSearchChange(newPageNumber)
|
||||||
|
await nextTick()
|
||||||
|
searchWrapper.value.scrollTo({ top: 0, behavior: 'smooth' })
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchUrl(offset, useObj) {
|
||||||
|
const queryItems = []
|
||||||
|
const obj = {}
|
||||||
|
|
||||||
|
if (query.value) {
|
||||||
|
queryItems.push(`q=${encodeURIComponent(query.value)}`)
|
||||||
|
obj.q = query.value
|
||||||
|
}
|
||||||
|
if (offset > 0) {
|
||||||
|
queryItems.push(`o=${offset}`)
|
||||||
|
obj.o = offset
|
||||||
|
}
|
||||||
|
if (facets.value.length > 0) {
|
||||||
|
queryItems.push(`f=${encodeURIComponent(facets.value)}`)
|
||||||
|
obj.f = facets.value
|
||||||
|
}
|
||||||
|
if (orFacets.value.length > 0) {
|
||||||
|
queryItems.push(`g=${encodeURIComponent(orFacets.value)}`)
|
||||||
|
obj.g = orFacets.value
|
||||||
|
}
|
||||||
|
if (selectedVersions.value.length > 0) {
|
||||||
|
queryItems.push(`v=${encodeURIComponent(selectedVersions.value)}`)
|
||||||
|
obj.v = selectedVersions.value
|
||||||
|
}
|
||||||
|
if (onlyOpenSource.value) {
|
||||||
|
queryItems.push('l=true')
|
||||||
|
obj.l = true
|
||||||
|
}
|
||||||
|
if (showSnapshots.value) {
|
||||||
|
queryItems.push('h=true')
|
||||||
|
obj.h = true
|
||||||
|
}
|
||||||
|
if (selectedEnvironments.value.length > 0) {
|
||||||
|
queryItems.push(`e=${encodeURIComponent(selectedEnvironments.value)}`)
|
||||||
|
obj.e = selectedEnvironments.value
|
||||||
|
}
|
||||||
|
if (sortType.value.name !== 'relevance') {
|
||||||
|
queryItems.push(`s=${encodeURIComponent(sortType.value.name)}`)
|
||||||
|
obj.s = sortType.value.name
|
||||||
|
}
|
||||||
|
if (maxResults.value !== 20) {
|
||||||
|
queryItems.push(`m=${encodeURIComponent(maxResults.value)}`)
|
||||||
|
obj.m = maxResults.value
|
||||||
|
}
|
||||||
|
if (instanceContext.value) {
|
||||||
|
queryItems.push(`i=${encodeURIComponent(instanceContext.value.path)}`)
|
||||||
|
obj.i = instanceContext.value.path
|
||||||
|
}
|
||||||
|
if (ignoreInstanceGameVersions.value) {
|
||||||
|
queryItems.push('iv=true')
|
||||||
|
obj.iv = true
|
||||||
|
}
|
||||||
|
if (ignoreInstanceLoaders.value) {
|
||||||
|
queryItems.push('il=true')
|
||||||
|
obj.il = true
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = `${route.path}`
|
||||||
|
|
||||||
|
if (queryItems.length > 0) {
|
||||||
|
url += `?${queryItems[0]}`
|
||||||
|
|
||||||
|
for (let i = 1; i < queryItems.length; i++) {
|
||||||
|
url += `&${queryItems[i]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return useObj ? obj : url
|
||||||
|
}
|
||||||
|
|
||||||
const sortedCategories = computed(() => {
|
const sortedCategories = computed(() => {
|
||||||
const values = new Map()
|
const values = new Map()
|
||||||
for (const category of categories.value.filter(
|
for (const category of categories.value.filter(
|
||||||
(cat) =>
|
(cat) => cat.project_type === (projectType.value === 'datapack' ? 'mod' : projectType.value)
|
||||||
cat.project_type ===
|
|
||||||
(searchStore.projectType === 'datapack' ? 'mod' : searchStore.projectType)
|
|
||||||
)) {
|
)) {
|
||||||
if (!values.has(category.header)) {
|
if (!values.has(category.header)) {
|
||||||
values.set(category.header, [])
|
values.set(category.header, [])
|
||||||
@@ -92,128 +354,188 @@ const sortedCategories = computed(() => {
|
|||||||
return values
|
return values
|
||||||
})
|
})
|
||||||
|
|
||||||
const getSearchResults = async () => {
|
async function clearFilters() {
|
||||||
const queryString = searchStore.getQueryString()
|
for (const facet of [...facets.value]) {
|
||||||
const response = await useFetch(
|
await toggleFacet(facet, true)
|
||||||
`https://api.modrinth.com/v2/search${queryString}`,
|
}
|
||||||
'search results'
|
for (const facet of [...orFacets.value]) {
|
||||||
)
|
await toggleOrFacet(facet, true)
|
||||||
searchStore.setSearchResults(response)
|
}
|
||||||
|
|
||||||
|
onlyOpenSource.value = false
|
||||||
|
selectedVersions.value = []
|
||||||
|
selectedEnvironments.value = []
|
||||||
|
await onSearchChange(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleReset = async () => {
|
async function toggleFacet(elementName, doNotSendRequest = false) {
|
||||||
searchStore.currentPage = 1
|
const index = facets.value.indexOf(elementName)
|
||||||
searchStore.offset = 0
|
|
||||||
searchStore.resetFilters()
|
if (index !== -1) {
|
||||||
await getSearchResults()
|
facets.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
facets.value.push(elementName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doNotSendRequest) {
|
||||||
|
await onSearchChange(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleFacet = async (facet) => {
|
async function toggleOrFacet(elementName, doNotSendRequest) {
|
||||||
searchStore.currentPage = 1
|
const index = orFacets.value.indexOf(elementName)
|
||||||
searchStore.offset = 0
|
if (index !== -1) {
|
||||||
const index = searchStore.facets.indexOf(facet)
|
orFacets.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
orFacets.value.push(elementName)
|
||||||
|
}
|
||||||
|
|
||||||
if (index !== -1) searchStore.facets.splice(index, 1)
|
if (!doNotSendRequest) {
|
||||||
else searchStore.facets.push(facet)
|
await onSearchChange(1)
|
||||||
|
}
|
||||||
await switchPage(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleOrFacet = async (orFacet) => {
|
function toggleEnv(environment, sendRequest) {
|
||||||
const index = searchStore.orFacets.indexOf(orFacet)
|
const index = selectedEnvironments.value.indexOf(environment)
|
||||||
|
if (index !== -1) {
|
||||||
|
selectedEnvironments.value.splice(index, 1)
|
||||||
|
} else {
|
||||||
|
selectedEnvironments.value.push(environment)
|
||||||
|
}
|
||||||
|
|
||||||
if (index !== -1) searchStore.orFacets.splice(index, 1)
|
if (!sendRequest) {
|
||||||
else searchStore.orFacets.push(orFacet)
|
onSearchChange(1)
|
||||||
|
}
|
||||||
await switchPage(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
const switchPage = async (page) => {
|
|
||||||
searchStore.currentPage = parseInt(page)
|
|
||||||
if (page === 1) searchStore.offset = 0
|
|
||||||
else searchStore.offset = (searchStore.currentPage - 1) * searchStore.limit
|
|
||||||
await getSearchResults()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.projectType,
|
() => route.params.projectType,
|
||||||
async (projectType) => {
|
async (newType) => {
|
||||||
if (!projectType) return
|
if (!newType) return
|
||||||
searchStore.projectType = projectType
|
projectType.value = newType
|
||||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${searchStore.projectType}` })
|
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${projectType.value}` })
|
||||||
await handleReset()
|
|
||||||
await switchPage(1)
|
sortType.value = { display: 'Relevance', name: 'relevance' }
|
||||||
|
query.value = ''
|
||||||
|
|
||||||
|
loading.value = true
|
||||||
|
await clearFilters()
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const handleInstanceSwitch = async (value) => {
|
const [categories, loaders, availableGameVersions] = await Promise.all([
|
||||||
searchStore.ignoreInstance = value
|
get_categories().catch(handleError).then(ref),
|
||||||
await switchPage(1)
|
get_loaders().catch(handleError).then(ref),
|
||||||
}
|
get_game_versions().catch(handleError).then(ref),
|
||||||
|
refreshSearch(),
|
||||||
|
])
|
||||||
|
|
||||||
const selectableProjectTypes = computed(() => {
|
const selectableProjectTypes = computed(() => {
|
||||||
const values = [
|
const values = [
|
||||||
{ label: 'Data Packs', href: `/browse/datapack` },
|
|
||||||
{ label: 'Shaders', href: `/browse/shader` },
|
{ label: 'Shaders', href: `/browse/shader` },
|
||||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||||
]
|
]
|
||||||
|
|
||||||
if (searchStore.instanceContext) {
|
if (instanceContext.value) {
|
||||||
if (searchStore.instanceContext.metadata.loader !== 'vanilla') {
|
if (
|
||||||
|
availableGameVersions.value.findIndex(
|
||||||
|
(x) => x.version === instanceContext.value.metadata.game_version
|
||||||
|
) <= availableGameVersions.value.findIndex((x) => x.version === '1.13')
|
||||||
|
) {
|
||||||
|
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceContext.value.metadata.loader !== 'vanilla') {
|
||||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||||
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
||||||
}
|
}
|
||||||
|
|
||||||
return values
|
return values
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const showVersions = computed(
|
||||||
|
() => instanceContext.value === null || ignoreInstanceGameVersions.value
|
||||||
|
)
|
||||||
|
const showLoaders = computed(
|
||||||
|
() =>
|
||||||
|
(projectType.value !== 'datapack' &&
|
||||||
|
projectType.value !== 'resourcepack' &&
|
||||||
|
projectType.value !== 'shader' &&
|
||||||
|
instanceContext.value === null) ||
|
||||||
|
ignoreInstanceLoaders.value
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<aside class="filter-panel">
|
<aside class="filter-panel">
|
||||||
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small>
|
<div v-if="instanceContext" class="small-instance">
|
||||||
<template #content>
|
<div class="instance">
|
||||||
<Checkbox
|
<Avatar
|
||||||
:model-value="searchStore.ignoreInstance"
|
:src="
|
||||||
:checked="searchStore.ignoreInstance"
|
!instanceContext.metadata.icon ||
|
||||||
label="Show unsupported content"
|
(instanceContext.metadata.icon && instanceContext.metadata.icon.startsWith('http'))
|
||||||
class="filter-checkbox"
|
? instanceContext.metadata.icon
|
||||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
: convertFileSrc(instanceContext.metadata.icon)
|
||||||
|
"
|
||||||
|
:alt="instanceContext.metadata.name"
|
||||||
|
size="sm"
|
||||||
/>
|
/>
|
||||||
</template>
|
<div class="small-instance_info">
|
||||||
</Instance>
|
<span class="title">{{ instanceContext.metadata.name }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
instanceContext.metadata.loader.charAt(0).toUpperCase() +
|
||||||
|
instanceContext.metadata.loader.slice(1)
|
||||||
|
}}
|
||||||
|
{{ instanceContext.metadata.game_version }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Checkbox
|
||||||
|
v-model="ignoreInstanceGameVersions"
|
||||||
|
label="Override game versions"
|
||||||
|
class="filter-checkbox"
|
||||||
|
@update:model-value="onSearchChangeToTop(1)"
|
||||||
|
/>
|
||||||
|
<Checkbox
|
||||||
|
v-model="ignoreInstanceLoaders"
|
||||||
|
label="Override loaders"
|
||||||
|
class="filter-checkbox"
|
||||||
|
@update:model-value="onSearchChangeToTop(1)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<Card class="search-panel-card">
|
<Card class="search-panel-card">
|
||||||
<Button
|
<Button
|
||||||
role="button"
|
role="button"
|
||||||
:disabled="
|
:disabled="
|
||||||
!(
|
onlyOpenSource === false &&
|
||||||
searchStore.facets.length > 0 ||
|
selectedEnvironments.length === 0 &&
|
||||||
searchStore.orFacets.length > 0 ||
|
selectedVersions.length === 0 &&
|
||||||
searchStore.environments.server === true ||
|
facets.length === 0 &&
|
||||||
searchStore.environments.client === true ||
|
orFacets.length === 0
|
||||||
searchStore.openSource === true ||
|
|
||||||
searchStore.activeVersions.length > 0
|
|
||||||
)
|
|
||||||
"
|
"
|
||||||
@click="handleReset"
|
@click="clearFilters"
|
||||||
><ClearIcon />Clear Filters</Button
|
|
||||||
>
|
>
|
||||||
|
<ClearIcon /> Clear Filters
|
||||||
|
</Button>
|
||||||
<div v-if="showLoaders" class="loaders">
|
<div v-if="showLoaders" class="loaders">
|
||||||
<h2>Loaders</h2>
|
<h2>Loaders</h2>
|
||||||
<div
|
<div
|
||||||
v-for="loader in loaders.filter(
|
v-for="loader in loaders.filter(
|
||||||
(l) =>
|
(l) =>
|
||||||
(searchStore.projectType !== 'mod' &&
|
(projectType !== 'mod' && l.supported_project_types?.includes(projectType)) ||
|
||||||
l.supported_project_types?.includes(searchStore.projectType)) ||
|
(projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
|
||||||
)"
|
)"
|
||||||
:key="loader"
|
:key="loader"
|
||||||
>
|
>
|
||||||
<SearchFilter
|
<SearchFilter
|
||||||
:active-filters="searchStore.orFacets"
|
:active-filters="orFacets"
|
||||||
:icon="loader.icon"
|
:icon="loader.icon"
|
||||||
:display-name="formatCategory(loader.name)"
|
:display-name="formatCategory(loader.name)"
|
||||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||||
@@ -222,49 +544,11 @@ const selectableProjectTypes = computed(() => {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</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="searchStore.facets"
|
|
||||||
:icon="category.icon"
|
|
||||||
:display-name="formatCategory(category.name)"
|
|
||||||
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
|
||||||
class="filter-checkbox"
|
|
||||||
@toggle="toggleFacet"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="searchStore.projectType !== 'datapack'" class="environment">
|
|
||||||
<h2>Environments</h2>
|
|
||||||
<SearchFilter
|
|
||||||
v-model="searchStore.environments.client"
|
|
||||||
display-name="Client"
|
|
||||||
facet-name="client"
|
|
||||||
class="filter-checkbox"
|
|
||||||
@click="getSearchResults"
|
|
||||||
>
|
|
||||||
<ClientIcon aria-hidden="true" />
|
|
||||||
</SearchFilter>
|
|
||||||
<SearchFilter
|
|
||||||
v-model="searchStore.environments.server"
|
|
||||||
display-name="Server"
|
|
||||||
facet-name="server"
|
|
||||||
class="filter-checkbox"
|
|
||||||
@click="getSearchResults"
|
|
||||||
>
|
|
||||||
<ServerIcon aria-hidden="true" />
|
|
||||||
</SearchFilter>
|
|
||||||
</div>
|
|
||||||
<div v-if="showVersions" class="versions">
|
<div v-if="showVersions" class="versions">
|
||||||
<h2>Minecraft versions</h2>
|
<h2>Minecraft versions</h2>
|
||||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
||||||
<multiselect
|
<multiselect
|
||||||
v-model="searchStore.activeVersions"
|
v-model="selectedVersions"
|
||||||
:options="
|
:options="
|
||||||
showSnapshots
|
showSnapshots
|
||||||
? availableGameVersions.map((x) => x.version)
|
? availableGameVersions.map((x) => x.version)
|
||||||
@@ -279,21 +563,59 @@ const selectableProjectTypes = computed(() => {
|
|||||||
:clear-search-on-select="false"
|
:clear-search-on-select="false"
|
||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
placeholder="Choose versions..."
|
placeholder="Choose versions..."
|
||||||
@update:model-value="getSearchResults"
|
@update:model-value="onSearchChange(1)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</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"
|
||||||
|
@toggle="toggleFacet"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
@toggle="toggleEnv"
|
||||||
|
>
|
||||||
|
<ClientIcon aria-hidden="true" />
|
||||||
|
</SearchFilter>
|
||||||
|
<SearchFilter
|
||||||
|
:active-filters="selectedEnvironments"
|
||||||
|
display-name="Server"
|
||||||
|
facet-name="server"
|
||||||
|
class="filter-checkbox"
|
||||||
|
@toggle="toggleEnv"
|
||||||
|
>
|
||||||
|
<ServerIcon aria-hidden="true" />
|
||||||
|
</SearchFilter>
|
||||||
|
</div>
|
||||||
<div class="open-source">
|
<div class="open-source">
|
||||||
<h2>Open source</h2>
|
<h2>Open source</h2>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
v-model="searchStore.openSource"
|
v-model="onlyOpenSource"
|
||||||
|
label="Open source only"
|
||||||
class="filter-checkbox"
|
class="filter-checkbox"
|
||||||
label="Open source"
|
@update:model-value="onSearchChange(1)"
|
||||||
@click="getSearchResults"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="search">
|
<div ref="searchWrapper" class="search">
|
||||||
<Promotion class="promotion" />
|
<Promotion class="promotion" />
|
||||||
<Card class="project-type-container">
|
<Card class="project-type-container">
|
||||||
<NavRow :links="selectableProjectTypes" />
|
<NavRow :links="selectableProjectTypes" />
|
||||||
@@ -302,64 +624,59 @@ const selectableProjectTypes = computed(() => {
|
|||||||
<div class="iconified-input">
|
<div class="iconified-input">
|
||||||
<SearchIcon aria-hidden="true" />
|
<SearchIcon aria-hidden="true" />
|
||||||
<input
|
<input
|
||||||
v-model="searchStore.searchInput"
|
v-model="query"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
type="text"
|
type="text"
|
||||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
:placeholder="`Search ${projectType}s...`"
|
||||||
@input="getSearchResults"
|
@input="onSearchChange(1)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-option">
|
<div class="inline-option">
|
||||||
<span>Sort by</span>
|
<span>Sort by</span>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="searchStore.filter"
|
v-model="sortType"
|
||||||
name="Sort dropdown"
|
name="Sort by"
|
||||||
:options="[
|
:options="sortTypes"
|
||||||
'Relevance',
|
:display-name="(option) => option?.display"
|
||||||
'Download count',
|
@change="onSearchChange(1)"
|
||||||
'Follow count',
|
|
||||||
'Recently published',
|
|
||||||
'Recently updated',
|
|
||||||
]"
|
|
||||||
class="sort-dropdown"
|
|
||||||
@change="getSearchResults"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="inline-option">
|
<div class="inline-option">
|
||||||
<span>Show per page</span>
|
<span>Show per page</span>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="searchStore.limit"
|
v-model="maxResults"
|
||||||
name="Limit dropdown"
|
name="Max results"
|
||||||
:options="[5, 10, 15, 20, 50, 100]"
|
:options="[5, 10, 15, 20, 50, 100]"
|
||||||
:default-value="searchStore.limit.toString()"
|
:default-value="maxResults"
|
||||||
:model-value="searchStore.limit.toString()"
|
:model-value="maxResults"
|
||||||
class="limit-dropdown"
|
class="limit-dropdown"
|
||||||
@change="getSearchResults"
|
@change="onSearchChange(1)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Pagination
|
<Pagination
|
||||||
:page="searchStore.currentPage"
|
:page="currentPage"
|
||||||
:count="searchStore.pageCount"
|
:count="pageCount"
|
||||||
@switch-page="switchPage"
|
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||||
|
class="pagination-before"
|
||||||
|
@switch-page="onSearchChange"
|
||||||
/>
|
/>
|
||||||
<SplashScreen v-if="loading" />
|
<SplashScreen v-if="loading" />
|
||||||
<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">
|
||||||
<SearchCard
|
<SearchCard
|
||||||
v-for="result in searchStore.searchResults"
|
v-for="result in results.hits"
|
||||||
:key="result?.project_id"
|
:key="result?.project_id"
|
||||||
:project="result"
|
:project="result"
|
||||||
:instance="searchStore.instanceContext"
|
:instance="instanceContext"
|
||||||
:categories="[
|
:categories="[
|
||||||
...categories.filter(
|
...categories.filter(
|
||||||
(cat) =>
|
(cat) =>
|
||||||
result?.display_categories.includes(cat.name) &&
|
result?.display_categories.includes(cat.name) && cat.project_type === projectType
|
||||||
cat.project_type === searchStore.projectType
|
|
||||||
),
|
),
|
||||||
...loaders.filter(
|
...loaders.filter(
|
||||||
(loader) =>
|
(loader) =>
|
||||||
result?.display_categories.includes(loader.name) &&
|
result?.display_categories.includes(loader.name) &&
|
||||||
loader.supported_project_types?.includes(searchStore.projectType)
|
loader.supported_project_types?.includes(projectType)
|
||||||
),
|
),
|
||||||
]"
|
]"
|
||||||
:confirm-modal="confirmModal"
|
:confirm-modal="confirmModal"
|
||||||
@@ -367,10 +684,12 @@ const selectableProjectTypes = computed(() => {
|
|||||||
:incompatibility-warning-modal="incompatibilityWarningModal"
|
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
<Pagination
|
<pagination
|
||||||
:page="searchStore.currentPage"
|
:page="currentPage"
|
||||||
:count="searchStore.pageCount"
|
:count="pageCount"
|
||||||
@switch-page="switchPage"
|
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||||
|
class="pagination-after"
|
||||||
|
@switch-page="onSearchChangeToTop"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -381,6 +700,32 @@ const selectableProjectTypes = computed(() => {
|
|||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.small-instance {
|
||||||
|
background: var(--color-bg);
|
||||||
|
padding: var(--gap-lg);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
margin-bottom: var(--gap-md);
|
||||||
|
|
||||||
|
.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 {
|
.filter-checkbox {
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
@@ -492,7 +837,8 @@ const selectableProjectTypes = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin: 0 1rem 0 21rem;
|
scroll-behavior: smooth;
|
||||||
|
margin: 0 1rem 0.5rem 21rem;
|
||||||
width: calc(100% - 22rem);
|
width: calc(100% - 22rem);
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ const getInstances = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getFeaturedModpacks = async () => {
|
const getFeaturedModpacks = async () => {
|
||||||
console.log(filter.value)
|
|
||||||
const response = await useFetch(
|
const response = await useFetch(
|
||||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||||
'featured modpacks'
|
'featured modpacks'
|
||||||
|
|||||||
@@ -21,35 +21,52 @@ fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).jo
|
|||||||
const settings = ref(fetchSettings)
|
const settings = ref(fetchSettings)
|
||||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
||||||
|
|
||||||
watch(settings.value, async (oldSettings, newSettings) => {
|
watch(
|
||||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
settings,
|
||||||
|
async (oldSettings, newSettings) => {
|
||||||
|
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||||
|
|
||||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||||
setSettings.java_globals.JAVA_8 = undefined
|
setSettings.java_globals.JAVA_8 = undefined
|
||||||
}
|
}
|
||||||
if (setSettings.java_globals.JAVA_17?.path === '') {
|
if (setSettings.java_globals.JAVA_17?.path === '') {
|
||||||
setSettings.java_globals.JAVA_17 = undefined
|
setSettings.java_globals.JAVA_17 = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
if (setSettings.java_globals.JAVA_8?.path) {
|
||||||
setSettings.custom_env_args = setSettings.envArgs
|
setSettings.java_globals.JAVA_8.path = setSettings.java_globals.JAVA_8.path.replace(
|
||||||
.trim()
|
'java.exe',
|
||||||
.split(/\s+/)
|
'javaw.exe'
|
||||||
.filter(Boolean)
|
)
|
||||||
.map((x) => x.split('=').filter(Boolean))
|
}
|
||||||
|
if (setSettings.java_globals.JAVA_17?.path) {
|
||||||
|
setSettings.java_globals.JAVA_17.path = setSettings.java_globals.JAVA_17?.path.replace(
|
||||||
|
'java.exe',
|
||||||
|
'javaw.exe'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (!setSettings.hooks.pre_launch) {
|
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||||
setSettings.hooks.pre_launch = null
|
setSettings.custom_env_args = setSettings.envArgs
|
||||||
}
|
.trim()
|
||||||
if (!setSettings.hooks.wrapper) {
|
.split(/\s+/)
|
||||||
setSettings.hooks.wrapper = null
|
.filter(Boolean)
|
||||||
}
|
.map((x) => x.split('=').filter(Boolean))
|
||||||
if (!setSettings.hooks.post_exit) {
|
|
||||||
setSettings.hooks.post_exit = null
|
|
||||||
}
|
|
||||||
|
|
||||||
await set(setSettings)
|
if (!setSettings.hooks.pre_launch) {
|
||||||
})
|
setSettings.hooks.pre_launch = null
|
||||||
|
}
|
||||||
|
if (!setSettings.hooks.wrapper) {
|
||||||
|
setSettings.hooks.wrapper = null
|
||||||
|
}
|
||||||
|
if (!setSettings.hooks.post_exit) {
|
||||||
|
setSettings.hooks.post_exit = null
|
||||||
|
}
|
||||||
|
|
||||||
|
await set(setSettings)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -119,7 +136,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
|||||||
id="max-downloads"
|
id="max-downloads"
|
||||||
v-model="settings.max_concurrent_downloads"
|
v-model="settings.max_concurrent_downloads"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="100"
|
:max="10"
|
||||||
:step="1"
|
:step="1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +153,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
|||||||
id="max-writes"
|
id="max-writes"
|
||||||
v-model="settings.max_concurrent_writes"
|
v-model="settings.max_concurrent_writes"
|
||||||
:min="1"
|
:min="1"
|
||||||
:max="100"
|
:max="50"
|
||||||
:step="1"
|
:step="1"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -126,22 +126,22 @@ import { process_listener, profile_listener } from '@/helpers/events'
|
|||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
import { ref, onUnmounted } from 'vue'
|
import { ref, onUnmounted } from 'vue'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import { handleError, useBreadcrumbs, useLoading, useSearch } from '@/store/state'
|
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
||||||
import { showInFolder } from '@/helpers/utils.js'
|
import { showInFolder } from '@/helpers/utils.js'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const searchStore = useSearch()
|
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
const instance = ref(await get(route.params.id).catch(handleError))
|
const instance = ref(await get(route.params.id).catch(handleError))
|
||||||
|
|
||||||
searchStore.instanceContext = instance.value
|
|
||||||
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
||||||
breadcrumbs.setContext({
|
breadcrumbs.setContext({
|
||||||
name: instance.value.metadata.name,
|
name: instance.value.metadata.name,
|
||||||
link: route.path,
|
link: route.path,
|
||||||
|
query: route.query,
|
||||||
})
|
})
|
||||||
|
|
||||||
const loadingBar = useLoading()
|
const loadingBar = useLoading()
|
||||||
@@ -183,7 +183,6 @@ const stopInstance = async () => {
|
|||||||
const unlistenProfiles = await profile_listener(async (event) => {
|
const unlistenProfiles = await profile_listener(async (event) => {
|
||||||
if (event.path === route.params.id) {
|
if (event.path === route.params.id) {
|
||||||
instance.value = await get(route.params.id).catch(handleError)
|
instance.value = await get(route.params.id).catch(handleError)
|
||||||
searchStore.instanceContext = instance.value
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -242,6 +241,7 @@ const handleOptionsClick = async (args) => {
|
|||||||
case 'add_content':
|
case 'add_content':
|
||||||
await router.push({
|
await router.push({
|
||||||
path: `/browse/${instance.value.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
path: `/browse/${instance.value.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||||
|
query: { i: route.params.id },
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case 'edit':
|
case 'edit':
|
||||||
|
|||||||
@@ -2,12 +2,12 @@
|
|||||||
<Card class="log-card">
|
<Card class="log-card">
|
||||||
<div class="button-row">
|
<div class="button-row">
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
|
v-model="selectedLogIndex"
|
||||||
|
:default-value="0"
|
||||||
name="Log date"
|
name="Log date"
|
||||||
:model-value="logs[selectedLogIndex]"
|
:options="logs.map((_, index) => index)"
|
||||||
:options="logs"
|
:display-name="(option) => logs[option]?.name"
|
||||||
:display-name="(option) => option?.name"
|
|
||||||
:disabled="logs.length === 0"
|
:disabled="logs.length === 0"
|
||||||
@change="(value) => (selectedLogIndex = value.index)"
|
|
||||||
/>
|
/>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
|
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
|
||||||
@@ -30,7 +30,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div ref="logContainer" class="log-text">
|
<div ref="logContainer" class="log-text">
|
||||||
<span v-for="line in logs[selectedLogIndex]?.stdout.split('\n')" :key="line" class="no-wrap">
|
<span
|
||||||
|
v-for="(line, index) in logs[selectedLogIndex]?.stdout.split('\n')"
|
||||||
|
:key="index"
|
||||||
|
class="no-wrap"
|
||||||
|
>
|
||||||
{{ line }} <br />
|
{{ line }} <br />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,15 +72,18 @@ const props = defineProps({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function getLiveLog() {
|
async function getLiveLog() {
|
||||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
if (route.params.id) {
|
||||||
let returnValue
|
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||||
if (uuids.length === 0) {
|
let returnValue
|
||||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
if (uuids.length === 0) {
|
||||||
} else {
|
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||||
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
} else {
|
||||||
}
|
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
||||||
|
}
|
||||||
|
|
||||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLogs() {
|
async function getLogs() {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
@click="
|
@click="
|
||||||
router.push({
|
router.push({
|
||||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||||
|
query: { i: $route.params.id },
|
||||||
})
|
})
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -80,7 +81,11 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-cell table-text name-cell">
|
<div class="table-cell table-text name-cell">
|
||||||
<router-link v-if="mod.slug" :to="`/project/${mod.slug}/`" class="mod-text">
|
<router-link
|
||||||
|
v-if="mod.slug"
|
||||||
|
:to="{ path: `/project/${mod.slug}/`, query: { i: props.instance.path } }"
|
||||||
|
class="mod-text"
|
||||||
|
>
|
||||||
<Avatar :src="mod.icon" />
|
<Avatar :src="mod.icon" />
|
||||||
{{ mod.name }}
|
{{ mod.name }}
|
||||||
</router-link>
|
</router-link>
|
||||||
@@ -305,7 +310,6 @@ async function updateProject(mod) {
|
|||||||
|
|
||||||
async function toggleDisableMod(mod) {
|
async function toggleDisableMod(mod) {
|
||||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||||
console.log(mod.disabled)
|
|
||||||
mod.disabled = !mod.disabled
|
mod.disabled = !mod.disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<ModalConfirm
|
||||||
|
ref="modal_confirm"
|
||||||
|
title="Are you sure you want to delete this instance?"
|
||||||
|
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||||
|
:has-to-type="false"
|
||||||
|
proceed-label="Delete"
|
||||||
|
@proceed="removeProfile"
|
||||||
|
/>
|
||||||
<Modal ref="changeVersionsModal" header="Change instance versions">
|
<Modal ref="changeVersionsModal" header="Change instance versions">
|
||||||
<div class="change-versions-modal universal-body">
|
<div class="change-versions-modal universal-body">
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
@@ -105,8 +113,8 @@
|
|||||||
placeholder="Select categories..."
|
placeholder="Select categories..."
|
||||||
@tag="
|
@tag="
|
||||||
(newTag) => {
|
(newTag) => {
|
||||||
groups.push(newTag)
|
groups.push(newTag.trim().substring(0, 32))
|
||||||
availableGroups.push(newTag)
|
availableGroups.push(newTag.trim().substring(0, 32))
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
@@ -288,7 +296,7 @@
|
|||||||
id="delete-profile"
|
id="delete-profile"
|
||||||
class="btn btn-danger"
|
class="btn btn-danger"
|
||||||
:disabled="removing"
|
:disabled="removing"
|
||||||
@click="removeProfile"
|
@click="$refs.modal_confirm.show()"
|
||||||
>
|
>
|
||||||
<TrashIcon /> Delete
|
<TrashIcon /> Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -311,6 +319,7 @@ import {
|
|||||||
XIcon,
|
XIcon,
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
HammerIcon,
|
HammerIcon,
|
||||||
|
ModalConfirm,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
@@ -398,7 +407,8 @@ const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
|
|||||||
watch(
|
watch(
|
||||||
[
|
[
|
||||||
title,
|
title,
|
||||||
groups.value,
|
groups,
|
||||||
|
groups,
|
||||||
overrideJavaInstall,
|
overrideJavaInstall,
|
||||||
javaInstall,
|
javaInstall,
|
||||||
overrideJavaArgs,
|
overrideJavaArgs,
|
||||||
@@ -406,17 +416,17 @@ watch(
|
|||||||
overrideEnvVars,
|
overrideEnvVars,
|
||||||
envVars,
|
envVars,
|
||||||
overrideMemorySettings,
|
overrideMemorySettings,
|
||||||
memory.value,
|
memory,
|
||||||
overrideWindowSettings,
|
overrideWindowSettings,
|
||||||
resolution.value,
|
resolution,
|
||||||
overrideHooks,
|
overrideHooks,
|
||||||
hooks.value,
|
hooks,
|
||||||
],
|
],
|
||||||
async () => {
|
async () => {
|
||||||
const editProfile = {
|
const editProfile = {
|
||||||
metadata: {
|
metadata: {
|
||||||
name: title.value,
|
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||||
groups: groups.value,
|
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||||
},
|
},
|
||||||
java: {},
|
java: {},
|
||||||
}
|
}
|
||||||
@@ -424,6 +434,10 @@ watch(
|
|||||||
if (overrideJavaInstall.value) {
|
if (overrideJavaInstall.value) {
|
||||||
if (javaInstall.value.path !== '') {
|
if (javaInstall.value.path !== '') {
|
||||||
editProfile.java.override_version = javaInstall.value
|
editProfile.java.override_version = javaInstall.value
|
||||||
|
editProfile.java.override_version.path = editProfile.java.override_version.path.replace(
|
||||||
|
'java.exe',
|
||||||
|
'javaw.exe'
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -456,7 +470,8 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
await edit(props.instance.path, editProfile)
|
await edit(props.instance.path, editProfile)
|
||||||
}
|
},
|
||||||
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
const repairing = ref(false)
|
const repairing = ref(false)
|
||||||
|
|||||||
@@ -1,7 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="root-container">
|
<div class="root-container">
|
||||||
<div v-if="data" class="project-sidebar">
|
<div v-if="data" class="project-sidebar">
|
||||||
<Instance v-if="instance" :instance="instance" small />
|
<div v-if="instance" class="small-instance">
|
||||||
|
<div class="instance">
|
||||||
|
<Avatar
|
||||||
|
:src="
|
||||||
|
!instance.metadata.icon ||
|
||||||
|
(instance.metadata.icon && instance.metadata.icon.startsWith('http'))
|
||||||
|
? instance.metadata.icon
|
||||||
|
: convertFileSrc(instance.metadata?.icon)
|
||||||
|
"
|
||||||
|
:alt="instance.metadata.name"
|
||||||
|
size="sm"
|
||||||
|
/>
|
||||||
|
<div class="small-instance_info">
|
||||||
|
<span class="title">{{ instance.metadata.name }}</span>
|
||||||
|
<span>
|
||||||
|
{{
|
||||||
|
instance.metadata.loader.charAt(0).toUpperCase() + instance.metadata.loader.slice(1)
|
||||||
|
}}
|
||||||
|
{{ instance.metadata.game_version }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
||||||
<Avatar size="lg" :src="data.icon_url" />
|
<Avatar size="lg" :src="data.icon_url" />
|
||||||
<div class="instance-info">
|
<div class="instance-info">
|
||||||
@@ -119,8 +141,8 @@
|
|||||||
<span>Wiki</span>
|
<span>Wiki</span>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
v-if="data.wiki_url"
|
v-if="data.discord_url"
|
||||||
:href="data.wiki_url"
|
:href="data.discord_url"
|
||||||
class="title"
|
class="title"
|
||||||
rel="noopener nofollow ugc external"
|
rel="noopener nofollow ugc external"
|
||||||
>
|
>
|
||||||
@@ -238,7 +260,12 @@ import {
|
|||||||
} from '@/assets/external'
|
} from '@/assets/external'
|
||||||
import { get_categories, get_loaders } from '@/helpers/tags'
|
import { get_categories, get_loaders } from '@/helpers/tags'
|
||||||
import { install as packInstall } from '@/helpers/pack'
|
import { install as packInstall } from '@/helpers/pack'
|
||||||
import { list, add_project_from_version as installMod, check_installed } from '@/helpers/profile'
|
import {
|
||||||
|
list,
|
||||||
|
add_project_from_version as installMod,
|
||||||
|
check_installed,
|
||||||
|
get as getInstance,
|
||||||
|
} from '@/helpers/profile'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
import { useRoute, useRouter } from 'vue-router'
|
||||||
@@ -246,16 +273,13 @@ 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 InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||||
import Instance from '@/components/ui/Instance.vue'
|
|
||||||
import { useSearch } from '@/store/search'
|
|
||||||
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'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
|
|
||||||
const searchStore = useSearch()
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
@@ -263,11 +287,11 @@ const breadcrumbs = useBreadcrumbs()
|
|||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const modInstallModal = ref(null)
|
const modInstallModal = ref(null)
|
||||||
const incompatibilityWarning = ref(null)
|
const incompatibilityWarning = ref(null)
|
||||||
|
|
||||||
const options = ref(null)
|
const options = ref(null)
|
||||||
const instance = ref(searchStore.instanceContext)
|
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
|
||||||
const [data, versions, members, dependencies, categories, loaders] = await Promise.all([
|
const [data, versions, members, dependencies, categories, loaders, instance] = await Promise.all([
|
||||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project').then(shallowRef),
|
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project').then(shallowRef),
|
||||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project').then(
|
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project').then(
|
||||||
shallowRef
|
shallowRef
|
||||||
@@ -280,6 +304,7 @@ const [data, versions, members, dependencies, categories, loaders] = await Promi
|
|||||||
),
|
),
|
||||||
get_loaders().then(ref).catch(handleError),
|
get_loaders().then(ref).catch(handleError),
|
||||||
get_categories().then(ref).catch(handleError),
|
get_categories().then(ref).catch(handleError),
|
||||||
|
route.query.i ? getInstance(route.query.i, true).then(ref) : Promise.resolve().then(ref),
|
||||||
])
|
])
|
||||||
|
|
||||||
const installed = ref(
|
const installed = ref(
|
||||||
@@ -587,4 +612,29 @@ const handleOptionsClick = (args) => {
|
|||||||
color: var(--color-contrast);
|
color: var(--color-contrast);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.small-instance {
|
||||||
|
background: var(--color-bg);
|
||||||
|
padding: var(--gap-lg);
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
margin-bottom: var(--gap-md);
|
||||||
|
|
||||||
|
.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;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.25rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,126 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
export const useSearch = defineStore('searchStore', {
|
|
||||||
state: () => ({
|
|
||||||
searchResults: [],
|
|
||||||
searchInput: '',
|
|
||||||
totalHits: 0,
|
|
||||||
currentPage: 1,
|
|
||||||
pageCount: 1,
|
|
||||||
offset: 0,
|
|
||||||
filter: 'Relevance',
|
|
||||||
projectType: '',
|
|
||||||
facets: [],
|
|
||||||
orFacets: [],
|
|
||||||
environments: {
|
|
||||||
client: false,
|
|
||||||
server: false,
|
|
||||||
},
|
|
||||||
activeVersions: [],
|
|
||||||
openSource: false,
|
|
||||||
limit: 20,
|
|
||||||
instanceContext: null,
|
|
||||||
ignoreInstance: false,
|
|
||||||
}),
|
|
||||||
actions: {
|
|
||||||
getQueryString() {
|
|
||||||
let andFacets = [`project_type:${this.projectType === 'datapack' ? 'mod' : this.projectType}`]
|
|
||||||
|
|
||||||
if (this.instanceContext && !this.ignoreInstance) {
|
|
||||||
this.activeVersions = [this.instanceContext.metadata.game_version]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate through possible andFacets
|
|
||||||
this.facets.forEach((facet) => {
|
|
||||||
andFacets.push(facet)
|
|
||||||
})
|
|
||||||
// Add open source to andFacets if enabled
|
|
||||||
if (this.openSource) andFacets.push('open_source:true')
|
|
||||||
|
|
||||||
// Create andFacet string
|
|
||||||
let formattedAndFacets = ''
|
|
||||||
if (this.projectType === 'datapack') {
|
|
||||||
;[...andFacets, `categories:${encodeURIComponent('datapack')}`].forEach(
|
|
||||||
(f) => (formattedAndFacets += `["${f}"],`)
|
|
||||||
)
|
|
||||||
} else if (this.instanceContext && !this.ignoreInstance && this.projectType === 'mod') {
|
|
||||||
;[
|
|
||||||
...andFacets,
|
|
||||||
`categories:${encodeURIComponent(this.instanceContext.metadata.loader)}`,
|
|
||||||
].forEach((f) => (formattedAndFacets += `["${f}"],`))
|
|
||||||
} else {
|
|
||||||
andFacets.forEach((f) => (formattedAndFacets += `["${f}"],`))
|
|
||||||
}
|
|
||||||
formattedAndFacets = formattedAndFacets.slice(0, formattedAndFacets.length - 1)
|
|
||||||
formattedAndFacets += ''
|
|
||||||
|
|
||||||
// If orFacets are present, start building formatted orFacet filter
|
|
||||||
let formattedOrFacets = ''
|
|
||||||
if (this.orFacets.length > 0) {
|
|
||||||
formattedOrFacets += '['
|
|
||||||
this.orFacets.forEach((orF) => (formattedOrFacets += `"${orF}",`))
|
|
||||||
formattedOrFacets = formattedOrFacets.slice(0, formattedOrFacets.length - 1)
|
|
||||||
formattedOrFacets += '],'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Snip normal orFacets and start version orFacets
|
|
||||||
if (this.activeVersions.length > 0) {
|
|
||||||
formattedOrFacets += '['
|
|
||||||
this.activeVersions.forEach((ver) => (formattedOrFacets += `"versions:${ver}",`))
|
|
||||||
formattedOrFacets = formattedOrFacets.slice(0, formattedOrFacets.length - 1)
|
|
||||||
formattedOrFacets += '],'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add environments to orFacets if enabled
|
|
||||||
if (this.environments.client)
|
|
||||||
formattedOrFacets += '["client_side:optional","client_side:required"]]'
|
|
||||||
if (this.environments.server)
|
|
||||||
formattedOrFacets += '["server_side:optional","server_side:required"]]'
|
|
||||||
|
|
||||||
formattedOrFacets = formattedOrFacets.slice(0, formattedOrFacets.length - 1)
|
|
||||||
|
|
||||||
// Aggregate facet query string
|
|
||||||
const facets = `&facets=[${formattedAndFacets}${
|
|
||||||
formattedOrFacets.length > 0 ? `,${formattedOrFacets}` : ''
|
|
||||||
}]`
|
|
||||||
|
|
||||||
// Configure results sorting
|
|
||||||
let indexSort
|
|
||||||
switch (this.filter) {
|
|
||||||
case 'Download count':
|
|
||||||
indexSort = 'downloads'
|
|
||||||
break
|
|
||||||
case 'Follow count':
|
|
||||||
indexSort = 'follows'
|
|
||||||
break
|
|
||||||
case 'Recently published':
|
|
||||||
indexSort = 'newest'
|
|
||||||
break
|
|
||||||
case 'Recently updated':
|
|
||||||
indexSort = 'updated'
|
|
||||||
break
|
|
||||||
default:
|
|
||||||
indexSort = 'relevance'
|
|
||||||
}
|
|
||||||
|
|
||||||
return `?query=${this.searchInput || ''}&limit=${this.limit}&offset=${this.offset || 0}${
|
|
||||||
facets || ''
|
|
||||||
}&index=${indexSort}`
|
|
||||||
},
|
|
||||||
setSearchResults(response) {
|
|
||||||
this.searchResults = [...response.hits]
|
|
||||||
this.totalHits = response.total_hits
|
|
||||||
this.offset = response.offset
|
|
||||||
this.pageCount = Math.ceil(this.totalHits / this.limit)
|
|
||||||
},
|
|
||||||
resetFilters() {
|
|
||||||
this.facets = []
|
|
||||||
this.orFacets = []
|
|
||||||
Object.keys(this.environments).forEach((env) => {
|
|
||||||
this.environments[env] = false
|
|
||||||
})
|
|
||||||
this.activeVersions = []
|
|
||||||
this.openSource = false
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useSearch } from './search'
|
|
||||||
import { useTheming } from './theme'
|
import { useTheming } from './theme'
|
||||||
import { useBreadcrumbs } from './breadcrumbs'
|
import { useBreadcrumbs } from './breadcrumbs'
|
||||||
import { useLoading } from './loading'
|
import { useLoading } from './loading'
|
||||||
import { useNotifications, handleError } from './notifications'
|
import { useNotifications, handleError } from './notifications'
|
||||||
|
|
||||||
export { useSearch, useTheming, useBreadcrumbs, useLoading, useNotifications, handleError }
|
export { useTheming, useBreadcrumbs, useLoading, useNotifications, handleError }
|
||||||
|
|||||||
Reference in New Issue
Block a user