From ee0c91aa80d85204ae00b9b7728539e1ac9602d6 Mon Sep 17 00:00:00 2001 From: Geometrically <18202329+Geometrically@users.noreply.github.com> Date: Thu, 11 May 2023 18:11:15 -0700 Subject: [PATCH] Performance improvements (#114) * Performance improvements * run fmt * optimize creation modal * remove print, fix mod loader editing * Fix library update * update extract loading bar * Update theseus_gui/src-tauri/src/api/metadata.rs Co-authored-by: triphora * fix cli --------- Co-authored-by: triphora --- theseus/src/api/metadata.rs | 8 + theseus/src/api/mod.rs | 3 +- theseus/src/api/pack.rs | 146 +++++++++--------- theseus/src/api/profile.rs | 37 ++++- theseus/src/event/mod.rs | 2 + theseus/src/launcher/mod.rs | 24 +-- theseus/src/state/children.rs | 8 +- theseus/src/state/profiles.rs | 2 +- theseus_cli/src/subcommands/profile.rs | 2 +- theseus_gui/src-tauri/Cargo.toml | 2 +- theseus_gui/src-tauri/src/api/jre.rs | 2 +- theseus_gui/src-tauri/src/api/metadata.rs | 6 + theseus_gui/src-tauri/src/api/profile.rs | 30 +++- theseus_gui/src-tauri/src/main.rs | 2 + theseus_gui/src/components/GridDisplay.vue | 2 +- .../components/modrinth-loading-indicator.js | 2 +- theseus_gui/src/components/ui/Instance.vue | 60 +++---- .../components/ui/InstanceCreationModal.vue | 32 ++-- .../components/ui/InstanceInstallModal.vue | 34 ++-- theseus_gui/src/helpers/metadata.js | 6 + theseus_gui/src/helpers/profile.js | 12 +- theseus_gui/src/helpers/utils.js | 10 +- theseus_gui/src/pages/Browse.vue | 10 +- theseus_gui/src/pages/Index.vue | 6 +- theseus_gui/src/pages/Library.vue | 12 +- theseus_gui/src/pages/instance/Index.vue | 3 +- theseus_gui/src/pages/project/Index.vue | 16 +- theseus_playground/src/main.rs | 81 +++++----- 28 files changed, 326 insertions(+), 234 deletions(-) diff --git a/theseus/src/api/metadata.rs b/theseus/src/api/metadata.rs index 4b90f738f..7227ae74c 100644 --- a/theseus/src/api/metadata.rs +++ b/theseus/src/api/metadata.rs @@ -25,3 +25,11 @@ pub async fn get_forge_versions() -> crate::Result { Ok(tags) } + +#[tracing::instrument] +pub async fn get_quilt_versions() -> crate::Result { + let state = State::get().await?; + let tags = state.metadata.read().await.quilt.clone(); + + Ok(tags) +} diff --git a/theseus/src/api/mod.rs b/theseus/src/api/mod.rs index b9d4c4969..009ad9f0b 100644 --- a/theseus/src/api/mod.rs +++ b/theseus/src/api/mod.rs @@ -13,7 +13,8 @@ pub mod tags; pub mod data { pub use crate::state::{ DirectoryInfo, Hooks, JavaSettings, MemorySettings, ModLoader, - ProfileMetadata, Settings, Theme, WindowSize, + ModrinthProject, ModrinthTeamMember, ModrinthUser, ModrinthVersion, + ProfileMetadata, ProjectMetadata, Settings, Theme, WindowSize, }; } diff --git a/theseus/src/api/pack.rs b/theseus/src/api/pack.rs index 796e3dc78..afb5b969d 100644 --- a/theseus/src/api/pack.rs +++ b/theseus/src/api/pack.rs @@ -6,7 +6,8 @@ use crate::event::emit::{ }; use crate::event::{LoadingBarId, LoadingBarType}; use crate::state::{ - LinkedData, ModrinthProject, ModrinthVersion, ProfileInstallStage, SideType, + LinkedData, ModrinthProject, ModrinthVersion, Profile, ProfileInstallStage, + SideType, }; use crate::util::fetch::{ fetch, fetch_advanced, fetch_json, fetch_mirrors, write, write_cached_icon, @@ -325,6 +326,7 @@ async fn install_pack( prof.metadata.icon = icon.clone(); prof.metadata.game_version = game_version.clone(); prof.metadata.loader_version = loader_version.clone(); + prof.metadata.loader = mod_loader.unwrap_or(ModLoader::Vanilla); async { Ok(()) } }) @@ -409,83 +411,79 @@ async fn install_pack( ) .await?; - let extract_overrides = |overrides: String| async { - let reader = Cursor::new(&file); - - let mut overrides_zip = - ZipFileReader::new(reader).await.map_err(|_| { - crate::Error::from(crate::ErrorKind::InputError( - "Failed extract overrides Zip".to_string(), - )) - })?; - - let profile = profile.clone(); - async move { - for index in 0..overrides_zip.file().entries().len() { - let file = overrides_zip - .file() - .entries() - .get(index) - .unwrap() - .entry() - .clone(); - - let file_path = PathBuf::from(file.filename()); - if file.filename().starts_with(&overrides) - && !file.filename().ends_with('/') - { - // Reads the file into the 'content' variable - let mut content = Vec::new(); - let mut reader = - overrides_zip.entry(index).await?; - reader - .read_to_end_checked(&mut content, &file) - .await?; - - let mut new_path = PathBuf::new(); - let components = file_path.components().skip(1); - - for component in components { - new_path.push(component); - } - - if new_path.file_name().is_some() { - write( - &profile.join(new_path), - &content, - &state.io_semaphore, - ) - .await?; - } - } - } - - Ok::<(), crate::Error>(()) - } - .await - }; - emit_loading(&loading_bar, 0.0, Some("Extracting overrides")) .await?; - extract_overrides("overrides".to_string()).await?; - extract_overrides("client_overrides".to_string()).await?; - emit_loading( - &loading_bar, - 29.9, - Some("Done extracting overrides"), - ) - .await?; - if let Some(profile) = - crate::api::profile::get(&profile).await? + let mut total_len = 0; + + for index in 0..zip_reader.file().entries().len() { + let file = + zip_reader.file().entries().get(index).unwrap().entry(); + + if (file.filename().starts_with("overrides") + || file.filename().starts_with("client_overrides")) + && !file.filename().ends_with('/') + { + total_len += 1; + } + } + + for index in 0..zip_reader.file().entries().len() { + let file = zip_reader + .file() + .entries() + .get(index) + .unwrap() + .entry() + .clone(); + + let file_path = PathBuf::from(file.filename()); + if (file.filename().starts_with("overrides") + || file.filename().starts_with("client_overrides")) + && !file.filename().ends_with('/') + { + // Reads the file into the 'content' variable + let mut content = Vec::new(); + let mut reader = zip_reader.entry(index).await?; + reader.read_to_end_checked(&mut content, &file).await?; + + let mut new_path = PathBuf::new(); + let components = file_path.components().skip(1); + + for component in components { + new_path.push(component); + } + + if new_path.file_name().is_some() { + write( + &profile.join(new_path), + &content, + &state.io_semaphore, + ) + .await?; + } + + emit_loading( + &loading_bar, + 30.0 / total_len as f64, + Some(&format!( + "Extracting override {}/{}", + index, total_len + )), + ) + .await?; + } + } + + if let Some(profile_val) = + crate::api::profile::get(&profile, None).await? { - tokio::try_join!( - super::profile::sync(&profile.path), - crate::launcher::install_minecraft( - &profile, - Some(loading_bar) - ), - )?; + Profile::sync_projects_task(profile.clone()); + crate::launcher::install_minecraft( + &profile_val, + Some(loading_bar), + ) + .await?; } Ok::(profile.clone()) diff --git a/theseus/src/api/profile.rs b/theseus/src/api/profile.rs index 7028b14bf..23c556390 100644 --- a/theseus/src/api/profile.rs +++ b/theseus/src/api/profile.rs @@ -12,6 +12,7 @@ pub use crate::{ state::{JavaSettings, Profile}, State, }; +use std::collections::HashMap; use std::{ future::Future, path::{Path, PathBuf}, @@ -40,11 +41,21 @@ pub async fn remove(path: &Path) -> crate::Result<()> { /// Get a profile by path, #[tracing::instrument] -pub async fn get(path: &Path) -> crate::Result> { +pub async fn get( + path: &Path, + clear_projects: Option, +) -> crate::Result> { let state = State::get().await?; let profiles = state.profiles.read().await; + let mut profile = profiles.0.get(path).cloned(); - Ok(profiles.0.get(path).cloned()) + if clear_projects.unwrap_or(false) { + if let Some(profile) = &mut profile { + profile.projects = HashMap::new(); + } + } + + Ok(profile) } /// Edit a profile using a given asynchronous closure @@ -79,11 +90,23 @@ where /// Get a copy of the profile set #[tracing::instrument] -pub async fn list() -> crate::Result> -{ +pub async fn list( + clear_projects: Option, +) -> crate::Result> { let state = State::get().await?; let profiles = state.profiles.read().await; - Ok(profiles.0.clone()) + Ok(profiles + .0 + .clone() + .into_iter() + .map(|mut x| { + if clear_projects.unwrap_or(false) { + x.1.projects = HashMap::new(); + } + + x + }) + .collect()) } /// Query + sync profile's projects with the UI from the FS @@ -117,7 +140,7 @@ pub async fn sync(path: &Path) -> crate::Result<()> { /// Installs/Repairs a profile #[tracing::instrument] pub async fn install(path: &Path) -> crate::Result<()> { - let profile = get(path).await?; + let profile = get(path, None).await?; if let Some(profile) = profile { crate::launcher::install_minecraft(&profile, None).await?; @@ -398,7 +421,7 @@ pub async fn run_credentials( let state = State::get().await?; let settings = state.settings.read().await; let metadata = state.metadata.read().await; - let profile = get(path).await?.ok_or_else(|| { + let profile = get(path, None).await?.ok_or_else(|| { crate::ErrorKind::OtherError(format!( "Tried to run a nonexistent or unloaded profile at path {}!", path.display() diff --git a/theseus/src/event/mod.rs b/theseus/src/event/mod.rs index 1826c1f8f..f77f2cdb3 100644 --- a/theseus/src/event/mod.rs +++ b/theseus/src/event/mod.rs @@ -111,6 +111,8 @@ impl Drop for LoadingBar { let loader_uuid = self.loading_bar_uuid; let event = self.bar_type.clone(); let fraction = self.current / self.total; + + #[cfg(feature = "cli")] let cli_progress_bar = self.cli_progress_bar.clone(); tokio::spawn(async move { diff --git a/theseus/src/launcher/mod.rs b/theseus/src/launcher/mod.rs index be89f0f52..d69d513f5 100644 --- a/theseus/src/launcher/mod.rs +++ b/theseus/src/launcher/mod.rs @@ -60,6 +60,18 @@ pub async fn install_minecraft( existing_loading_bar: Option, ) -> crate::Result<()> { Box::pin(async move { + let loading_bar = init_or_edit_loading( + existing_loading_bar, + LoadingBarType::MinecraftDownload { + // If we are downloading minecraft for a profile, provide its name and uuid + profile_name: profile.metadata.name.clone(), + profile_path: profile.path.clone(), + }, + 100.0, + "Downloading Minecraft", + ) + .await?; + crate::api::profile::edit(&profile.path, |prof| { prof.install_stage = ProfileInstallStage::Installing; @@ -90,18 +102,6 @@ pub async fn install_minecraft( format!("{}-{}", version.id.clone(), it.id.clone()) }); - let loading_bar = init_or_edit_loading( - existing_loading_bar, - LoadingBarType::MinecraftDownload { - // If we are downloading minecraft for a profile, provide its name and uuid - profile_name: profile.metadata.name.clone(), - profile_path: profile.path.clone(), - }, - 100.0, - "Downloading Minecraft", - ) - .await?; - // Download version info (5) let mut version_info = download::download_version_info( &state, diff --git a/theseus/src/state/children.rs b/theseus/src/state/children.rs index 8f5dad29b..9cb079881 100644 --- a/theseus/src/state/children.rs +++ b/theseus/src/state/children.rs @@ -253,9 +253,11 @@ impl Children { let child = child.clone(); let child = child.write().await; if child.current_child.write().await.try_wait()?.is_none() { - if let Some(prof) = - crate::api::profile::get(&child.profile_path.clone()) - .await? + if let Some(prof) = crate::api::profile::get( + &child.profile_path.clone(), + None, + ) + .await? { profiles.push(prof); } diff --git a/theseus/src/state/profiles.rs b/theseus/src/state/profiles.rs index 6148bd799..f6d777bec 100644 --- a/theseus/src/state/profiles.rs +++ b/theseus/src/state/profiles.rs @@ -210,7 +210,7 @@ impl Profile { tokio::task::spawn(async move { let res = async { let state = State::get().await?; - let profile = crate::api::profile::get(&path).await?; + let profile = crate::api::profile::get(&path, None).await?; if let Some(profile) = profile { let paths = profile.get_profile_project_paths()?; diff --git a/theseus_cli/src/subcommands/profile.rs b/theseus_cli/src/subcommands/profile.rs index 6ecb4d303..92a92abfe 100644 --- a/theseus_cli/src/subcommands/profile.rs +++ b/theseus_cli/src/subcommands/profile.rs @@ -257,7 +257,7 @@ impl ProfileList { _args: &crate::Args, _largs: &ProfileCommand, ) -> Result<()> { - let profiles = profile::list().await?; + let profiles = profile::list(None).await?; let rows = profiles.values().map(ProfileRow::from); let table = table(rows).with( diff --git a/theseus_gui/src-tauri/Cargo.toml b/theseus_gui/src-tauri/Cargo.toml index dde54fc27..962110f14 100644 --- a/theseus_gui/src-tauri/Cargo.toml +++ b/theseus_gui/src-tauri/Cargo.toml @@ -15,7 +15,7 @@ tauri-build = { version = "1.2", features = [] } regex = "1.5" [dependencies] -theseus = { path = "../../theseus", features = ["tauri", "cli"] } +theseus = { path = "../../theseus", features = ["tauri"] } serde_json = "1.0" serde = { version = "1.0", features = ["derive"] } diff --git a/theseus_gui/src-tauri/src/api/jre.rs b/theseus_gui/src-tauri/src/api/jre.rs index 1666ba1d1..3fedad587 100644 --- a/theseus_gui/src-tauri/src/api/jre.rs +++ b/theseus_gui/src-tauri/src/api/jre.rs @@ -48,7 +48,7 @@ pub async fn jre_get_optimal_jre_key(profile: Profile) -> Result { // The key can be used in the hashmap contained by JavaGlobals in Settings (if it exists) #[tauri::command] pub async fn jre_get_optimal_jre_key_by_path(path: &Path) -> Result { - let profile = profile::get(path).await?.ok_or_else(|| { + let profile = profile::get(path, Some(true)).await?.ok_or_else(|| { TheseusSerializableError::NoProfileFound(path.display().to_string()) })?; Ok(jre::get_optimal_jre_key(&profile).await?) diff --git a/theseus_gui/src-tauri/src/api/metadata.rs b/theseus_gui/src-tauri/src/api/metadata.rs index e7c01ad26..807f5ec3c 100644 --- a/theseus_gui/src-tauri/src/api/metadata.rs +++ b/theseus_gui/src-tauri/src/api/metadata.rs @@ -19,3 +19,9 @@ pub async fn metadata_get_fabric_versions() -> Result { pub async fn metadata_get_forge_versions() -> Result { Ok(theseus::metadata::get_forge_versions().await?) } + +/// Gets the quilt versions from daedalus +#[tauri::command] +pub async fn metadata_get_quilt_versions() -> Result { + Ok(theseus::metadata::get_quilt_versions().await?) +} diff --git a/theseus_gui/src-tauri/src/api/profile.rs b/theseus_gui/src-tauri/src/api/profile.rs index d8ce08149..a552e0bf5 100644 --- a/theseus_gui/src-tauri/src/api/profile.rs +++ b/theseus_gui/src-tauri/src/api/profile.rs @@ -16,8 +16,11 @@ pub async fn profile_remove(path: &Path) -> Result<()> { // Get a profile by path // invoke('profile_add_path',path) #[tauri::command] -pub async fn profile_get(path: &Path) -> Result> { - let res = profile::get(path).await?; +pub async fn profile_get( + path: &Path, + clear_projects: Option, +) -> Result> { + let res = profile::get(path, clear_projects).await?; Ok(res) } @@ -25,11 +28,32 @@ pub async fn profile_get(path: &Path) -> Result> { // invoke('profile_list') #[tauri::command] pub async fn profile_list( + clear_projects: Option, ) -> Result> { - let res = profile::list().await?; + let res = profile::list(clear_projects).await?; Ok(res) } +#[tauri::command] +pub async fn profile_check_installed( + path: &Path, + project_id: String, +) -> Result { + let profile = profile_get(path, None).await?; + if let Some(profile) = profile { + Ok(profile.projects.into_iter().any(|(_, project)| { + if let ProjectMetadata::Modrinth { project, .. } = &project.metadata + { + project.id == project_id + } else { + false + } + })) + } else { + Ok(false) + } +} + /// Syncs a profile's in memory state with the state on the disk /// // invoke('profile_sync') #[tauri::command] diff --git a/theseus_gui/src-tauri/src/main.rs b/theseus_gui/src-tauri/src/main.rs index d51def999..af99868c7 100644 --- a/theseus_gui/src-tauri/src/main.rs +++ b/theseus_gui/src-tauri/src/main.rs @@ -93,6 +93,7 @@ fn main() { api::profile::profile_run_credentials, api::profile::profile_run_wait_credentials, api::profile::profile_edit, + api::profile::profile_check_installed, api::pack::pack_install_version_id, api::pack::pack_install_file, api::auth::auth_authenticate_begin_flow, @@ -133,6 +134,7 @@ fn main() { api::metadata::metadata_get_game_versions, api::metadata::metadata_get_fabric_versions, api::metadata::metadata_get_forge_versions, + api::metadata::metadata_get_quilt_versions, api::logs::logs_get_logs, api::logs::logs_get_logs_by_datetime, api::logs::logs_get_stdout_by_datetime, diff --git a/theseus_gui/src/components/GridDisplay.vue b/theseus_gui/src/components/GridDisplay.vue index 958952098..e1d4983bc 100644 --- a/theseus_gui/src/components/GridDisplay.vue +++ b/theseus_gui/src/components/GridDisplay.vue @@ -103,7 +103,7 @@ const modsRow = ref(null) margin-right: auto; margin-top: 0.8rem; scroll-behavior: smooth; - overflow-y: scroll; + overflow-y: auto; } } diff --git a/theseus_gui/src/components/modrinth-loading-indicator.js b/theseus_gui/src/components/modrinth-loading-indicator.js index d3e78e3d9..adc9bf98c 100644 --- a/theseus_gui/src/components/modrinth-loading-indicator.js +++ b/theseus_gui/src/components/modrinth-loading-indicator.js @@ -63,7 +63,7 @@ export default defineComponent({ background: props.color || undefined, backgroundSize: `${(100 / indicator.progress.value) * 100}% auto`, transition: 'width 0.1s, height 0.4s, opacity 0.4s', - zIndex: 99, + zIndex: 6, }, }, slots diff --git a/theseus_gui/src/components/ui/Instance.vue b/theseus_gui/src/components/ui/Instance.vue index b73e2855b..59619d45c 100644 --- a/theseus_gui/src/components/ui/Instance.vue +++ b/theseus_gui/src/components/ui/Instance.vue @@ -64,7 +64,7 @@ const install = async (e) => { ) if (props.instance.project_type === 'modpack') { - const packs = Object.values(await list()) + const packs = Object.values(await list(true)) if ( packs.length === 0 || @@ -125,9 +125,14 @@ await process_listener((e) => { @@ -209,10 +217,6 @@ await process_listener((e) => { font-weight: bolder; } } - - .cta { - display: none; - } } .instance { diff --git a/theseus_gui/src/components/ui/InstanceCreationModal.vue b/theseus_gui/src/components/ui/InstanceCreationModal.vue index 59c9d4b0f..91dce8ec2 100644 --- a/theseus_gui/src/components/ui/InstanceCreationModal.vue +++ b/theseus_gui/src/components/ui/InstanceCreationModal.vue @@ -86,13 +86,13 @@ import { CodeIcon, Checkbox, } from 'omorphia' -import { computed, ref } from 'vue' +import { computed, ref, shallowRef } from 'vue' import { get_game_versions, get_loaders } from '@/helpers/tags' import { create } from '@/helpers/profile' import { open } from '@tauri-apps/api/dialog' import { useRouter } from 'vue-router' import { tauri } from '@tauri-apps/api' -import { get_fabric_versions, get_forge_versions } from '@/helpers/metadata' +import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata' const router = useRouter() @@ -129,20 +129,27 @@ defineExpose({ }, }) -const all_game_versions = ref(await get_game_versions()) +const [fabric_versions, forge_versions, quilt_versions, all_game_versions, loaders] = + await Promise.all([ + get_fabric_versions().then(shallowRef), + get_forge_versions().then(shallowRef), + get_quilt_versions().then(shallowRef), + get_game_versions().then(shallowRef), + get_loaders() + .then((value) => + value + .filter((item) => item.supported_project_types.includes('modpack')) + .map((item) => item.name.toLowerCase()) + ) + .then(ref), + ]) const game_versions = computed(() => { return all_game_versions.value .filter((item) => item.version_type === 'release' || showSnapshots.value) .map((item) => item.version) }) -const loaders = ref( - await get_loaders().then((value) => - value - .filter((item) => item.supported_project_types.includes('modpack')) - .map((item) => item.name.toLowerCase()) - ) -) + const modal = ref(null) const check_valid = computed(() => { @@ -191,9 +198,6 @@ const reset_icon = () => { display_icon.value = null } -const fabric_versions = ref(await get_fabric_versions()) -const forge_versions = ref(await get_forge_versions()) - const selectable_versions = computed(() => { if (game_version.value) { if (loader.value === 'fabric') { @@ -202,6 +206,8 @@ const selectable_versions = computed(() => { return forge_versions.value.gameVersions .find((item) => item.id === game_version.value) .loaders.map((item) => item.id) + } else if (loader.value === 'quilt') { + return quilt_versions.value.gameVersions[0].loaders.map((item) => item.id) } } return [] diff --git a/theseus_gui/src/components/ui/InstanceInstallModal.vue b/theseus_gui/src/components/ui/InstanceInstallModal.vue index c98441d58..8fffed4c9 100644 --- a/theseus_gui/src/components/ui/InstanceInstallModal.vue +++ b/theseus_gui/src/components/ui/InstanceInstallModal.vue @@ -11,14 +11,15 @@ import { RightArrowIcon, CheckIcon, } from 'omorphia' -import { computed, ref } from 'vue' -import { add_project_from_version as installMod, list } from '@/helpers/profile' +import { computed, ref, shallowRef } from 'vue' +import { add_project_from_version as installMod, check_installed, list } from '@/helpers/profile' import { tauri } from '@tauri-apps/api' import { open } from '@tauri-apps/api/dialog' import { convertFileSrc } from '@tauri-apps/api/tauri' import { useRouter } from 'vue-router' import { create } from '@/helpers/profile' -import { checkInstalled, installVersionDependencies } from '@/helpers/utils' +import { installVersionDependencies } from '@/helpers/utils' + const router = useRouter() const versions = ref([]) const project = ref('') @@ -33,15 +34,17 @@ const gameVersion = ref(null) const creatingInstance = ref(false) defineExpose({ - show: (projectId, selectedVersion) => { + show: async (projectId, selectedVersion) => { project.value = projectId versions.value = selectedVersion installModal.value.show() searchFilter.value = '' + + profiles.value = await getData() }, }) -const profiles = ref(await list().then(Object.values)) +const profiles = shallowRef(await getData()) async function install(instance) { instance.installing = true @@ -59,8 +62,10 @@ async function install(instance) { instance.installing = false } -const filteredVersions = computed(() => { - const filtered = profiles.value +async function getData() { + const projects = await list(true).then(Object.values) + + const filtered = projects .filter((profile) => { return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase()) }) @@ -69,17 +74,20 @@ const filteredVersions = computed(() => { versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) && versions.value .flatMap((v) => v.loaders) - .some((value) => value === profile.metadata.loader || value === 'minecraft') + .some( + (value) => + value === profile.metadata.loader || ['minecraft', 'iris', 'optifine'].includes(value) + ) ) }) - filtered.map((profile) => { + for (let profile of filtered) { profile.installing = false - profile.installedMod = checkInstalled(profile, project.value) - }) + profile.installedMod = await check_installed(profile.path, project.value) + } return filtered -}) +} const toggleCreation = () => { showCreation.value = !showCreation.value @@ -140,7 +148,7 @@ const check_valid = computed(() => {