You've already forked AstralRinth
forked from didirus/AstralRinth
Search UI improvements (#107)
* Base impl * Make project type selectable * Update Browse.vue * address changes * Quick create * Run linter * fix merge * Addressed changes * Installation improvements * Run lint * resourcepacks * automatic installation of dependencies * Fix bugs with search * Addressed changes * Run linter * Fixed direct install not working * Remove back to search * Update Index.vue * Addressed some changes * Shader fix * fix resetting * Update Browse.vue * Direct install from search * More improvements * Update SearchCard.vue * Card V2 * Run linter * add instance ignoring * Update Browse.vue * Finalize changes * Update SearchCard.vue * More adjustments * Fix out of order rendering * Run linter * Fix issues * Add unlisteners --------- Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com> Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
@@ -127,6 +127,14 @@ impl Children {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !mc_exit_status.success() {
|
if !mc_exit_status.success() {
|
||||||
|
emit_process(
|
||||||
|
uuid,
|
||||||
|
current_pid,
|
||||||
|
ProcessPayloadType::Finished,
|
||||||
|
"Exited process",
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
return Ok(mc_exit_status); // Err for a non-zero exit is handled in helper
|
return Ok(mc_exit_status); // Err for a non-zero exit is handled in helper
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -75,24 +75,24 @@ const loading = useLoading()
|
|||||||
<LibraryIcon />
|
<LibraryIcon />
|
||||||
<span v-if="!themeStore.collapsedNavigation">Library</span>
|
<span v-if="!themeStore.collapsedNavigation">Library</span>
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
<Button
|
|
||||||
color="primary"
|
|
||||||
:class="{
|
|
||||||
'icon-only': themeStore.collapsedNavigation,
|
|
||||||
'collapsed-button': themeStore.collapsedNavigation,
|
|
||||||
'expanded-button': !themeStore.collapsedNavigation,
|
|
||||||
}"
|
|
||||||
@click="() => $refs.installationModal.show()"
|
|
||||||
>
|
|
||||||
<PlusIcon />
|
|
||||||
<span v-if="!themeStore.collapsedNavigation" class="no-wrap">New Instance</span>
|
|
||||||
</Button>
|
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<InstanceCreationModal ref="installationModal" />
|
<InstanceCreationModal ref="installationModal" />
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings pages-list">
|
<div class="settings pages-list">
|
||||||
|
<Button
|
||||||
|
class="sleek-primary"
|
||||||
|
:class="{
|
||||||
|
'icon-only': themeStore.collapsedNavigation,
|
||||||
|
'collapsed-button': themeStore.collapsedNavigation,
|
||||||
|
'expanded-button': !themeStore.collapsedNavigation,
|
||||||
|
}"
|
||||||
|
@click="() => $refs.installationModal.show()"
|
||||||
|
>
|
||||||
|
<PlusIcon />
|
||||||
|
<span v-if="!themeStore.collapsedNavigation" class="no-wrap">New Instance</span>
|
||||||
|
</Button>
|
||||||
<RouterLink
|
<RouterLink
|
||||||
to="/settings"
|
to="/settings"
|
||||||
class="btn"
|
class="btn"
|
||||||
@@ -136,6 +136,10 @@ const loading = useLoading()
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.sleek-primary {
|
||||||
|
background-color: var(--color-brand-highlight);
|
||||||
|
transition: all ease-in-out 0.1s;
|
||||||
|
}
|
||||||
.container {
|
.container {
|
||||||
--appbar-height: 3.25rem;
|
--appbar-height: 3.25rem;
|
||||||
--sidebar-width: 5rem;
|
--sidebar-width: 5rem;
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ a {
|
|||||||
.multiselect {
|
.multiselect {
|
||||||
color: var(--color-base) !important;
|
color: var(--color-base) !important;
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
|
max-width: 15rem;
|
||||||
|
width: 100% !important;
|
||||||
|
|
||||||
.multiselect__input:focus-visible {
|
.multiselect__input:focus-visible {
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
@@ -167,3 +169,7 @@ a {
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
color: var(--color-contrast);
|
color: var(--color-contrast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: none !important;
|
||||||
|
}
|
||||||
|
|||||||
136
theseus_gui/src/components/ui/IncompatibilityWarningModal.vue
Normal file
136
theseus_gui/src/components/ui/IncompatibilityWarningModal.vue
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
<template>
|
||||||
|
<Modal ref="incompatibleModal" header="Incompatibility warning">
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>
|
||||||
|
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
|
||||||
|
you're trying to install it on. Are you sure you want to continue? Dependencies will not be
|
||||||
|
installed.
|
||||||
|
</p>
|
||||||
|
<table>
|
||||||
|
<tr class="header">
|
||||||
|
<th>{{ instance?.metadata.name }}</th>
|
||||||
|
<th>{{ projectTitle }}</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="content">
|
||||||
|
<td class="data">
|
||||||
|
{{ instance?.metadata.loader }} {{ instance?.metadata.game_version }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<DropdownSelect
|
||||||
|
v-if="versions?.length > 1"
|
||||||
|
v-model="selectedVersion"
|
||||||
|
:options="versions"
|
||||||
|
placeholder="Select version"
|
||||||
|
:display-name="
|
||||||
|
(version) =>
|
||||||
|
`${version?.name} (${version?.loaders
|
||||||
|
.map((name) => formatCategory(name))
|
||||||
|
.join(', ')} - ${version?.game_versions.join(', ')})`
|
||||||
|
"
|
||||||
|
render-up
|
||||||
|
/>
|
||||||
|
<span v-else>
|
||||||
|
<span>
|
||||||
|
{{ selectedVersion?.name }} ({{
|
||||||
|
selectedVersion?.loaders.map((name) => formatCategory(name)).join(', ')
|
||||||
|
}}
|
||||||
|
- {{ selectedVersion?.game_versions.join(', ') }})
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class="button-group">
|
||||||
|
<Button @click="() => incompatibleModal.hide()"><XIcon />Cancel</Button>
|
||||||
|
<Button color="primary" :disabled="installing" @click="install()">
|
||||||
|
<DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
|
||||||
|
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||||
|
import { defineExpose, ref } from 'vue'
|
||||||
|
|
||||||
|
const instance = ref(null)
|
||||||
|
const projectTitle = ref(null)
|
||||||
|
const versions = ref(null)
|
||||||
|
const selectedVersion = ref(null)
|
||||||
|
const incompatibleModal = ref(null)
|
||||||
|
const installing = ref(false)
|
||||||
|
|
||||||
|
let markInstalled = () => {}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
show: (instanceVal, projectTitleVal, selectedVersions, extMarkInstalled) => {
|
||||||
|
instance.value = instanceVal
|
||||||
|
projectTitle.value = projectTitleVal
|
||||||
|
versions.value = selectedVersions
|
||||||
|
selectedVersion.value = selectedVersions[0]
|
||||||
|
incompatibleModal.value.show()
|
||||||
|
markInstalled = extMarkInstalled
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const install = async () => {
|
||||||
|
installing.value = true
|
||||||
|
await installMod(instance.value.path, selectedVersion.value.id)
|
||||||
|
installing.value = false
|
||||||
|
markInstalled()
|
||||||
|
incompatibleModal.value.hide()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.data {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
border-collapse: collapse;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
overflow: hidden;
|
||||||
|
border-bottom: 1px solid var(--color-button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
th:first-child {
|
||||||
|
border-top-left-radius: var(--radius-lg);
|
||||||
|
border-right: 1px solid var(--color-button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
th:last-child {
|
||||||
|
border-top-right-radius: var(--radius-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
td:first-child {
|
||||||
|
border-right: 1px solid var(--color-button-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -2,11 +2,15 @@
|
|||||||
import { Button, Modal, XIcon, DownloadIcon } from 'omorphia'
|
import { Button, Modal, XIcon, DownloadIcon } from 'omorphia'
|
||||||
import { install as pack_install } from '@/helpers/pack'
|
import { install as pack_install } from '@/helpers/pack'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
const version = ref('')
|
const version = ref('')
|
||||||
const title = ref('')
|
const title = ref('')
|
||||||
const icon = ref('')
|
const icon = ref('')
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
|
const installing = ref(false)
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: (id, projectTitle, projectIcon) => {
|
show: (id, projectTitle, projectIcon) => {
|
||||||
@@ -18,8 +22,11 @@ defineExpose({
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function install() {
|
async function install() {
|
||||||
confirmModal.value.hide()
|
installing.value = true
|
||||||
|
let id = await pack_install(version.value)
|
||||||
await pack_install(version.value, title.value, icon.value ? icon.value : null)
|
await pack_install(version.value, title.value, icon.value ? icon.value : null)
|
||||||
|
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
|
||||||
|
confirmModal.value.hide()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -31,7 +38,9 @@ async function install() {
|
|||||||
</p>
|
</p>
|
||||||
<div class="button-group">
|
<div class="button-group">
|
||||||
<Button @click="() => $refs.confirmModal.hide()"><XIcon />Cancel</Button>
|
<Button @click="() => $refs.confirmModal.hide()"><XIcon />Cancel</Button>
|
||||||
<Button color="primary" @click="install()"><DownloadIcon /> Install</Button>
|
<Button color="primary" :disabled="installing" @click="install()"
|
||||||
|
><DownloadIcon /> {{ installing ? 'Installing' : 'Install' }}</Button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref } from 'vue'
|
import { onUnmounted, ref, useSlots } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo } from 'omorphia'
|
import { Card, SaveIcon, XIcon, Avatar, AnimatedLogo } from 'omorphia'
|
||||||
@@ -33,13 +33,14 @@ const playing = ref(false)
|
|||||||
|
|
||||||
const uuid = ref(null)
|
const uuid = ref(null)
|
||||||
const modLoading = ref(false)
|
const modLoading = ref(false)
|
||||||
|
const slots = useSlots()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const seeInstance = async () => {
|
const seeInstance = async () => {
|
||||||
const instancePath = props.instance.metadata
|
const instancePath = props.instance.metadata
|
||||||
? `/instance/${encodeURIComponent(props.instance.path)}`
|
? `/instance/${encodeURIComponent(props.instance.path)}/`
|
||||||
: `/project/${encodeURIComponent(props.instance.project_id)}`
|
: `/project/${encodeURIComponent(props.instance.project_id)}/`
|
||||||
|
|
||||||
await router.push(instancePath)
|
await router.push(instancePath)
|
||||||
}
|
}
|
||||||
@@ -118,31 +119,42 @@ const stop = async (e) => {
|
|||||||
uuid.value = null
|
uuid.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
await process_listener((e) => {
|
const unlisten = await process_listener((e) => {
|
||||||
if (e.event === 'finished' && e.uuid === uuid.value) playing.value = false
|
if (e.event === 'finished' && e.uuid === uuid.value) playing.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => unlisten())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="instance">
|
<div class="instance">
|
||||||
<Card v-if="props.small" class="instance-small-card button-base" @click="seeInstance">
|
<Card v-if="props.small" class="instance-small-card" :class="{ 'button-base': !slots.content }">
|
||||||
<Avatar
|
<div
|
||||||
:src="
|
class="instance-small-card__description"
|
||||||
!props.instance.metadata.icon ||
|
:class="{ 'button-base': slots.content }"
|
||||||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
@click="seeInstance"
|
||||||
? props.instance.metadata.icon
|
>
|
||||||
: convertFileSrc(instance.metadata?.icon)
|
<Avatar
|
||||||
"
|
:src="
|
||||||
:alt="props.instance.metadata.name"
|
!props.instance.metadata.icon ||
|
||||||
size="sm"
|
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
||||||
/>
|
? props.instance.metadata.icon
|
||||||
<div class="instance-small-card__info">
|
: convertFileSrc(instance.metadata?.icon)
|
||||||
<span class="title">{{ props.instance.metadata.name }}</span>
|
"
|
||||||
{{
|
:alt="props.instance.metadata.name"
|
||||||
props.instance.metadata.loader.charAt(0).toUpperCase() +
|
size="sm"
|
||||||
props.instance.metadata.loader.slice(1)
|
/>
|
||||||
}}
|
<div class="instance-small-card__info">
|
||||||
{{ props.instance.metadata.game_version }}
|
<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>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<Card
|
<Card
|
||||||
@@ -180,7 +192,7 @@ await process_listener((e) => {
|
|||||||
>
|
>
|
||||||
<PlayIcon />
|
<PlayIcon />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="modLoading === true && playing === false" class="cta loading">
|
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||||
<AnimatedLogo class="loading" />
|
<AnimatedLogo class="loading" />
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -200,12 +212,27 @@ await process_listener((e) => {
|
|||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.instance-small-card {
|
.instance-small-card {
|
||||||
background-color: var(--color-bg) !important;
|
background-color: var(--color-bg) !important;
|
||||||
padding: 1rem !important;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
min-height: min-content !important;
|
min-height: min-content !important;
|
||||||
gap: 1rem;
|
gap: 0.5rem;
|
||||||
align-items: center;
|
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 {
|
.instance-small-card__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -217,6 +244,15 @@ await process_listener((e) => {
|
|||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.instance-small-card__content {
|
||||||
|
padding: var(--gap-xl);
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cta {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance {
|
.instance {
|
||||||
@@ -242,33 +278,6 @@ await process_listener((e) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.install {
|
|
||||||
background: var(--color-brand);
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stop {
|
|
||||||
background: var(--color-red);
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cta.loading {
|
|
||||||
background: hsl(220, 11%, 10%) !important;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.loading {
|
|
||||||
width: 2.5rem !important;
|
|
||||||
height: 2.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
width: 2.5rem !important;
|
|
||||||
height: 2.5rem !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.light-mode {
|
.light-mode {
|
||||||
.instance-card-item {
|
.instance-card-item {
|
||||||
background: hsl(0, 0%, 100%) !important;
|
background: hsl(0, 0%, 100%) !important;
|
||||||
@@ -304,6 +313,33 @@ await process_listener((e) => {
|
|||||||
filter: none !important; /* overrides button-base class */
|
filter: none !important; /* overrides button-base class */
|
||||||
box-shadow: var(--shadow-floating);
|
box-shadow: var(--shadow-floating);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.install {
|
||||||
|
background: var(--color-brand);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.stop {
|
||||||
|
background: var(--color-red);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.loading-cta {
|
||||||
|
background: hsl(220, 11%, 10%) !important;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
width: 2.5rem !important;
|
||||||
|
height: 2.5rem !important;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 2.5rem !important;
|
||||||
|
height: 2.5rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance-card-item {
|
.instance-card-item {
|
||||||
|
|||||||
@@ -190,6 +190,7 @@ const upload_icon = async () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!icon.value) return
|
||||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { computed, ref, shallowRef } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import { add_project_from_version as installMod, check_installed, list } from '@/helpers/profile'
|
import { add_project_from_version as installMod, check_installed, list } from '@/helpers/profile'
|
||||||
import { tauri } from '@tauri-apps/api'
|
import { tauri } from '@tauri-apps/api'
|
||||||
import { open } from '@tauri-apps/api/dialog'
|
import { open } from '@tauri-apps/api/dialog'
|
||||||
@@ -34,9 +34,9 @@ const gameVersion = ref(null)
|
|||||||
const creatingInstance = ref(false)
|
const creatingInstance = ref(false)
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: async (projectId, selectedVersion) => {
|
show: async (projectId, selectedVersions) => {
|
||||||
project.value = projectId
|
project.value = projectId
|
||||||
versions.value = selectedVersion
|
versions.value = selectedVersions
|
||||||
installModal.value.show()
|
installModal.value.show()
|
||||||
searchFilter.value = ''
|
searchFilter.value = ''
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ defineExpose({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const profiles = shallowRef(await getData())
|
const profiles = ref([])
|
||||||
|
|
||||||
async function install(instance) {
|
async function install(instance) {
|
||||||
instance.installing = true
|
instance.installing = true
|
||||||
@@ -109,6 +109,7 @@ const upload_icon = async () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!icon.value) return
|
||||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,7 +148,7 @@ const check_valid = computed(() => {
|
|||||||
<Modal ref="installModal" header="Install mod to instance">
|
<Modal ref="installModal" header="Install mod to instance">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
|
<input v-model="searchFilter" type="text" class="search" placeholder="Search for a profile" />
|
||||||
<div class="profiles">
|
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||||
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
||||||
<Button
|
<Button
|
||||||
color="raised"
|
color="raised"
|
||||||
@@ -265,6 +266,10 @@ const check_valid = computed(() => {
|
|||||||
.profiles {
|
.profiles {
|
||||||
max-height: 12rem;
|
max-height: 12rem;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
&.hide-creation {
|
||||||
|
max-height: 21rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.option {
|
.option {
|
||||||
@@ -277,6 +282,7 @@ const check_valid = computed(() => {
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0 0.5rem;
|
padding: 0 0.5rem;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
@@ -289,7 +295,9 @@ const check_valid = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.profile-button {
|
.profile-button {
|
||||||
|
align-content: start;
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
text-align: left;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,8 +67,7 @@ const showCard = ref(false)
|
|||||||
|
|
||||||
const currentProcesses = ref(await getRunningProfiles())
|
const currentProcesses = ref(await getRunningProfiles())
|
||||||
|
|
||||||
await process_listener(async (event) => {
|
await process_listener(async () => {
|
||||||
console.log(event)
|
|
||||||
await refresh()
|
await refresh()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
282
theseus_gui/src/components/ui/SearchCard.vue
Normal file
282
theseus_gui/src/components/ui/SearchCard.vue
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
<template>
|
||||||
|
<Card class="card button-base" @click="$router.push(`/project/${project.project_id}/`)">
|
||||||
|
<div class="icon">
|
||||||
|
<Avatar :src="project.icon_url" size="md" class="search-icon" />
|
||||||
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<div class="title joined-text">
|
||||||
|
<h2>{{ project.title }}</h2>
|
||||||
|
<span>by {{ project.author }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
{{ project.description }}
|
||||||
|
</div>
|
||||||
|
<div class="tags">
|
||||||
|
<Categories :categories="categories" :type="project.project_type">
|
||||||
|
<EnvironmentIndicator
|
||||||
|
:type-only="project.moderation"
|
||||||
|
:client-side="project.client_side"
|
||||||
|
:server-side="project.server_side"
|
||||||
|
:type="project.project_type"
|
||||||
|
:search="true"
|
||||||
|
/>
|
||||||
|
</Categories>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="stats button-group">
|
||||||
|
<div v-if="featured" class="badge">
|
||||||
|
<StarIcon />
|
||||||
|
Featured
|
||||||
|
</div>
|
||||||
|
<div class="badge">
|
||||||
|
<DownloadIcon />
|
||||||
|
{{ formatNumber(project.downloads) }}
|
||||||
|
</div>
|
||||||
|
<div class="badge">
|
||||||
|
<HeartIcon />
|
||||||
|
{{ formatNumber(project.follows) }}
|
||||||
|
</div>
|
||||||
|
<div class="badge">
|
||||||
|
<CalendarIcon />
|
||||||
|
{{ formatCategory(dayjs(project.date_modified).fromNow()) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="install">
|
||||||
|
<Button
|
||||||
|
:to="`/browse/${project.slug}`"
|
||||||
|
color="primary"
|
||||||
|
:disabled="installed || installing"
|
||||||
|
@click.stop="install()"
|
||||||
|
>
|
||||||
|
<DownloadIcon v-if="!installed" />
|
||||||
|
<CheckIcon v-else />
|
||||||
|
{{ installing ? 'Installing' : installed ? 'Installed' : 'Install' }}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {
|
||||||
|
Avatar,
|
||||||
|
Card,
|
||||||
|
Categories,
|
||||||
|
EnvironmentIndicator,
|
||||||
|
Button,
|
||||||
|
DownloadIcon,
|
||||||
|
formatNumber,
|
||||||
|
formatCategory,
|
||||||
|
HeartIcon,
|
||||||
|
CalendarIcon,
|
||||||
|
CheckIcon,
|
||||||
|
StarIcon,
|
||||||
|
} from 'omorphia'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
import { add_project_from_version as installMod, list } from '@/helpers/profile.js'
|
||||||
|
import { install as packInstall } from '@/helpers/pack.js'
|
||||||
|
import { installVersionDependencies } from '@/helpers/utils.js'
|
||||||
|
import { ofetch } from 'ofetch'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
backgroundImage: {
|
||||||
|
type: String,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
categories: {
|
||||||
|
type: Array,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
instance: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
confirmModal: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
modInstallModal: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
incompatibilityWarningModal: {
|
||||||
|
type: Object,
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
featured: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
async function install() {
|
||||||
|
installing.value = true
|
||||||
|
const versions = await ofetch(
|
||||||
|
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`
|
||||||
|
)
|
||||||
|
let queuedVersionData
|
||||||
|
|
||||||
|
if (!props.instance) {
|
||||||
|
queuedVersionData = versions[0]
|
||||||
|
} else {
|
||||||
|
queuedVersionData = versions.find(
|
||||||
|
(v) =>
|
||||||
|
v.game_versions.includes(props.instance.metadata.game_version) &&
|
||||||
|
v.loaders.includes(props.instance.metadata.loader)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.project.project_type === 'modpack') {
|
||||||
|
const packs = Object.values(await list())
|
||||||
|
if (
|
||||||
|
packs.length === 0 ||
|
||||||
|
!packs
|
||||||
|
.map((value) => value.metadata)
|
||||||
|
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||||
|
) {
|
||||||
|
let id = await packInstall(queuedVersionData.id, props.project.title, props.project.icon_url)
|
||||||
|
await router.push({ path: `/instance/${encodeURIComponent(id)}` })
|
||||||
|
} else {
|
||||||
|
props.confirmModal.show(queuedVersionData.id)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (props.instance) {
|
||||||
|
if (!queuedVersionData) {
|
||||||
|
props.incompatibilityWarningModal.show(
|
||||||
|
props.instance,
|
||||||
|
props.project.title,
|
||||||
|
versions,
|
||||||
|
() => (installed.value = true)
|
||||||
|
)
|
||||||
|
installing.value = false
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
await installMod(props.instance.path, queuedVersionData.id)
|
||||||
|
installVersionDependencies(props.instance, queuedVersionData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
props.modInstallModal.show(props.project.project_id, versions)
|
||||||
|
installing.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (props.instance) installed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
installing.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.icon {
|
||||||
|
grid-column: 1;
|
||||||
|
grid-row: 1;
|
||||||
|
align-self: center;
|
||||||
|
height: 6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
grid-column: 2 / 4;
|
||||||
|
flex-direction: column;
|
||||||
|
grid-row: 1;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.description {
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats {
|
||||||
|
grid-column: 1 / 3;
|
||||||
|
grid-row: 2;
|
||||||
|
justify-self: stretch;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.install {
|
||||||
|
grid-column: 3 / 4;
|
||||||
|
grid-row: 2;
|
||||||
|
justify-self: end;
|
||||||
|
align-self: start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
margin-bottom: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 6rem auto 7rem;
|
||||||
|
gap: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
|
||||||
|
&:active:not(&:disabled) {
|
||||||
|
scale: 0.98 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.joined-text {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex-direction: row;
|
||||||
|
column-gap: 0.5rem;
|
||||||
|
align-items: baseline;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: flex;
|
||||||
|
border-radius: var(--radius-md);
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 500;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--color-bg);
|
||||||
|
padding-block: var(--gap-sm);
|
||||||
|
padding-inline: var(--gap-lg);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 1.1rem;
|
||||||
|
height: 1.1rem;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.featured {
|
||||||
|
background-color: var(--color-brand-highlight);
|
||||||
|
color: var(--color-contrast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -3,7 +3,6 @@ import { computed, onMounted, ref, watch } from 'vue'
|
|||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
ProjectCard,
|
|
||||||
Checkbox,
|
Checkbox,
|
||||||
Button,
|
Button,
|
||||||
ClearIcon,
|
ClearIcon,
|
||||||
@@ -15,21 +14,37 @@ import {
|
|||||||
ServerIcon,
|
ServerIcon,
|
||||||
NavRow,
|
NavRow,
|
||||||
formatCategoryHeader,
|
formatCategoryHeader,
|
||||||
|
formatCategory,
|
||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
import { useSearch } from '@/store/state'
|
import { useSearch } 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 } from 'vue-router'
|
||||||
import Instance from '@/components/ui/Instance.vue'
|
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 SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||||
|
import Instance from '@/components/ui/Instance.vue'
|
||||||
|
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
|
||||||
const searchStore = useSearch()
|
const searchStore = useSearch()
|
||||||
searchStore.projectType = route.params.projectType
|
searchStore.projectType = route.params.projectType
|
||||||
const showVersions = ref(true)
|
const showVersions = computed(
|
||||||
const showLoaders = ref(true)
|
() => 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()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
@@ -40,6 +55,12 @@ const categories = ref([])
|
|||||||
const loaders = ref([])
|
const loaders = ref([])
|
||||||
const availableGameVersions = ref([])
|
const availableGameVersions = ref([])
|
||||||
|
|
||||||
|
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
||||||
|
|
||||||
|
if (searchStore.projectType === 'modpack') {
|
||||||
|
searchStore.instanceContext = null
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
;[categories.value, loaders.value, availableGameVersions.value] = await Promise.all([
|
||||||
get_categories(),
|
get_categories(),
|
||||||
@@ -72,12 +93,6 @@ const sortedCategories = computed(() => {
|
|||||||
|
|
||||||
const getSearchResults = async () => {
|
const getSearchResults = async () => {
|
||||||
const queryString = searchStore.getQueryString()
|
const queryString = searchStore.getQueryString()
|
||||||
if (searchStore.instanceContext) {
|
|
||||||
showVersions.value = false
|
|
||||||
showLoaders.value = !(
|
|
||||||
searchStore.projectType === 'mod' || searchStore.projectType === 'resourcepack'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
|
const response = await ofetch(`https://api.modrinth.com/v2/search${queryString}`)
|
||||||
searchStore.setSearchResults(response)
|
searchStore.setSearchResults(response)
|
||||||
}
|
}
|
||||||
@@ -119,131 +134,141 @@ const switchPage = async (page) => {
|
|||||||
watch(
|
watch(
|
||||||
() => route.params.projectType,
|
() => route.params.projectType,
|
||||||
async (projectType) => {
|
async (projectType) => {
|
||||||
searchStore.projectType = projectType ?? 'modpack'
|
if (!projectType) return
|
||||||
breadcrumbs.setContext({ name: 'Browse', link: route.path })
|
searchStore.projectType = projectType
|
||||||
|
breadcrumbs.setContext({ name: 'Browse', link: `/browse/${searchStore.projectType}` })
|
||||||
await handleReset()
|
await handleReset()
|
||||||
await switchPage(1)
|
await switchPage(1)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const handleInstanceSwitch = async (value) => {
|
||||||
|
searchStore.ignoreInstance = value
|
||||||
|
await switchPage(1)
|
||||||
|
}
|
||||||
</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 />
|
<Instance v-if="searchStore.instanceContext" :instance="searchStore.instanceContext" small>
|
||||||
<Button
|
<template #content>
|
||||||
role="button"
|
<Checkbox
|
||||||
:disabled="
|
:model-value="searchStore.ignoreInstance"
|
||||||
!(
|
:checked="searchStore.ignoreInstance"
|
||||||
searchStore.facets.length > 0 ||
|
label="Unfilter loader & version"
|
||||||
searchStore.orFacets.length > 0 ||
|
|
||||||
searchStore.environments.server === true ||
|
|
||||||
searchStore.environments.client === true ||
|
|
||||||
searchStore.openSource === true ||
|
|
||||||
searchStore.activeVersions.length > 0
|
|
||||||
)
|
|
||||||
"
|
|
||||||
@click="handleReset"
|
|
||||||
><ClearIcon />Clear Filters</Button
|
|
||||||
>
|
|
||||||
<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="category.name"
|
|
||||||
:facet-name="`categories:${encodeURIComponent(category.name)}`"
|
|
||||||
class="filter-checkbox"
|
class="filter-checkbox"
|
||||||
@toggle="toggleFacet"
|
@update:model-value="(value) => handleInstanceSwitch(value)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</template>
|
||||||
</div>
|
</Instance>
|
||||||
<div
|
<Card class="search-panel-card">
|
||||||
v-if="
|
<Button
|
||||||
showLoaders &&
|
role="button"
|
||||||
searchStore.projectType !== 'datapack' &&
|
:disabled="
|
||||||
searchStore.projectType !== 'resourcepack'
|
!(
|
||||||
"
|
searchStore.facets.length > 0 ||
|
||||||
class="loaders"
|
searchStore.orFacets.length > 0 ||
|
||||||
>
|
searchStore.environments.server === true ||
|
||||||
<h2>Loaders</h2>
|
searchStore.environments.client === true ||
|
||||||
<div
|
searchStore.openSource === true ||
|
||||||
v-for="loader in loaders.filter(
|
searchStore.activeVersions.length > 0
|
||||||
(l) =>
|
)
|
||||||
(searchStore.projectType !== 'mod' &&
|
|
||||||
l.supported_project_types?.includes(searchStore.projectType)) ||
|
|
||||||
(searchStore.projectType === 'mod' && ['fabric', 'forge', 'quilt'].includes(l.name))
|
|
||||||
)"
|
|
||||||
:key="loader"
|
|
||||||
>
|
|
||||||
<SearchFilter
|
|
||||||
:active-filters="searchStore.orFacets"
|
|
||||||
:icon="loader.icon"
|
|
||||||
:display-name="loader.name"
|
|
||||||
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
|
||||||
class="filter-checkbox"
|
|
||||||
@toggle="toggleOrFacet"
|
|
||||||
/>
|
|
||||||
</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">Show snapshots</Checkbox>
|
|
||||||
<multiselect
|
|
||||||
v-model="searchStore.activeVersions"
|
|
||||||
:options="
|
|
||||||
showSnapshots
|
|
||||||
? availableGameVersions.map((x) => x.version)
|
|
||||||
: availableGameVersions
|
|
||||||
.filter((it) => it.version_type === 'release')
|
|
||||||
.map((x) => x.version)
|
|
||||||
"
|
"
|
||||||
:multiple="true"
|
@click="handleReset"
|
||||||
:searchable="true"
|
><ClearIcon />Clear Filters</Button
|
||||||
:show-no-results="false"
|
|
||||||
:close-on-select="false"
|
|
||||||
:clear-search-on-select="false"
|
|
||||||
:show-labels="false"
|
|
||||||
placeholder="Choose versions..."
|
|
||||||
@update:model-value="getSearchResults"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="open-source">
|
|
||||||
<h2>Open source</h2>
|
|
||||||
<Checkbox
|
|
||||||
v-model="searchStore.openSource"
|
|
||||||
class="filter-checkbox"
|
|
||||||
@click="getSearchResults"
|
|
||||||
>
|
>
|
||||||
Open source
|
<div v-if="showLoaders" class="loaders">
|
||||||
</Checkbox>
|
<h2>Loaders</h2>
|
||||||
</div>
|
<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))
|
||||||
|
)"
|
||||||
|
:key="loader"
|
||||||
|
>
|
||||||
|
<SearchFilter
|
||||||
|
:active-filters="searchStore.orFacets"
|
||||||
|
:icon="loader.icon"
|
||||||
|
:display-name="formatCategory(loader.name)"
|
||||||
|
:facet-name="`categories:${encodeURIComponent(loader.name)}`"
|
||||||
|
class="filter-checkbox"
|
||||||
|
@toggle="toggleOrFacet"
|
||||||
|
/>
|
||||||
|
</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"
|
||||||
|
:options="
|
||||||
|
showSnapshots
|
||||||
|
? availableGameVersions.map((x) => x.version)
|
||||||
|
: availableGameVersions
|
||||||
|
.filter((it) => it.version_type === 'release')
|
||||||
|
.map((x) => x.version)
|
||||||
|
"
|
||||||
|
:multiple="true"
|
||||||
|
:searchable="true"
|
||||||
|
:show-no-results="false"
|
||||||
|
:close-on-select="false"
|
||||||
|
:clear-search-on-select="false"
|
||||||
|
:show-labels="false"
|
||||||
|
placeholder="Choose versions..."
|
||||||
|
@update:model-value="getSearchResults"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="open-source">
|
||||||
|
<h2>Open source</h2>
|
||||||
|
<Checkbox
|
||||||
|
v-model="searchStore.openSource"
|
||||||
|
class="filter-checkbox"
|
||||||
|
label="Open source"
|
||||||
|
@click="getSearchResults"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Card class="project-type-container">
|
<Card class="project-type-container">
|
||||||
@@ -267,16 +292,16 @@ watch(
|
|||||||
/>
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="search-panel-container">
|
<Card class="search-panel-container">
|
||||||
<div class="search-panel">
|
<div class="iconified-input">
|
||||||
<div class="iconified-input">
|
<SearchIcon aria-hidden="true" />
|
||||||
<SearchIcon aria-hidden="true" />
|
<input
|
||||||
<input
|
v-model="searchStore.searchInput"
|
||||||
v-model="searchStore.searchInput"
|
type="text"
|
||||||
type="text"
|
:placeholder="`Search ${searchStore.projectType}s...`"
|
||||||
:placeholder="`Search ${searchStore.projectType}s...`"
|
@input="getSearchResults"
|
||||||
@input="getSearchResults"
|
/>
|
||||||
/>
|
</div>
|
||||||
</div>
|
<div class="inline-option">
|
||||||
<span>Sort by</span>
|
<span>Sort by</span>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="searchStore.filter"
|
v-model="searchStore.filter"
|
||||||
@@ -291,6 +316,8 @@ watch(
|
|||||||
class="sort-dropdown"
|
class="sort-dropdown"
|
||||||
@change="getSearchResults"
|
@change="getSearchResults"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="inline-option">
|
||||||
<span>Show per page</span>
|
<span>Show per page</span>
|
||||||
<DropdownSelect
|
<DropdownSelect
|
||||||
v-model="searchStore.limit"
|
v-model="searchStore.limit"
|
||||||
@@ -310,19 +337,11 @@ watch(
|
|||||||
/>
|
/>
|
||||||
<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">
|
||||||
<ProjectCard
|
<SearchCard
|
||||||
v-for="result in searchStore.searchResults"
|
v-for="result in searchStore.searchResults"
|
||||||
:id="`${result?.project_id}/`"
|
|
||||||
:key="result?.project_id"
|
:key="result?.project_id"
|
||||||
class="result-project-item"
|
:project="result"
|
||||||
:type="result?.project_type"
|
:instance="searchStore.instanceContext"
|
||||||
:name="result?.title"
|
|
||||||
:description="result?.description"
|
|
||||||
:icon-url="result?.icon_url"
|
|
||||||
:downloads="result?.downloads?.toString()"
|
|
||||||
:follows="result?.follows?.toString()"
|
|
||||||
:created-at="result?.date_created"
|
|
||||||
:updated-at="result?.date_modified"
|
|
||||||
:categories="[
|
:categories="[
|
||||||
...categories.filter(
|
...categories.filter(
|
||||||
(cat) =>
|
(cat) =>
|
||||||
@@ -335,16 +354,21 @@ watch(
|
|||||||
loader.supported_project_types?.includes(searchStore.projectType)
|
loader.supported_project_types?.includes(searchStore.projectType)
|
||||||
),
|
),
|
||||||
]"
|
]"
|
||||||
:project-type-display="result?.project_type"
|
:confirm-modal="confirmModal"
|
||||||
project-type-url="project"
|
:mod-install-modal="modInstallModal"
|
||||||
:server-side="result?.server_side"
|
:incompatibility-warning-modal="incompatibilityWarningModal"
|
||||||
:client-side="result?.client_side"
|
|
||||||
:show-updated-date="false"
|
|
||||||
:color="result?.color"
|
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
<Pagination
|
||||||
|
:page="searchStore.currentPage"
|
||||||
|
:count="searchStore.pageCount"
|
||||||
|
@switch-page="switchPage"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
|
<InstanceInstallModal ref="modInstallModal" />
|
||||||
|
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
<style src="vue-multiselect/dist/vue-multiselect.css"></style>
|
||||||
@@ -360,39 +384,47 @@ watch(
|
|||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-panel-container {
|
.search-panel-card {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
background-color: var(--color-bg) !important;
|
||||||
justify-content: center;
|
margin-bottom: 0;
|
||||||
width: 100%;
|
min-height: min-content !important;
|
||||||
margin-top: 1rem;
|
}
|
||||||
padding: 0.8rem !important;
|
|
||||||
|
|
||||||
.search-panel {
|
.iconified-input {
|
||||||
|
input {
|
||||||
|
max-width: 20rem !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-panel-container {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem !important;
|
||||||
|
white-space: nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.inline-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-evenly;
|
gap: 0.5rem;
|
||||||
width: 100%;
|
|
||||||
gap: 1rem;
|
|
||||||
margin: 1rem auto;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
.sort-dropdown {
|
.sort-dropdown {
|
||||||
min-width: 12.18rem;
|
max-width: 12.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.limit-dropdown {
|
.limit-dropdown {
|
||||||
width: 5rem;
|
width: 5rem;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.iconified-input {
|
.iconified-input {
|
||||||
width: 75%;
|
flex-grow: 1;
|
||||||
|
|
||||||
input {
|
|
||||||
flex-basis: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
@@ -413,13 +445,14 @@ watch(
|
|||||||
|
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 19rem;
|
width: 20rem;
|
||||||
background: var(--color-raised-bg);
|
background: var(--color-raised-bg);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: fit-content;
|
||||||
max-height: calc(100vh - 3rem);
|
min-height: calc(100vh - 3.25rem);
|
||||||
|
max-height: calc(100vh - 3.25rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
@@ -432,7 +465,6 @@ watch(
|
|||||||
.filter-checkbox {
|
.filter-checkbox {
|
||||||
margin-bottom: 0.3rem;
|
margin-bottom: 0.3rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
text-transform: capitalize;
|
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -447,8 +479,8 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin: 0 1rem 0 20rem;
|
margin: 0 1rem 0 21rem;
|
||||||
width: calc(100% - 21rem);
|
width: calc(100% - 22rem);
|
||||||
|
|
||||||
.loading {
|
.loading {
|
||||||
margin: 2rem;
|
margin: 2rem;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { shallowRef } from 'vue'
|
import { onUnmounted, shallowRef } from 'vue'
|
||||||
import GridDisplay from '@/components/GridDisplay.vue'
|
import GridDisplay from '@/components/GridDisplay.vue'
|
||||||
import { list } from '@/helpers/profile.js'
|
import { list } from '@/helpers/profile.js'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
@@ -14,10 +14,11 @@ breadcrumbs.setRootContext({ name: 'Library', link: route.path })
|
|||||||
const profiles = await list(true)
|
const profiles = await list(true)
|
||||||
const instances = shallowRef(Object.values(profiles))
|
const instances = shallowRef(Object.values(profiles))
|
||||||
|
|
||||||
profile_listener(async () => {
|
const unlisten = await profile_listener(async () => {
|
||||||
const profiles = await list(true)
|
const profiles = await list(true)
|
||||||
instances.value = Object.values(profiles)
|
instances.value = Object.values(profiles)
|
||||||
})
|
})
|
||||||
|
onUnmounted(() => unlisten())
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -96,12 +96,6 @@ breadcrumbs.setContext({
|
|||||||
link: route.path,
|
link: route.path,
|
||||||
})
|
})
|
||||||
|
|
||||||
profile_listener(async (event) => {
|
|
||||||
if (event.profile_path === route.params.id) {
|
|
||||||
instance.value = await get(route.params.id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
const uuid = ref(null)
|
const uuid = ref(null)
|
||||||
const playing = ref(false)
|
const playing = ref(false)
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -143,11 +137,20 @@ const stopInstance = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlisten = await process_listener((e) => {
|
const unlistenProfiles = await profile_listener(async (event) => {
|
||||||
|
if (event.path === route.params.id) {
|
||||||
|
instance.value = await get(route.params.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const unlistenProcesses = await process_listener((e) => {
|
||||||
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => unlisten())
|
onUnmounted(() => {
|
||||||
|
unlistenProcesses()
|
||||||
|
unlistenProfiles()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -84,10 +84,9 @@ import {
|
|||||||
} from 'omorphia'
|
} from 'omorphia'
|
||||||
import { computed, ref, shallowRef } from 'vue'
|
import { computed, ref, shallowRef } from 'vue'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
@@ -182,7 +181,7 @@ const updateSort = (projects, sort) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const searchMod = () => {
|
const searchMod = () => {
|
||||||
router.push({ path: '/browse/mod', query: { instance: route.params.id } })
|
router.push({ path: '/browse/mod' })
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Card>
|
<Card>
|
||||||
<div class="markdown-body" v-html="renderHighlightedString(project.body)" />
|
<div class="markdown-body" v-html="renderHighlightedString(project?.body ?? '')" />
|
||||||
</Card>
|
</Card>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -187,6 +187,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<InstallConfirmModal ref="confirmModal" />
|
<InstallConfirmModal ref="confirmModal" />
|
||||||
<InstanceInstallModal ref="modInstallModal" />
|
<InstanceInstallModal ref="modInstallModal" />
|
||||||
|
<IncompatibilityWarningModal ref="incompatibilityWarning" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -233,6 +234,7 @@ import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
|||||||
import Instance from '@/components/ui/Instance.vue'
|
import Instance from '@/components/ui/Instance.vue'
|
||||||
import { useSearch } from '@/store/search'
|
import { useSearch } from '@/store/search'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
|
import IncompatibilityWarningModal from '@/components/ui/IncompatibilityWarningModal.vue'
|
||||||
|
|
||||||
const searchStore = useSearch()
|
const searchStore = useSearch()
|
||||||
|
|
||||||
@@ -242,6 +244,7 @@ const breadcrumbs = useBreadcrumbs()
|
|||||||
|
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const modInstallModal = ref(null)
|
const modInstallModal = ref(null)
|
||||||
|
const incompatibilityWarning = ref(null)
|
||||||
const instance = ref(searchStore.instanceContext)
|
const instance = ref(searchStore.instanceContext)
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
|
||||||
@@ -267,6 +270,10 @@ watch(
|
|||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
|
|
||||||
|
const markInstalled = () => {
|
||||||
|
installed.value = true
|
||||||
|
}
|
||||||
|
|
||||||
async function install(version) {
|
async function install(version) {
|
||||||
installing.value = true
|
installing.value = true
|
||||||
let queuedVersionData
|
let queuedVersionData
|
||||||
@@ -308,17 +315,43 @@ async function install(version) {
|
|||||||
: true)
|
: true)
|
||||||
)
|
)
|
||||||
if (!selectedVersion) {
|
if (!selectedVersion) {
|
||||||
|
incompatibilityWarning.value.show(
|
||||||
|
instance.value,
|
||||||
|
data.value.title,
|
||||||
|
versions.value,
|
||||||
|
markInstalled
|
||||||
|
)
|
||||||
|
installing.value = false
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
queuedVersionData = selectedVersion
|
||||||
|
await installMod(instance.value.path, selectedVersion.id)
|
||||||
|
installVersionDependencies(instance.value, queuedVersionData)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const gameVersion = instance.value.metadata.game_version
|
||||||
|
const loader = instance.value.metadata.loader
|
||||||
|
const compatible = versions.value.some(
|
||||||
|
(v) =>
|
||||||
|
v.game_versions.includes(gameVersion) &&
|
||||||
|
(data.value.project_type === 'mod'
|
||||||
|
? v.loaders.includes(loader) || v.loaders.includes('minecraft')
|
||||||
|
: true)
|
||||||
|
)
|
||||||
|
if (compatible) {
|
||||||
|
await installMod(instance.value.path, queuedVersionData.id)
|
||||||
|
await installVersionDependencies(instance.value, queuedVersionData)
|
||||||
|
} else {
|
||||||
|
incompatibilityWarning.value.show(
|
||||||
|
instance.value,
|
||||||
|
data.value.title,
|
||||||
|
[queuedVersionData],
|
||||||
|
markInstalled
|
||||||
|
)
|
||||||
installing.value = false
|
installing.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
queuedVersionData = selectedVersion
|
|
||||||
await installMod(instance.value.path, selectedVersion.id)
|
|
||||||
} else {
|
|
||||||
await installMod(instance.value.path, queuedVersionData.id)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await installVersionDependencies(instance.value, queuedVersionData)
|
|
||||||
|
|
||||||
installed.value = true
|
installed.value = true
|
||||||
} else {
|
} else {
|
||||||
if (version) {
|
if (version) {
|
||||||
@@ -345,9 +378,9 @@ async function install(version) {
|
|||||||
.project-sidebar {
|
.project-sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
min-height: 100vh;
|
min-height: calc(100vh - 3.25rem);
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
max-height: 100vh;
|
max-height: calc(100vh - 3.25rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background: var(--color-raised-bg);
|
background: var(--color-raised-bg);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
|||||||
@@ -35,14 +35,9 @@
|
|||||||
placeholder="Filter versions..."
|
placeholder="Filter versions..."
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Checkbox
|
|
||||||
v-model="filterCompatible"
|
|
||||||
label="Only show compatible versions"
|
|
||||||
class="filter-checkbox"
|
|
||||||
/>
|
|
||||||
<Button
|
<Button
|
||||||
class="no-wrap clear-filters"
|
class="no-wrap clear-filters"
|
||||||
:disabled="!filterLoader && !filterVersions && !filterCompatible"
|
:disabled="!filterLoader && !filterVersions"
|
||||||
:action="clearFilters"
|
:action="clearFilters"
|
||||||
>
|
>
|
||||||
<ClearIcon />
|
<ClearIcon />
|
||||||
@@ -129,28 +124,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import { Card, Button, CheckIcon, ClearIcon, Badge, DownloadIcon, formatNumber } from 'omorphia'
|
||||||
Card,
|
|
||||||
Button,
|
|
||||||
CheckIcon,
|
|
||||||
ClearIcon,
|
|
||||||
Badge,
|
|
||||||
DownloadIcon,
|
|
||||||
Checkbox,
|
|
||||||
formatNumber,
|
|
||||||
} from 'omorphia'
|
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
import { releaseColor } from '@/helpers/utils'
|
import { releaseColor } from '@/helpers/utils'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
let filterVersions = ref(null)
|
let filterVersions = ref(null)
|
||||||
let filterLoader = ref(null)
|
let filterLoader = ref(null)
|
||||||
let filterCompatible = ref(false)
|
|
||||||
|
|
||||||
const clearFilters = () => {
|
const clearFilters = () => {
|
||||||
filterVersions.value = null
|
filterVersions.value = null
|
||||||
filterLoader.value = null
|
filterLoader.value = null
|
||||||
filterCompatible.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
|
|||||||
@@ -20,12 +20,13 @@ export const useSearch = defineStore('searchStore', {
|
|||||||
openSource: false,
|
openSource: false,
|
||||||
limit: 20,
|
limit: 20,
|
||||||
instanceContext: null,
|
instanceContext: null,
|
||||||
|
ignoreInstance: false,
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
getQueryString() {
|
getQueryString() {
|
||||||
let andFacets = [`project_type:${this.projectType === 'datapack' ? 'mod' : this.projectType}`]
|
let andFacets = [`project_type:${this.projectType === 'datapack' ? 'mod' : this.projectType}`]
|
||||||
|
|
||||||
if (this.instanceContext) {
|
if (this.instanceContext && !this.ignoreInstance) {
|
||||||
this.activeVersions = [this.instanceContext.metadata.game_version]
|
this.activeVersions = [this.instanceContext.metadata.game_version]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ export const useSearch = defineStore('searchStore', {
|
|||||||
;[...andFacets, `categories:${encodeURIComponent('datapack')}`].forEach(
|
;[...andFacets, `categories:${encodeURIComponent('datapack')}`].forEach(
|
||||||
(f) => (formattedAndFacets += `["${f}"],`)
|
(f) => (formattedAndFacets += `["${f}"],`)
|
||||||
)
|
)
|
||||||
} else if (this.instanceContext && this.projectType === 'mod') {
|
} else if (this.instanceContext && !this.ignoreInstance && this.projectType === 'mod') {
|
||||||
;[
|
;[
|
||||||
...andFacets,
|
...andFacets,
|
||||||
`categories:${encodeURIComponent(this.instanceContext.metadata.loader)}`,
|
`categories:${encodeURIComponent(this.instanceContext.metadata.loader)}`,
|
||||||
|
|||||||
Reference in New Issue
Block a user