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(),
|
||||
))
|
||||
})?;
|
||||
emit_loading(&loading_bar, 100.0, Some("Done extracting java")).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
|
||||
Ok(path
|
||||
.join(
|
||||
download
|
||||
|
||||
@@ -175,7 +175,7 @@ pub async fn emit_loading(
|
||||
);
|
||||
}
|
||||
|
||||
// Emit event to tauri
|
||||
//Emit event to tauri
|
||||
#[cfg(feature = "tauri")]
|
||||
event_state
|
||||
.app
|
||||
|
||||
@@ -94,51 +94,43 @@ impl Drop for LoadingBarId {
|
||||
let _event = LoadingBarType::StateInit;
|
||||
let _message = "finished".to_string();
|
||||
tokio::spawn(async move {
|
||||
if let Ok(event_state) = crate::EventState::get().await {
|
||||
{
|
||||
let mut bars = event_state.loading_bars.write().await;
|
||||
bars.remove(&loader_uuid);
|
||||
if let Ok(event_state) = EventState::get().await {
|
||||
let mut bars = event_state.loading_bars.write().await;
|
||||
|
||||
#[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(feature = "tauri")]
|
||||
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();
|
||||
#[cfg(not(any(feature = "tauri", feature = "cli")))]
|
||||
bars.remove(&loader_uuid);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
"dayjs": "^1.11.7",
|
||||
"floating-vue": "^2.0.0-beta.20",
|
||||
"ofetch": "^1.0.1",
|
||||
"omorphia": "^0.4.22",
|
||||
"omorphia": "^0.4.24",
|
||||
"pinia": "^2.1.3",
|
||||
"vite-svg-loader": "^4.0.0",
|
||||
"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
|
||||
version: 1.0.1
|
||||
omorphia:
|
||||
specifier: ^0.4.22
|
||||
version: 0.4.22
|
||||
specifier: ^0.4.24
|
||||
version: 0.4.24
|
||||
pinia:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3(vue@3.3.4)
|
||||
@@ -1326,8 +1326,8 @@ packages:
|
||||
ufo: 1.1.2
|
||||
dev: false
|
||||
|
||||
/omorphia@0.4.22:
|
||||
resolution: {integrity: sha512-UzG/MqOu/q+F65RZ74oCOLHE4dm716cCkX9EtSP30s8RInd8AHuax4riFKK+ANfCCtE9zcDw4AmU7fJlUnX6Xw==}
|
||||
/omorphia@0.4.24:
|
||||
resolution: {integrity: sha512-tYhg88wkv9yfCNF8uVDkt6MIZ5WuCEezWrWJuBpHXP0X1yNGey6ICbH0LSuNuOMtqhGJEIupGEB7uV4Db9b7uQ==}
|
||||
dependencies:
|
||||
dayjs: 1.11.7
|
||||
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": [],
|
||||
"icon": [
|
||||
"icons/32x32.png",
|
||||
"icons/128x128.png",
|
||||
"icons/128x128@2x.png",
|
||||
"icons/icon.icns",
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
<p>Selected</p>
|
||||
</div>
|
||||
<Button v-tooltip="'Log out'" icon-only color="raised" @click="logout(selectedAccount.id)">
|
||||
<XIcon />
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
<div v-else class="logged-out account">
|
||||
@@ -41,7 +41,7 @@
|
||||
<p>{{ account.username }}</p>
|
||||
</Button>
|
||||
<Button v-tooltip="'Log out'" icon-only @click="logout(account.id)">
|
||||
<XIcon />
|
||||
<TrashIcon />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -54,7 +54,7 @@
|
||||
</template>
|
||||
|
||||
<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 {
|
||||
users,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
<div v-for="breadcrumb in breadcrumbs" :key="breadcrumb.name" class="breadcrumbs__item">
|
||||
<router-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) === '?'
|
||||
? breadcrumbData.getName(breadcrumb.name.slice(1))
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, useSlots, watch } from 'vue'
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Card, DownloadIcon, StopCircleIcon, Avatar, AnimatedLogo, PlayIcon } from 'omorphia'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { get, list, remove, run } from '@/helpers/profile'
|
||||
import { list, remove, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
@@ -13,12 +13,10 @@ import {
|
||||
} from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
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 InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
|
||||
const searchStore = useSearch()
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
type: Object,
|
||||
@@ -26,10 +24,6 @@ const props = defineProps({
|
||||
return {}
|
||||
},
|
||||
},
|
||||
small: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
const confirmModal = ref(null)
|
||||
@@ -41,13 +35,14 @@ const modLoading = ref(
|
||||
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false
|
||||
)
|
||||
|
||||
watch(props.instance, () => {
|
||||
modLoading.value = props.instance.install_stage
|
||||
? props.instance.install_stage !== 'installed'
|
||||
: false
|
||||
})
|
||||
|
||||
const slots = useSlots()
|
||||
watch(
|
||||
() => props.instance,
|
||||
() => {
|
||||
modLoading.value = props.instance.install_stage
|
||||
? props.instance.install_stage !== 'installed'
|
||||
: false
|
||||
}
|
||||
)
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -143,8 +138,10 @@ const openFolder = async () => {
|
||||
}
|
||||
|
||||
const addContent = async () => {
|
||||
searchStore.instanceContext = await get(props.instance.path).catch(handleError)
|
||||
await router.push({ path: '/browse/mod' })
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
@@ -168,41 +165,7 @@ onUnmounted(() => unlisten())
|
||||
|
||||
<template>
|
||||
<div class="instance">
|
||||
<Card v-if="props.small" class="instance-small-card" :class="{ 'button-base': !slots.content }">
|
||||
<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"
|
||||
>
|
||||
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
|
||||
<Avatar
|
||||
size="sm"
|
||||
:src="
|
||||
@@ -210,7 +173,7 @@ onUnmounted(() => unlisten())
|
||||
? !props.instance.metadata.icon ||
|
||||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
||||
? props.instance.metadata.icon
|
||||
: convertFileSrc(instance.metadata?.icon)
|
||||
: convertFileSrc(props.instance.metadata?.icon)
|
||||
: props.instance.icon_url
|
||||
"
|
||||
alt="Mod card"
|
||||
@@ -224,27 +187,25 @@ onUnmounted(() => unlisten())
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<template v-if="!props.small">
|
||||
<div
|
||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||
class="install cta button-base"
|
||||
@click="play"
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="stop"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
</template>
|
||||
<div
|
||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||
class="install cta button-base"
|
||||
@click="play"
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="stop"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<InstanceInstallModal ref="modInstallModal" />
|
||||
</div>
|
||||
@@ -263,58 +224,13 @@ onUnmounted(() => unlisten())
|
||||
</style>
|
||||
|
||||
<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 {
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
.cta {
|
||||
opacity: 1;
|
||||
bottom: 5.5rem;
|
||||
bottom: 4.75rem;
|
||||
}
|
||||
|
||||
.instance-card-item {
|
||||
@@ -329,9 +245,7 @@ onUnmounted(() => unlisten())
|
||||
background: hsl(0, 0%, 91%) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.light-mode {
|
||||
.instance-card-item {
|
||||
background: hsl(0, 0%, 100%) !important;
|
||||
|
||||
@@ -351,7 +265,7 @@ onUnmounted(() => unlisten())
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
right: 1.25rem;
|
||||
bottom: 5rem;
|
||||
bottom: 4.25rem;
|
||||
opacity: 0;
|
||||
transition: 0.2s ease-in-out bottom, 0.1s ease-in-out opacity, 0.1s ease-in-out filter !important;
|
||||
cursor: pointer;
|
||||
|
||||
@@ -52,14 +52,12 @@ const currentSelected = ref({})
|
||||
defineExpose({
|
||||
show: async (version, currentSelectedJava) => {
|
||||
if (version <= 8 && !!version) {
|
||||
console.log(version)
|
||||
chosenInstallOptions.value = await find_jre_8_jres().catch(handleError)
|
||||
} else if (version >= 18) {
|
||||
chosenInstallOptions.value = await find_jre_18plus_jres().catch(handleError)
|
||||
} else if (version) {
|
||||
chosenInstallOptions.value = await find_jre_17_jres().catch(handleError)
|
||||
} else {
|
||||
console.log('get all')
|
||||
chosenInstallOptions.value = await get_all_jre().catch(handleError)
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ const showCard = ref(false)
|
||||
|
||||
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
||||
|
||||
await process_listener(async () => {
|
||||
const unlistenProcess = await process_listener(async () => {
|
||||
await refresh()
|
||||
})
|
||||
|
||||
@@ -91,7 +91,7 @@ const goToTerminal = () => {
|
||||
|
||||
const currentLoadingBars = ref(Object.values(await progress_bars_list().catch(handleError)))
|
||||
|
||||
await loading_listener(async () => {
|
||||
const unlistenLoading = await loading_listener(async () => {
|
||||
await refreshInfo()
|
||||
})
|
||||
|
||||
@@ -128,6 +128,8 @@ onMounted(() => {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutside)
|
||||
unlistenProcess()
|
||||
unlistenLoading()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<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">
|
||||
<Avatar :src="project.icon_url" size="md" class="search-icon" />
|
||||
</div>
|
||||
@@ -117,14 +125,7 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
const installing = ref(false)
|
||||
|
||||
const installed = ref(
|
||||
props.instance
|
||||
? Object.values(props.instance.projects).some(
|
||||
(p) => p.metadata?.project?.id === props.project.project_id
|
||||
)
|
||||
: false
|
||||
)
|
||||
const installed = ref(props.project.installed)
|
||||
|
||||
async function install() {
|
||||
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
|
||||
/// Returns [u32]
|
||||
export async function get_all_running_profile_paths(profile_path) {
|
||||
return await invoke('process_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', { profilePath })
|
||||
}
|
||||
|
||||
/// Gets all running process IDs with a given profile path
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
<script setup>
|
||||
import { computed, onMounted, ref, watch } from 'vue'
|
||||
import { computed, nextTick, ref, readonly, shallowRef, watch } from 'vue'
|
||||
import {
|
||||
Pagination,
|
||||
Checkbox,
|
||||
Button,
|
||||
ClearIcon,
|
||||
SearchIcon,
|
||||
DropdownSelect,
|
||||
SearchFilter,
|
||||
Card,
|
||||
ClientIcon,
|
||||
@@ -15,74 +14,337 @@ import {
|
||||
formatCategoryHeader,
|
||||
formatCategory,
|
||||
Promotion,
|
||||
DropdownSelect,
|
||||
} from 'omorphia'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { handleError, useSearch } from '@/store/state'
|
||||
import { handleError } from '@/store/state'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
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 InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
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 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 modInstallModal = ref(null)
|
||||
const incompatibilityWarningModal = ref(null)
|
||||
|
||||
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 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 loaders = ref([])
|
||||
const availableGameVersions = ref([])
|
||||
const results = shallowRef([])
|
||||
const pageCount = computed(() =>
|
||||
results.value ? Math.ceil(results.value.total_hits / results.value.limit) : 1
|
||||
)
|
||||
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
|
||||
if (searchStore.projectType === 'modpack') {
|
||||
searchStore.instanceContext = null
|
||||
function getArrayOrString(x) {
|
||||
if (typeof x === 'string' || x instanceof String) {
|
||||
return [x]
|
||||
} else {
|
||||
return x
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
||||
get_categories().catch(handleError),
|
||||
get_loaders().catch(handleError),
|
||||
get_game_versions().catch(handleError),
|
||||
])
|
||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||
if (searchStore.projectType === 'modpack') {
|
||||
searchStore.instanceContext = null
|
||||
if (route.query.iv) {
|
||||
ignoreInstanceGameVersions.value = route.query.iv === 'true'
|
||||
}
|
||||
if (route.query.il) {
|
||||
ignoreInstanceLoaders.value = route.query.il === 'true'
|
||||
}
|
||||
if (route.query.i) {
|
||||
instanceContext.value = await getInstance(route.query.i, true)
|
||||
}
|
||||
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 values = new Map()
|
||||
for (const category of categories.value.filter(
|
||||
(cat) =>
|
||||
cat.project_type ===
|
||||
(searchStore.projectType === 'datapack' ? 'mod' : searchStore.projectType)
|
||||
(cat) => cat.project_type === (projectType.value === 'datapack' ? 'mod' : projectType.value)
|
||||
)) {
|
||||
if (!values.has(category.header)) {
|
||||
values.set(category.header, [])
|
||||
@@ -92,128 +354,188 @@ const sortedCategories = computed(() => {
|
||||
return values
|
||||
})
|
||||
|
||||
const getSearchResults = async () => {
|
||||
const queryString = searchStore.getQueryString()
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search${queryString}`,
|
||||
'search results'
|
||||
)
|
||||
searchStore.setSearchResults(response)
|
||||
async function clearFilters() {
|
||||
for (const facet of [...facets.value]) {
|
||||
await toggleFacet(facet, true)
|
||||
}
|
||||
for (const facet of [...orFacets.value]) {
|
||||
await toggleOrFacet(facet, true)
|
||||
}
|
||||
|
||||
onlyOpenSource.value = false
|
||||
selectedVersions.value = []
|
||||
selectedEnvironments.value = []
|
||||
await onSearchChange(1)
|
||||
}
|
||||
|
||||
const handleReset = async () => {
|
||||
searchStore.currentPage = 1
|
||||
searchStore.offset = 0
|
||||
searchStore.resetFilters()
|
||||
await getSearchResults()
|
||||
async function toggleFacet(elementName, doNotSendRequest = false) {
|
||||
const index = facets.value.indexOf(elementName)
|
||||
|
||||
if (index !== -1) {
|
||||
facets.value.splice(index, 1)
|
||||
} else {
|
||||
facets.value.push(elementName)
|
||||
}
|
||||
|
||||
if (!doNotSendRequest) {
|
||||
await onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleFacet = async (facet) => {
|
||||
searchStore.currentPage = 1
|
||||
searchStore.offset = 0
|
||||
const index = searchStore.facets.indexOf(facet)
|
||||
async function toggleOrFacet(elementName, doNotSendRequest) {
|
||||
const index = orFacets.value.indexOf(elementName)
|
||||
if (index !== -1) {
|
||||
orFacets.value.splice(index, 1)
|
||||
} else {
|
||||
orFacets.value.push(elementName)
|
||||
}
|
||||
|
||||
if (index !== -1) searchStore.facets.splice(index, 1)
|
||||
else searchStore.facets.push(facet)
|
||||
|
||||
await switchPage(1)
|
||||
if (!doNotSendRequest) {
|
||||
await onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
const toggleOrFacet = async (orFacet) => {
|
||||
const index = searchStore.orFacets.indexOf(orFacet)
|
||||
function toggleEnv(environment, sendRequest) {
|
||||
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)
|
||||
else searchStore.orFacets.push(orFacet)
|
||||
|
||||
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()
|
||||
if (!sendRequest) {
|
||||
onSearchChange(1)
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => route.params.projectType,
|
||||
async (projectType) => {
|
||||
if (!projectType) return
|
||||
searchStore.projectType = projectType
|
||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${searchStore.projectType}` })
|
||||
await handleReset()
|
||||
await switchPage(1)
|
||||
async (newType) => {
|
||||
if (!newType) return
|
||||
projectType.value = newType
|
||||
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${projectType.value}` })
|
||||
|
||||
sortType.value = { display: 'Relevance', name: 'relevance' }
|
||||
query.value = ''
|
||||
|
||||
loading.value = true
|
||||
await clearFilters()
|
||||
loading.value = false
|
||||
}
|
||||
)
|
||||
|
||||
const handleInstanceSwitch = async (value) => {
|
||||
searchStore.ignoreInstance = value
|
||||
await switchPage(1)
|
||||
}
|
||||
const [categories, loaders, availableGameVersions] = await Promise.all([
|
||||
get_categories().catch(handleError).then(ref),
|
||||
get_loaders().catch(handleError).then(ref),
|
||||
get_game_versions().catch(handleError).then(ref),
|
||||
refreshSearch(),
|
||||
])
|
||||
|
||||
const selectableProjectTypes = computed(() => {
|
||||
const values = [
|
||||
{ label: 'Data Packs', href: `/browse/datapack` },
|
||||
{ label: 'Shaders', href: `/browse/shader` },
|
||||
{ label: 'Resource Packs', href: `/browse/resourcepack` },
|
||||
]
|
||||
|
||||
if (searchStore.instanceContext) {
|
||||
if (searchStore.instanceContext.metadata.loader !== 'vanilla') {
|
||||
if (instanceContext.value) {
|
||||
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' })
|
||||
}
|
||||
} else {
|
||||
values.unshift({ label: 'Data Packs', href: `/browse/datapack` })
|
||||
values.unshift({ label: 'Mods', href: '/browse/mod' })
|
||||
values.unshift({ label: 'Modpacks', href: '/browse/modpack' })
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<div class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small>
|
||||
<template #content>
|
||||
<Checkbox
|
||||
:model-value="searchStore.ignoreInstance"
|
||||
:checked="searchStore.ignoreInstance"
|
||||
label="Show unsupported content"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||
<div v-if="instanceContext" class="small-instance">
|
||||
<div class="instance">
|
||||
<Avatar
|
||||
:src="
|
||||
!instanceContext.metadata.icon ||
|
||||
(instanceContext.metadata.icon && instanceContext.metadata.icon.startsWith('http'))
|
||||
? instanceContext.metadata.icon
|
||||
: convertFileSrc(instanceContext.metadata.icon)
|
||||
"
|
||||
:alt="instanceContext.metadata.name"
|
||||
size="sm"
|
||||
/>
|
||||
</template>
|
||||
</Instance>
|
||||
<div class="small-instance_info">
|
||||
<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">
|
||||
<Button
|
||||
role="button"
|
||||
:disabled="
|
||||
!(
|
||||
searchStore.facets.length > 0 ||
|
||||
searchStore.orFacets.length > 0 ||
|
||||
searchStore.environments.server === true ||
|
||||
searchStore.environments.client === true ||
|
||||
searchStore.openSource === true ||
|
||||
searchStore.activeVersions.length > 0
|
||||
)
|
||||
onlyOpenSource === false &&
|
||||
selectedEnvironments.length === 0 &&
|
||||
selectedVersions.length === 0 &&
|
||||
facets.length === 0 &&
|
||||
orFacets.length === 0
|
||||
"
|
||||
@click="handleReset"
|
||||
><ClearIcon />Clear Filters</Button
|
||||
@click="clearFilters"
|
||||
>
|
||||
<ClearIcon /> Clear Filters
|
||||
</Button>
|
||||
<div v-if="showLoaders" class="loaders">
|
||||
<h2>Loaders</h2>
|
||||
<div
|
||||
v-for="loader in loaders.filter(
|
||||
(l) =>
|
||||
(searchStore.projectType !== 'mod' &&
|
||||
l.supported_project_types?.includes(searchStore.projectType)) ||
|
||||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
(projectType !== 'mod' && l.supported_project_types?.includes(projectType)) ||
|
||||
(projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
||||
)"
|
||||
:key="loader"
|
||||
>
|
||||
<SearchFilter
|
||||
:active-filters="searchStore.orFacets"
|
||||
:active-filters="orFacets"
|
||||
:icon="loader.icon"
|
||||
:display-name="formatCategory(loader.name)"
|
||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||
@@ -222,49 +544,11 @@ const selectableProjectTypes = computed(() => {
|
||||
/>
|
||||
</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">
|
||||
<h2>Minecraft versions</h2>
|
||||
<Checkbox v-model="showSnapshots" class="filter-checkbox" label="Include snapshots" />
|
||||
<multiselect
|
||||
v-model="searchStore.activeVersions"
|
||||
v-model="selectedVersions"
|
||||
:options="
|
||||
showSnapshots
|
||||
? availableGameVersions.map((x) => x.version)
|
||||
@@ -279,21 +563,59 @@ const selectableProjectTypes = computed(() => {
|
||||
:clear-search-on-select="false"
|
||||
:show-labels="false"
|
||||
placeholder="Choose versions..."
|
||||
@update:model-value="getSearchResults"
|
||||
@update:model-value="onSearchChange(1)"
|
||||
/>
|
||||
</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">
|
||||
<h2>Open source</h2>
|
||||
<Checkbox
|
||||
v-model="searchStore.openSource"
|
||||
v-model="onlyOpenSource"
|
||||
label="Open source only"
|
||||
class="filter-checkbox"
|
||||
label="Open source"
|
||||
@click="getSearchResults"
|
||||
@update:model-value="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="search">
|
||||
<div ref="searchWrapper" class="search">
|
||||
<Promotion class="promotion" />
|
||||
<Card class="project-type-container">
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
@@ -302,64 +624,59 @@ const selectableProjectTypes = computed(() => {
|
||||
<div class="iconified-input">
|
||||
<SearchIcon aria-hidden="true" />
|
||||
<input
|
||||
v-model="searchStore.searchInput"
|
||||
v-model="query"
|
||||
autocomplete="off"
|
||||
type="text"
|
||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||
@input="getSearchResults"
|
||||
:placeholder="`Search ${projectType}s...`"
|
||||
@input="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Sort by</span>
|
||||
<DropdownSelect
|
||||
v-model="searchStore.filter"
|
||||
name="Sort dropdown"
|
||||
:options="[
|
||||
'Relevance',
|
||||
'Download count',
|
||||
'Follow count',
|
||||
'Recently published',
|
||||
'Recently updated',
|
||||
]"
|
||||
class="sort-dropdown"
|
||||
@change="getSearchResults"
|
||||
v-model="sortType"
|
||||
name="Sort by"
|
||||
:options="sortTypes"
|
||||
:display-name="(option) => option?.display"
|
||||
@change="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
<div class="inline-option">
|
||||
<span>Show per page</span>
|
||||
<DropdownSelect
|
||||
v-model="searchStore.limit"
|
||||
name="Limit dropdown"
|
||||
v-model="maxResults"
|
||||
name="Max results"
|
||||
:options="[5, 10, 15, 20, 50, 100]"
|
||||
:default-value="searchStore.limit.toString()"
|
||||
:model-value="searchStore.limit.toString()"
|
||||
:default-value="maxResults"
|
||||
:model-value="maxResults"
|
||||
class="limit-dropdown"
|
||||
@change="getSearchResults"
|
||||
@change="onSearchChange(1)"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Pagination
|
||||
:page="searchStore.currentPage"
|
||||
:count="searchStore.pageCount"
|
||||
@switch-page="switchPage"
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||
class="pagination-before"
|
||||
@switch-page="onSearchChange"
|
||||
/>
|
||||
<SplashScreen v-if="loading" />
|
||||
<section v-else class="project-list display-mode--list instance-results" role="list">
|
||||
<SearchCard
|
||||
v-for="result in searchStore.searchResults"
|
||||
v-for="result in results.hits"
|
||||
:key="result?.project_id"
|
||||
:project="result"
|
||||
:instance="searchStore.instanceContext"
|
||||
:instance="instanceContext"
|
||||
:categories="[
|
||||
...categories.filter(
|
||||
(cat) =>
|
||||
result?.display_categories.includes(cat.name) &&
|
||||
cat.project_type === searchStore.projectType
|
||||
result?.display_categories.includes(cat.name) && cat.project_type === projectType
|
||||
),
|
||||
...loaders.filter(
|
||||
(loader) =>
|
||||
result?.display_categories.includes(loader.name) &&
|
||||
loader.supported_project_types?.includes(searchStore.projectType)
|
||||
loader.supported_project_types?.includes(projectType)
|
||||
),
|
||||
]"
|
||||
:confirm-modal="confirmModal"
|
||||
@@ -367,10 +684,12 @@ const selectableProjectTypes = computed(() => {
|
||||
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||
/>
|
||||
</section>
|
||||
<Pagination
|
||||
:page="searchStore.currentPage"
|
||||
:count="searchStore.pageCount"
|
||||
@switch-page="switchPage"
|
||||
<pagination
|
||||
:page="currentPage"
|
||||
:count="pageCount"
|
||||
:link-function="(x) => getSearchUrl(x <= 1 ? 0 : (x - 1) * maxResults)"
|
||||
class="pagination-after"
|
||||
@switch-page="onSearchChangeToTop"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -381,6 +700,32 @@ const selectableProjectTypes = computed(() => {
|
||||
|
||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||
<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 {
|
||||
margin-bottom: 0.3rem;
|
||||
font-size: 1rem;
|
||||
@@ -492,7 +837,8 @@ const selectableProjectTypes = computed(() => {
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 1rem 0 21rem;
|
||||
scroll-behavior: smooth;
|
||||
margin: 0 1rem 0.5rem 21rem;
|
||||
width: calc(100% - 22rem);
|
||||
|
||||
.loading {
|
||||
|
||||
@@ -36,7 +36,6 @@ const getInstances = async () => {
|
||||
}
|
||||
|
||||
const getFeaturedModpacks = async () => {
|
||||
console.log(filter.value)
|
||||
const response = await useFetch(
|
||||
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
|
||||
'featured modpacks'
|
||||
|
||||
@@ -21,35 +21,52 @@ fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).jo
|
||||
const settings = ref(fetchSettings)
|
||||
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
|
||||
|
||||
watch(settings.value, async (oldSettings, newSettings) => {
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
watch(
|
||||
settings,
|
||||
async (oldSettings, newSettings) => {
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
|
||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||
setSettings.java_globals.JAVA_8 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_17?.path === '') {
|
||||
setSettings.java_globals.JAVA_17 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||
setSettings.java_globals.JAVA_8 = undefined
|
||||
}
|
||||
if (setSettings.java_globals.JAVA_17?.path === '') {
|
||||
setSettings.java_globals.JAVA_17 = undefined
|
||||
}
|
||||
|
||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_args = setSettings.envArgs
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
if (setSettings.java_globals.JAVA_8?.path) {
|
||||
setSettings.java_globals.JAVA_8.path = setSettings.java_globals.JAVA_8.path.replace(
|
||||
'java.exe',
|
||||
'javaw.exe'
|
||||
)
|
||||
}
|
||||
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.hooks.pre_launch = null
|
||||
}
|
||||
if (!setSettings.hooks.wrapper) {
|
||||
setSettings.hooks.wrapper = null
|
||||
}
|
||||
if (!setSettings.hooks.post_exit) {
|
||||
setSettings.hooks.post_exit = null
|
||||
}
|
||||
setSettings.custom_java_args = setSettings.javaArgs.trim().split(/\s+/).filter(Boolean)
|
||||
setSettings.custom_env_args = setSettings.envArgs
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
@@ -119,7 +136,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
id="max-downloads"
|
||||
v-model="settings.max_concurrent_downloads"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:max="10"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
@@ -136,7 +153,7 @@ watch(settings.value, async (oldSettings, newSettings) => {
|
||||
id="max-writes"
|
||||
v-model="settings.max_concurrent_writes"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:max="50"
|
||||
:step="1"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -126,22 +126,22 @@ import { process_listener, profile_listener } from '@/helpers/events'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
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 ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const router = useRouter()
|
||||
const searchStore = useSearch()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const instance = ref(await get(route.params.id).catch(handleError))
|
||||
|
||||
searchStore.instanceContext = instance.value
|
||||
breadcrumbs.setName('Instance', instance.value.metadata.name)
|
||||
breadcrumbs.setContext({
|
||||
name: instance.value.metadata.name,
|
||||
link: route.path,
|
||||
query: route.query,
|
||||
})
|
||||
|
||||
const loadingBar = useLoading()
|
||||
@@ -183,7 +183,6 @@ const stopInstance = async () => {
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
searchStore.instanceContext = instance.value
|
||||
}
|
||||
})
|
||||
|
||||
@@ -242,6 +241,7 @@ const handleOptionsClick = async (args) => {
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
path: `/browse/${instance.value.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: route.params.id },
|
||||
})
|
||||
break
|
||||
case 'edit':
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
<Card class="log-card">
|
||||
<div class="button-row">
|
||||
<DropdownSelect
|
||||
v-model="selectedLogIndex"
|
||||
:default-value="0"
|
||||
name="Log date"
|
||||
:model-value="logs[selectedLogIndex]"
|
||||
:options="logs"
|
||||
:display-name="(option) => option?.name"
|
||||
:options="logs.map((_, index) => index)"
|
||||
:display-name="(option) => logs[option]?.name"
|
||||
:disabled="logs.length === 0"
|
||||
@change="(value) => (selectedLogIndex = value.index)"
|
||||
/>
|
||||
<div class="button-group">
|
||||
<Button :disabled="!logs[selectedLogIndex]" @click="copyLog()">
|
||||
@@ -30,7 +30,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<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 />
|
||||
</span>
|
||||
</div>
|
||||
@@ -68,15 +72,18 @@ const props = defineProps({
|
||||
})
|
||||
|
||||
async function getLiveLog() {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} else {
|
||||
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
||||
}
|
||||
if (route.params.id) {
|
||||
const uuids = await get_uuids_by_profile_path(route.params.id).catch(handleError)
|
||||
let returnValue
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} 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() {
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
@click="
|
||||
router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: $route.params.id },
|
||||
})
|
||||
"
|
||||
>
|
||||
@@ -80,7 +81,11 @@
|
||||
</Button>
|
||||
</div>
|
||||
<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" />
|
||||
{{ mod.name }}
|
||||
</router-link>
|
||||
@@ -305,7 +310,6 @@ async function updateProject(mod) {
|
||||
|
||||
async function toggleDisableMod(mod) {
|
||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||
console.log(mod.disabled)
|
||||
mod.disabled = !mod.disabled
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
<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">
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
@@ -105,8 +113,8 @@
|
||||
placeholder="Select categories..."
|
||||
@tag="
|
||||
(newTag) => {
|
||||
groups.push(newTag)
|
||||
availableGroups.push(newTag)
|
||||
groups.push(newTag.trim().substring(0, 32))
|
||||
availableGroups.push(newTag.trim().substring(0, 32))
|
||||
}
|
||||
"
|
||||
/>
|
||||
@@ -288,7 +296,7 @@
|
||||
id="delete-profile"
|
||||
class="btn btn-danger"
|
||||
:disabled="removing"
|
||||
@click="removeProfile"
|
||||
@click="$refs.modal_confirm.show()"
|
||||
>
|
||||
<TrashIcon /> Delete
|
||||
</button>
|
||||
@@ -311,6 +319,7 @@ import {
|
||||
XIcon,
|
||||
SaveIcon,
|
||||
HammerIcon,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
import { useRouter } from 'vue-router'
|
||||
@@ -398,7 +407,8 @@ const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
|
||||
watch(
|
||||
[
|
||||
title,
|
||||
groups.value,
|
||||
groups,
|
||||
groups,
|
||||
overrideJavaInstall,
|
||||
javaInstall,
|
||||
overrideJavaArgs,
|
||||
@@ -406,17 +416,17 @@ watch(
|
||||
overrideEnvVars,
|
||||
envVars,
|
||||
overrideMemorySettings,
|
||||
memory.value,
|
||||
memory,
|
||||
overrideWindowSettings,
|
||||
resolution.value,
|
||||
resolution,
|
||||
overrideHooks,
|
||||
hooks.value,
|
||||
hooks,
|
||||
],
|
||||
async () => {
|
||||
const editProfile = {
|
||||
metadata: {
|
||||
name: title.value,
|
||||
groups: groups.value,
|
||||
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
@@ -424,6 +434,10 @@ watch(
|
||||
if (overrideJavaInstall.value) {
|
||||
if (javaInstall.value.path !== '') {
|
||||
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)
|
||||
}
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
const repairing = ref(false)
|
||||
|
||||
@@ -1,7 +1,29 @@
|
||||
<template>
|
||||
<div class="root-container">
|
||||
<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">
|
||||
<Avatar size="lg" :src="data.icon_url" />
|
||||
<div class="instance-info">
|
||||
@@ -119,8 +141,8 @@
|
||||
<span>Wiki</span>
|
||||
</a>
|
||||
<a
|
||||
v-if="data.wiki_url"
|
||||
:href="data.wiki_url"
|
||||
v-if="data.discord_url"
|
||||
:href="data.discord_url"
|
||||
class="title"
|
||||
rel="noopener nofollow ugc external"
|
||||
>
|
||||
@@ -238,7 +260,12 @@ import {
|
||||
} from '@/assets/external'
|
||||
import { get_categories, get_loaders } from '@/helpers/tags'
|
||||
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 relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
@@ -246,16 +273,13 @@ import { ref, shallowRef, watch } from 'vue'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { useSearch } from '@/store/search'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
|
||||
const searchStore = useSearch()
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
@@ -263,11 +287,11 @@ const breadcrumbs = useBreadcrumbs()
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const incompatibilityWarning = ref(null)
|
||||
|
||||
const options = ref(null)
|
||||
const instance = ref(searchStore.instanceContext)
|
||||
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}/version`, 'project').then(
|
||||
shallowRef
|
||||
@@ -280,6 +304,7 @@ const [data, versions, members, dependencies, categories, loaders] = await Promi
|
||||
),
|
||||
get_loaders().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(
|
||||
@@ -587,4 +612,29 @@ const handleOptionsClick = (args) => {
|
||||
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>
|
||||
|
||||
@@ -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 { useBreadcrumbs } from './breadcrumbs'
|
||||
import { useLoading } from './loading'
|
||||
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