Library improvements (#126)

* Base impl

* Add grouping

* Fix some styling things

* Run linter

* add missing features

* add dev mode

---------

Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
Co-authored-by: Jai A <jaiagr+gpg@pm.me>
This commit is contained in:
Adrian O.V
2023-06-02 18:36:10 -04:00
committed by GitHub
parent 72fc215641
commit e0e9c3f166
15 changed files with 398 additions and 88 deletions

View File

@@ -1,5 +1,8 @@
<script setup>
import Instance from '@/components/ui/Instance.vue'
import { computed, ref } from 'vue'
import { SearchIcon, DropdownSelect, Card, formatCategoryHeader } from 'omorphia'
import dayjs from 'dayjs'
const props = defineProps({
instances: {
@@ -13,16 +16,143 @@ const props = defineProps({
default: '',
},
})
const search = ref('')
const group = ref('Category')
const filters = ref('All profiles')
const sortBy = ref('Name')
const filteredResults = computed(() => {
let instances = props.instances.filter((instance) => {
return instance.metadata.name.toLowerCase().includes(search.value.toLowerCase())
})
if (sortBy.value === 'Name') {
instances.sort((a, b) => {
return a.metadata.name.localeCompare(b.metadata.name)
})
}
if (sortBy.value === 'Game version') {
instances.sort((a, b) => {
return a.metadata.name.localeCompare(b.metadata.game_version)
})
}
if (sortBy.value === 'Last played') {
instances.sort((a, b) => {
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
})
}
if (sortBy.value === 'Date created') {
instances.sort((a, b) => {
return dayjs(b.metadata.date_created).diff(dayjs(a.metadata.date_created))
})
}
if (sortBy.value === 'Date modified') {
instances.sort((a, b) => {
return dayjs(b.metadata.date_modified).diff(dayjs(a.metadata.date_modified))
})
}
if (filters.value === 'Custom instances') {
instances = instances.filter((instance) => {
return !instance.metadata?.linked_data
})
} else if (filters.value === 'Downloaded modpacks') {
instances = instances.filter((instance) => {
return instance.metadata?.linked_data
})
}
const instanceMap = new Map()
if (group.value === 'Loader') {
instances.forEach((instance) => {
const loader = formatCategoryHeader(instance.metadata.loader)
if (!instanceMap.has(loader)) {
instanceMap.set(loader, [])
}
instanceMap.get(loader).push(instance)
})
} else if (group.value === 'Game version') {
instances.forEach((instance) => {
if (!instanceMap.has(instance.metadata.game_version)) {
instanceMap.set(instance.metadata.game_version, [])
}
instanceMap.get(instance.metadata.game_version).push(instance)
})
} else if (group.value === 'Category') {
instances.forEach((instance) => {
if (instance.metadata.groups.length === 0) {
instance.metadata.groups.push('None')
}
for (const category of instance.metadata.groups) {
if (!instanceMap.has(category)) {
instanceMap.set(category, [])
}
instanceMap.get(category).push(instance)
}
})
} else {
return instanceMap.set('None', instances)
}
return instanceMap
})
</script>
<template>
<div class="row">
<div class="header">
<p>{{ props.label }}</p>
<hr />
<Card class="header">
<div class="iconified-input">
<SearchIcon />
<input v-model="search" type="text" placeholder="Search" class="search-input" />
</div>
<div class="labeled_button">
<span>Sort by</span>
<DropdownSelect
v-model="sortBy"
class="sort-dropdown"
:options="['Name', 'Last played', 'Date created', 'Date modified', 'Game version']"
placeholder="Select..."
/>
</div>
<div class="labeled_button">
<span>Filter by</span>
<DropdownSelect
v-model="filters"
class="filter-dropdown"
:options="['All profiles', 'Custom instances', 'Downloaded modpacks']"
placeholder="Select..."
/>
</div>
<div class="labeled_button">
<span>Group by</span>
<DropdownSelect
v-model="group"
class="group-dropdown"
:options="['Category', 'Loader', 'Game version', 'None']"
placeholder="Select..."
/>
</div>
</Card>
<div
v-for="instanceSection in Array.from(filteredResults, ([key, value]) => ({ key, value }))"
:key="instanceSection.key"
class="row"
>
<div v-if="instanceSection.key !== 'None'" class="divider">
<p>{{ instanceSection.key }}</p>
<hr aria-hidden="true" />
</div>
<section class="instances">
<Instance
v-for="instance in props.instances"
v-for="instance in instanceSection.value"
:key="instance.id"
display="card"
:instance="instance"
@@ -34,17 +164,17 @@ const props = defineProps({
.row {
display: flex;
flex-direction: column;
align-items: center;
align-items: flex-start;
width: 100%;
padding: 1rem;
.header {
.divider {
display: flex;
justify-content: space-between;
align-items: inherit;
align-items: center;
width: 100%;
margin-bottom: 1rem;
gap: 1rem;
margin-bottom: 1rem;
p {
margin: 0;
@@ -59,50 +189,52 @@ const props = defineProps({
width: 100%;
border: none;
}
.pagination {
display: inherit;
align-items: inherit;
svg {
background: var(--color-raised-bg);
border-radius: var(--radius-lg);
width: 1.3rem;
height: 1.2rem;
cursor: pointer;
margin-right: 0.5rem;
transition: all ease-in-out 0.1s;
&:hover {
filter: brightness(150%);
}
}
}
}
section {
display: flex;
align-items: inherit;
transition: all ease-in-out 0.4s;
gap: 1rem;
}
.instances {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
width: 100%;
gap: 1rem;
margin-right: auto;
scroll-behavior: smooth;
overflow-y: auto;
}
}
.dark-mode {
.row {
&:nth-child(odd) {
background-color: rgb(30, 31, 34);
}
.header {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
gap: 1rem;
align-items: inherit;
margin: 1rem 1rem 0 !important;
padding: 1rem;
width: calc(100% - 2rem);
.iconified-input {
flex-grow: 1;
}
.sort-dropdown {
width: 10rem;
}
.filter-dropdown {
width: 15rem;
}
.group-dropdown {
width: 10rem;
}
.labeled_button {
display: flex;
flex-direction: row;
align-items: center;
gap: 0.5rem;
white-space: nowrap;
}
}
.instances {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
width: 100%;
gap: 1rem;
margin-right: auto;
scroll-behavior: smooth;
overflow-y: auto;
}
</style>

View File

@@ -152,9 +152,7 @@ onUnmounted(() => {
.dark-mode {
.row {
&:nth-child(odd) {
background-color: rgb(30, 31, 34);
}
background-color: rgb(30, 31, 34);
}
}

View File

@@ -362,6 +362,7 @@ onUnmounted(() => unlisten())
padding: 0.75rem !important; /* overrides card class */
transition: 0.1s ease-in-out all !important; /* overrides Omorphia defaults */
background: hsl(220, 11%, 17%) !important;
margin-bottom: 0;
&:hover {
filter: brightness(1) !important;

View File

@@ -7,6 +7,7 @@ import { profile_listener } from '@/helpers/events'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useFetch } from '@/helpers/fetch.js'
import { handleError } from '@/store/notifications.js'
import dayjs from 'dayjs'
const featuredModpacks = ref({})
const featuredMods = ref({})
@@ -21,7 +22,9 @@ const recentInstances = shallowRef([])
const getInstances = async () => {
const profiles = await list(true).catch(handleError)
recentInstances.value = Object.values(profiles)
recentInstances.value = Object.values(profiles).sort((a, b) => {
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
})
let filters = []
for (const instance of recentInstances.value) {

View File

@@ -19,7 +19,7 @@ fetchSettings.javaArgs = fetchSettings.custom_java_args.join(' ')
fetchSettings.envArgs = fetchSettings.custom_env_args.map((x) => x.join('=')).join(' ')
const settings = ref(fetchSettings)
const maxMemory = ref((await get_max_memory().catch(handleError)) / 1024)
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024))
watch(settings.value, async (oldSettings, newSettings) => {
const setSettings = JSON.parse(JSON.stringify(newSettings))

View File

@@ -82,6 +82,35 @@
Edit versions
</button>
</div>
<div class="adjacent-input">
<label>
<span class="label__title">Categories</span>
<span class="label__description">
Set the categories of this instance, for display in the library page. This is purely
cosmetic.
</span>
</label>
<multiselect
v-model="groups"
:options="availableGroups"
:multiple="true"
:searchable="true"
:show-no-results="false"
:close-on-select="false"
:clear-search-on-select="false"
:show-labels="false"
:taggable="true"
tag-placeholder="Add new category"
placeholder="Select categories..."
@tag="
(newTag) => {
groups.push(newTag)
availableGroups.push(newTag)
}
"
/>
</div>
</section>
<Card>
<div class="label">
@@ -283,9 +312,10 @@ import {
SaveIcon,
HammerIcon,
} from 'omorphia'
import { Multiselect } from 'vue-multiselect'
import { useRouter } from 'vue-router'
import { edit, edit_icon, get_optimal_jre_key, install, remove } from '@/helpers/profile.js'
import { computed, onMounted, readonly, ref, shallowRef, watch } from 'vue'
import { edit, edit_icon, get_optimal_jre_key, install, list, remove } from '@/helpers/profile.js'
import { computed, readonly, ref, shallowRef, watch } from 'vue'
import { get_max_memory } from '@/helpers/jre.js'
import { get } from '@/helpers/settings.js'
import JavaSelector from '@/components/ui/JavaSelector.vue'
@@ -306,6 +336,16 @@ const props = defineProps({
const title = ref(props.instance.metadata.name)
const icon = ref(props.instance.metadata.icon)
const groups = ref(props.instance.metadata.groups)
const instancesList = Object.values(await list(true))
const availableGroups = ref([
...new Set(
instancesList.reduce((acc, obj) => {
return acc.concat(obj.metadata.groups)
}, [])
),
])
async function resetIcon() {
icon.value = null
@@ -347,7 +387,7 @@ const envVars = ref(
const overrideMemorySettings = ref(!!props.instance.memory)
const memory = ref(props.instance.memory ?? globalSettings.memory)
const maxMemory = (await get_max_memory().catch(handleError)) / 1024
const maxMemory = Math.floor((await get_max_memory().catch(handleError)) / 1024)
const overrideWindowSettings = ref(!!props.instance.resolution)
const resolution = ref(props.instance.resolution ?? globalSettings.game_resolution)
@@ -358,6 +398,7 @@ const hooks = ref(props.instance.hooks ?? globalSettings.hooks)
watch(
[
title,
groups.value,
overrideJavaInstall,
javaInstall,
overrideJavaArgs,
@@ -375,6 +416,7 @@ watch(
const editProfile = {
metadata: {
name: title.value,
groups: groups.value,
},
java: {},
}
@@ -520,8 +562,6 @@ async function saveGvLoaderEdits() {
editing.value = false
changeVersionsModal.value.hide()
}
onMounted(() => console.log(loader.value))
</script>
<style scoped lang="scss">