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:
parent
72fc215641
commit
e0e9c3f166
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -53,6 +53,12 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@ -603,13 +609,13 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.24"
|
||||
version = "0.4.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e3c5919066adf22df73762e50cffcde3a758f2a848b113b586d1f86728b673b"
|
||||
checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
@ -4274,6 +4280,7 @@ dependencies = [
|
||||
name = "theseus_gui"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"daedalus",
|
||||
"futures",
|
||||
"os_info",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
//! Authentication flow interface
|
||||
use crate::{launcher::auth as inner, State};
|
||||
use futures::prelude::*;
|
||||
use chrono::Utc;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use crate::state::AuthTask;
|
||||
@ -71,22 +71,20 @@ pub async fn refresh(user: uuid::Uuid) -> crate::Result<Credentials> {
|
||||
let state = State::get().await?;
|
||||
let mut users = state.users.write().await;
|
||||
|
||||
let fetch_semaphore = &state.fetch_semaphore;
|
||||
futures::future::ready(users.get(user).ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(format!(
|
||||
"You are not logged in with a Minecraft account!"
|
||||
))
|
||||
let mut credentials = users.get(user).ok_or_else(|| {
|
||||
crate::ErrorKind::OtherError(
|
||||
"You are not logged in with a Minecraft account!".to_string(),
|
||||
)
|
||||
.as_error()
|
||||
}))
|
||||
.and_then(|mut credentials| async move {
|
||||
if chrono::offset::Utc::now() > credentials.expires {
|
||||
inner::refresh_credentials(&mut credentials, fetch_semaphore)
|
||||
.await?;
|
||||
}
|
||||
users.insert(&credentials).await?;
|
||||
Ok(credentials)
|
||||
})
|
||||
.await
|
||||
})?;
|
||||
|
||||
let fetch_semaphore = &state.fetch_semaphore;
|
||||
if Utc::now() > credentials.expires {
|
||||
inner::refresh_credentials(&mut credentials, fetch_semaphore).await?;
|
||||
}
|
||||
users.insert(&credentials).await?;
|
||||
|
||||
Ok(credentials)
|
||||
}
|
||||
|
||||
/// Remove a user account from the database
|
||||
|
||||
@ -9,6 +9,7 @@ use crate::{
|
||||
state::{self as st, MinecraftChild},
|
||||
State,
|
||||
};
|
||||
use chrono::Utc;
|
||||
use daedalus as d;
|
||||
use daedalus::minecraft::VersionInfo;
|
||||
use dunce::canonicalize;
|
||||
@ -432,6 +433,13 @@ pub async fn launch_minecraft(
|
||||
let stdout_log_path = logs_dir.join("stdout.log");
|
||||
let stderr_log_path = logs_dir.join("stderr.log");
|
||||
|
||||
crate::api::profile::edit(&profile.path, |prof| {
|
||||
prof.metadata.last_played = Some(Utc::now());
|
||||
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
|
||||
// Create Minecraft child by inserting it into the state
|
||||
// This also spawns the process and prepares the subsequent processes
|
||||
let mut state_children = state.children.write().await;
|
||||
|
||||
@ -10,6 +10,7 @@ use crate::util::fetch::{
|
||||
fetch, fetch_json, write, write_cached_icon, IoSemaphore,
|
||||
};
|
||||
use crate::State;
|
||||
use chrono::{DateTime, Utc};
|
||||
use daedalus::get_hash;
|
||||
use daedalus::modded::LoaderVersion;
|
||||
use dunce::canonicalize;
|
||||
@ -30,9 +31,6 @@ const PROFILE_JSON_PATH: &str = "profile.json";
|
||||
|
||||
pub(crate) struct Profiles(pub HashMap<PathBuf, Profile>);
|
||||
|
||||
// TODO: possibly add defaults to some of these values
|
||||
pub const CURRENT_FORMAT_VERSION: u32 = 1;
|
||||
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Copy, Debug, Default, Eq, PartialEq,
|
||||
)]
|
||||
@ -75,13 +73,24 @@ pub struct ProfileMetadata {
|
||||
pub icon: Option<PathBuf>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub icon_url: Option<String>,
|
||||
#[serde(default)]
|
||||
pub groups: Vec<String>,
|
||||
|
||||
pub game_version: String,
|
||||
#[serde(default)]
|
||||
pub loader: ModLoader,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub loader_version: Option<LoaderVersion>,
|
||||
pub format_version: u32,
|
||||
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub linked_data: Option<LinkedData>,
|
||||
|
||||
#[serde(default)]
|
||||
pub date_created: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub date_modified: DateTime<Utc>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub last_played: Option<DateTime<Utc>>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
@ -157,11 +166,14 @@ impl Profile {
|
||||
name,
|
||||
icon: None,
|
||||
icon_url: None,
|
||||
groups: vec![],
|
||||
game_version: version,
|
||||
loader: ModLoader::Vanilla,
|
||||
loader_version: None,
|
||||
format_version: CURRENT_FORMAT_VERSION,
|
||||
linked_data: None,
|
||||
date_created: Utc::now(),
|
||||
date_modified: Utc::now(),
|
||||
last_played: None,
|
||||
},
|
||||
projects: HashMap::new(),
|
||||
java: None,
|
||||
@ -182,6 +194,7 @@ impl Profile {
|
||||
let file =
|
||||
write_cached_icon(file_name, cache_dir, icon, semaphore).await?;
|
||||
self.metadata.icon = Some(file);
|
||||
self.metadata.date_modified = Utc::now();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -400,6 +413,7 @@ impl Profile {
|
||||
file_name: file_name.to_string(),
|
||||
},
|
||||
);
|
||||
profile.metadata.date_modified = Utc::now();
|
||||
}
|
||||
}
|
||||
|
||||
@ -446,6 +460,7 @@ impl Profile {
|
||||
let mut profiles = state.profiles.write().await;
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.insert(new_path.clone(), project);
|
||||
profile.metadata.date_modified = Utc::now();
|
||||
}
|
||||
|
||||
Ok(new_path)
|
||||
@ -471,6 +486,7 @@ impl Profile {
|
||||
|
||||
if let Some(profile) = profiles.0.get_mut(&self.path) {
|
||||
profile.projects.remove(path);
|
||||
profile.metadata.date_modified = Utc::now();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -26,6 +26,8 @@ pub struct Settings {
|
||||
pub max_concurrent_writes: usize,
|
||||
pub version: u32,
|
||||
pub collapsed_navigation: bool,
|
||||
#[serde(default)]
|
||||
pub developer_mode: bool,
|
||||
}
|
||||
|
||||
impl Default for Settings {
|
||||
@ -43,6 +45,7 @@ impl Default for Settings {
|
||||
max_concurrent_writes: 10,
|
||||
version: CURRENT_FORMAT_VERSION,
|
||||
collapsed_navigation: false,
|
||||
developer_mode: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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">
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user