You've already forked AstralRinth
forked from didirus/AstralRinth
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:
93
theseus_gui/.eslintignore
Normal file
93
theseus_gui/.eslintignore
Normal file
@@ -0,0 +1,93 @@
|
||||
node_modules
|
||||
*.log*
|
||||
.nuxt
|
||||
.nitro
|
||||
.cache
|
||||
.output
|
||||
.env
|
||||
dist
|
||||
*.md
|
||||
package.json
|
||||
dist/
|
||||
|
||||
generated/
|
||||
!.gitkeep
|
||||
|
||||
# Created by .ignore support plugin (hsz.mobi)
|
||||
### Node template
|
||||
# Logs
|
||||
/logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
|
||||
# IDE / Editor
|
||||
.idea
|
||||
|
||||
# Service worker
|
||||
sw.*
|
||||
|
||||
# macOS
|
||||
.DS_Store
|
||||
|
||||
# Vim swap files
|
||||
*.swp
|
||||
|
||||
# pnpm files
|
||||
pnpm-lock.yaml
|
||||
/.npmrc
|
||||
|
||||
src-tauri/
|
||||
*.rs
|
||||
@@ -88,3 +88,6 @@ sw.*
|
||||
# pnpm files
|
||||
pnpm-lock.yaml
|
||||
/.npmrc
|
||||
|
||||
src-tauri/
|
||||
*.rs
|
||||
|
||||
@@ -27,6 +27,7 @@ thiserror = "1.0"
|
||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
futures = "0.3"
|
||||
daedalus = {version = "0.1.15", features = ["bincode"] }
|
||||
chrono = "0.4.26"
|
||||
|
||||
url = "2.2"
|
||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||
|
||||
@@ -195,6 +195,7 @@ pub struct EditProfileMetadata {
|
||||
pub game_version: Option<String>,
|
||||
pub loader: Option<ModLoader>,
|
||||
pub loader_version: Option<LoaderVersion>,
|
||||
pub groups: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
// Edits a profile
|
||||
@@ -207,15 +208,19 @@ pub async fn profile_edit(
|
||||
profile::edit(path, |prof| {
|
||||
if let Some(metadata) = edit_profile.metadata.clone() {
|
||||
if let Some(name) = metadata.name {
|
||||
prof.metadata.name = name
|
||||
prof.metadata.name = name;
|
||||
}
|
||||
if let Some(game_version) = metadata.game_version {
|
||||
prof.metadata.game_version = game_version
|
||||
prof.metadata.game_version = game_version;
|
||||
}
|
||||
if let Some(loader) = metadata.loader {
|
||||
prof.metadata.loader = loader
|
||||
prof.metadata.loader = loader;
|
||||
}
|
||||
prof.metadata.loader_version = metadata.loader_version;
|
||||
|
||||
if let Some(groups) = metadata.groups {
|
||||
prof.metadata.groups = groups;
|
||||
}
|
||||
prof.metadata.loader_version = metadata.loader_version
|
||||
}
|
||||
|
||||
prof.java = edit_profile.java.clone();
|
||||
@@ -223,6 +228,8 @@ pub async fn profile_edit(
|
||||
prof.resolution = edit_profile.resolution;
|
||||
prof.hooks = edit_profile.hooks.clone();
|
||||
|
||||
prof.metadata.date_modified = chrono::Utc::now();
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -152,9 +152,7 @@ onUnmounted(() => {
|
||||
|
||||
.dark-mode {
|
||||
.row {
|
||||
&:nth-child(odd) {
|
||||
background-color: rgb(30, 31, 34);
|
||||
}
|
||||
background-color: rgb(30, 31, 34);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user