first draft

This commit is contained in:
Wyatt Verchere 2024-01-30 17:35:13 -08:00
parent 0d3f007dd4
commit bdde054036
21 changed files with 401 additions and 39 deletions

View File

@ -15,6 +15,7 @@ pub mod profile_create;
pub mod settings; pub mod settings;
pub mod tags; pub mod tags;
pub mod utils; pub mod utils;
pub mod profile_share;
pub type Result<T> = std::result::Result<T, TheseusSerializableError>; pub type Result<T> = std::result::Result<T, TheseusSerializableError>;

View File

@ -0,0 +1,76 @@
use crate::api::Result;
use theseus::{prelude::*, shared_profile::SharedProfile};
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("profile_share")
.invoke_handler(tauri::generate_handler![
profile_share_get_all,
profile_share_install,
profile_share_create,
profile_share_inbound_sync,
profile_share_outbound_sync,
profile_share_generate_share_link,
profile_share_accept_share_link
])
.build()
}
// invoke('plugin:profile_share|profile_share_get_all',profile)
#[tauri::command]
pub async fn profile_share_get_all(
) -> Result<Vec<SharedProfile>> {
let res = shared_profile::get_all()
.await?;
Ok(res)
}
#[tauri::command]
pub async fn profile_share_install(
profile: SharedProfile,
) -> Result<ProfilePathId> {
let res = shared_profile::install(profile)
.await?;
Ok(res)
}
#[tauri::command]
pub async fn profile_share_create(
path: ProfilePathId
) -> Result<()> {
shared_profile::create(path)
.await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_inbound_sync(
path: ProfilePathId
) -> Result<()> {
shared_profile::inbound_sync(path)
.await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_outbound_sync(
path : ProfilePathId
) -> Result<()> {
shared_profile::outbound_sync(path).await?;
Ok(())
}
#[tauri::command]
pub async fn profile_share_generate_share_link(
path : ProfilePathId
) -> Result<String> {
let res = shared_profile::generate_share_link(path).await?;
Ok(res)
}
#[tauri::command]
pub async fn profile_share_accept_share_link(
link : String
) -> Result<()> {
shared_profile::accept_share_link(link).await?;
Ok(())
}

View File

@ -139,6 +139,7 @@ fn main() {
.plugin(api::process::init()) .plugin(api::process::init())
.plugin(api::profile::init()) .plugin(api::profile::init())
.plugin(api::profile_create::init()) .plugin(api::profile_create::init())
.plugin(api::profile_share::init())
.plugin(api::settings::init()) .plugin(api::settings::init())
.plugin(api::tags::init()) .plugin(api::tags::init())
.plugin(api::utils::init()) .plugin(api::utils::init())

View File

@ -40,12 +40,14 @@ import { TauriEvent } from '@tauri-apps/api/event'
import { await_sync, check_safe_loading_bars_complete } from './helpers/state' import { await_sync, check_safe_loading_bars_complete } from './helpers/state'
import { confirm } from '@tauri-apps/api/dialog' import { confirm } from '@tauri-apps/api/dialog'
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import AcceptSharedProfileModal from '@/components/ui/AcceptSharedProfileModal.vue'
import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue' import StickyTitleBar from '@/components/ui/tutorial/StickyTitleBar.vue'
import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue' import OnboardingScreen from '@/components/ui/tutorial/OnboardingScreen.vue'
import { install_from_file } from './helpers/pack' import { install_from_file } from './helpers/pack'
const themeStore = useTheming() const themeStore = useTheming()
const urlModal = ref(null) const urlModal = ref(null)
const sharedProfileConfirmModal = ref(null)
const isLoading = ref(true) const isLoading = ref(true)
const videoPlaying = ref(false) const videoPlaying = ref(false)
@ -237,6 +239,9 @@ command_listener(async (e) => {
source: 'CreationModalFileDrop', source: 'CreationModalFileDrop',
}) })
} }
} else if (e.event === 'OpenSharedProfile') {
// Install a shared profile
sharedProfileConfirmModal.value.show(e)
} else { } else {
// Other commands are URL-based (deep linking) // Other commands are URL-based (deep linking)
urlModal.value.show(e) urlModal.value.show(e)
@ -388,6 +393,7 @@ command_listener(async (e) => {
</div> </div>
</div> </div>
<URLConfirmModal ref="urlModal" /> <URLConfirmModal ref="urlModal" />
<AcceptSharedProfileModal ref="sharedProfileConfirmModal" />
<Notifications ref="notificationsWrapper" /> <Notifications ref="notificationsWrapper" />
</template> </template>

View File

@ -169,7 +169,7 @@ const filteredResults = computed(() => {
}) })
} else if (filters.value === 'Downloaded modpacks') { } else if (filters.value === 'Downloaded modpacks') {
instances = instances.filter((instance) => { instances = instances.filter((instance) => {
return instance.metadata?.linked_data return instance.metadata?.linked_data?.modrinth_modpack
}) })
} }

View File

@ -170,7 +170,7 @@ const handleOptionsClick = async (args) => {
break break
case 'install': { case 'install': {
const versions = await useFetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${args.item.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${args.item.project_id}/version`,
'project versions' 'project versions'
) )

View File

@ -0,0 +1,89 @@
<script setup>
import { Modal, Button } from 'omorphia'
import { ref } from 'vue'
import { useFetch } from '@/helpers/fetch.js'
import SearchCard from '@/components/ui/SearchCard.vue'
import { handleError } from '@/store/notifications.js'
import { share_accept, share_install } from '@/helpers/shared_profiles.js'
const confirmModal = ref(null)
const linkId = ref(null)
const sharedProfile = ref(null)
defineExpose({
async show(event) {
linkId.value = event.id
sharedProfile.value = await useFetch(
`https://staging-api.modrinth.com/_internal/share/${encodeURIComponent(event.id)}`,
'shared profile'
)
confirmModal.value.show()
},
})
async function install() {
confirmModal.value.hide()
await share_accept(linkId.value).catch(handleError)
await share_install(sharedProfile.value.id).catch(handleError)
}
</script>
<template>
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
<div class="modal-body">
<SearchCard
:project="project"
class="project-card"
:categories="categories"
@open="confirmModal.hide()"
/>
<div class="button-row">
<div class="markdown-body">
<p>
Installing <code>{{ sharedProfile.id }}</code> from user {{ sharedProfile.owner_id }}
</p>
</div>
<div class="button-group">
<Button color="primary" @click="install">Install</Button>
</div>
</div>
</div>
</Modal>
</template>
<style scoped lang="scss">
.modal-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: var(--gap-md);
padding: var(--gap-lg);
}
.button-row {
width: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--gap-md);
}
.button-group {
display: flex;
flex-direction: row;
gap: var(--gap-sm);
}
.project-card {
background-color: var(--color-bg);
width: 100%;
:deep(.badge) {
border: 1px solid var(--color-raised-bg);
background-color: var(--color-accent-contrast);
}
}
</style>

View File

@ -71,7 +71,7 @@ const install = async (e) => {
e?.stopPropagation() e?.stopPropagation()
modLoading.value = true modLoading.value = true
const versions = await useFetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${props.instance.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${props.instance.project_id}/version`,
'project versions' 'project versions'
) )
@ -82,7 +82,7 @@ const install = async (e) => {
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === props.instance.project_id) .find((pack) => pack.linked_data?.modrinth_modpack?.project_id === props.instance.project_id)
) { ) {
modLoading.value = true modLoading.value = true
await pack_install( await pack_install(

View File

@ -247,7 +247,9 @@ const check_valid = computed(() => {
</Button> </Button>
<div <div
v-tooltip=" v-tooltip="
profile.metadata.linked_data?.locked && !profile.installedMod (profile.metadata.linked_data?.modrinth_modpack.locked
|| profile.metadata.linked_data?.shared_profile
) && !profile.installedMod
? 'Unpair or unlock an instance to add mods.' ? 'Unpair or unlock an instance to add mods.'
: '' : ''
" "
@ -265,7 +267,7 @@ const check_valid = computed(() => {
? 'Installing...' ? 'Installing...'
: profile.installedMod : profile.installedMod
? 'Installed' ? 'Installed'
: profile.metadata.linked_data && profile.metadata.linked_data.locked : profile.metadata.linked_data?.modrinth_modpack.locked || profile.metadata.linked_data?.shared_profile
? 'Paired' ? 'Paired'
: 'Install' : 'Install'
}} }}

View File

@ -28,7 +28,7 @@ const filteredVersions = computed(() => {
}) })
const modpackVersionModal = ref(null) const modpackVersionModal = ref(null)
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.version_id) const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
const installing = computed(() => props.instance.install_stage !== 'installed') const installing = computed(() => props.instance.install_stage !== 'installed')
const inProgress = ref(false) const inProgress = ref(false)
@ -49,7 +49,7 @@ const switchVersion = async (versionId) => {
:noblur="!themeStore.advancedRendering" :noblur="!themeStore.advancedRendering"
> >
<div class="modal-body"> <div class="modal-body">
<Card v-if="instance.metadata.linked_data" class="mod-card"> <Card v-if="instance.metadata.linked_data?.modrinth_modpack" class="mod-card">
<div class="table"> <div class="table">
<div class="table-row with-columns table-head"> <div class="table-row with-columns table-head">
<div class="table-cell table-text download-cell" /> <div class="table-cell table-text download-cell" />

View File

@ -73,7 +73,7 @@ const install = async (e) => {
e?.stopPropagation() e?.stopPropagation()
installing.value = true installing.value = true
const versions = await useFetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${props.project.project_id}/version`,
'project versions' 'project versions'
) )
@ -84,7 +84,7 @@ const install = async (e) => {
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === props.project.project_id) .find((pack) => pack.linked_data?.modrinth_modpack?.project_id === props.project.project_id)
) { ) {
installing.value = true installing.value = true
await pack_install( await pack_install(

View File

@ -135,7 +135,7 @@ const installed = ref(props.installed)
async function install() { async function install() {
installing.value = true installing.value = true
const versions = await useFetch( const versions = await useFetch(
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${props.project.project_id}/version`,
'project versions' 'project versions'
) )
let queuedVersionData let queuedVersionData
@ -156,7 +156,7 @@ async function install() {
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === props.project.project_id) .find((pack) => pack.linked_data?.modrinth_modpack?.project_id === props.project.project_id)
) { ) {
await packInstall( await packInstall(
props.project.project_id, props.project.project_id,

View File

@ -20,20 +20,20 @@ defineExpose({
async show(event) { async show(event) {
if (event.event === 'InstallVersion') { if (event.event === 'InstallVersion') {
version.value = await useFetch( version.value = await useFetch(
`https://api.modrinth.com/v2/version/${encodeURIComponent(event.id)}`, `https://staging-api.modrinth.com/v2/version/${encodeURIComponent(event.id)}`,
'version' 'version'
) )
project.value = await useFetch( project.value = await useFetch(
`https://api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`, `https://staging-api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
'project' 'project'
) )
} else { } else {
project.value = await useFetch( project.value = await useFetch(
`https://api.modrinth.com/v2/project/${encodeURIComponent(event.id)}`, `https://staging-api.modrinth.com/v2/project/${encodeURIComponent(event.id)}`,
'project' 'project'
) )
version.value = await useFetch( version.value = await useFetch(
`https://api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`, `https://staging-api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
'version' 'version'
) )
} }

View File

@ -0,0 +1,42 @@
/**
* All theseus API calls return serialized values (both return values and errors);
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object.
*/
import { invoke } from '@tauri-apps/api/tauri'
/// Created shared modpack from profile
export async function share_create(path) {
return await invoke('plugin:profile_share|profile_share_create', { path })
}
/// Generates a shared profile link
export async function share_generate(path) {
return await invoke('plugin:profile_share|profile_share_generate_share_link', { path })
}
/// Accepts a shared profile link
export async function share_accept(link) {
return await invoke('plugin:profile_share|profile_share_accept', { link })
}
/// Install a pack from a shared profile id
export async function share_install(id) {
return await invoke('plugin:profile_share|profile_share_install', { id })
}
// get all user profiles that are available to the currentt user
export async function get_all(path) {
return await invoke('plugin:profile_share|profile_share_get_all', { path })
}
// syncs profile to match that on server
export async function inbound_sync(path) {
return await invoke('plugin:profile_share|profile_share_inbound_sync', { path })
}
// syncs profile to update server
// only allowed if profile is owned by user
export async function outbound_sync(path) {
return await invoke('plugin:profile_share|profile_share_outbound_sync', { path })
}

View File

@ -68,7 +68,7 @@ export const installVersionDependencies = async (profile, version) => {
) )
continue continue
const depVersions = await useFetch( const depVersions = await useFetch(
`https://api.modrinth.com/v2/project/${dep.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${dep.project_id}/version`,
'dependency versions' 'dependency versions'
) )
const latest = depVersions.find( const latest = depVersions.find(

View File

@ -149,7 +149,7 @@ if (route.query.ai) {
} }
async function refreshSearch() { async function refreshSearch() {
const base = 'https://api.modrinth.com/v2/' const base = 'https://staging-api.modrinth.com/v2/'
const params = [`limit=${maxResults.value}`, `index=${sortType.value.name}`] const params = [`limit=${maxResults.value}`, `index=${sortType.value.name}`]
if (query.value.length > 0) { if (query.value.length > 0) {

View File

@ -31,8 +31,8 @@ const getInstances = async () => {
let filters = [] let filters = []
for (const instance of recentInstances.value) { for (const instance of recentInstances.value) {
if (instance.metadata.linked_data && instance.metadata.linked_data.project_id) { if (instance.metadata.linked_data?.modrinth_modpack?.project_id) {
filters.push(`NOT"project_id"="${instance.metadata.linked_data.project_id}"`) filters.push(`NOT"project_id"="${instance.metadata.linked_data?.modrinth_modpack?.project_id}"`)
} }
} }
filter.value = filters.join(' AND ') filter.value = filters.join(' AND ')
@ -40,7 +40,7 @@ const getInstances = async () => {
const getFeaturedModpacks = async () => { const getFeaturedModpacks = async () => {
const response = await useFetch( const response = await useFetch(
`https://api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`, `https://staging-api.modrinth.com/v2/search?facets=[["project_type:modpack"]]&limit=10&index=follows&filters=${filter.value}`,
'featured modpacks', 'featured modpacks',
offline.value offline.value
) )
@ -52,7 +52,7 @@ const getFeaturedModpacks = async () => {
} }
const getFeaturedMods = async () => { const getFeaturedMods = async () => {
const response = await useFetch( const response = await useFetch(
'https://api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows', 'https://staging-api.modrinth.com/v2/search?facets=[["project_type:mod"]]&limit=10&index=follows',
'featured mods', 'featured mods',
offline.value offline.value
) )

View File

@ -209,9 +209,9 @@ const checkProcess = async () => {
// Get information on associated modrinth versions, if any // Get information on associated modrinth versions, if any
const modrinthVersions = ref([]) const modrinthVersions = ref([])
if (!(await isOffline()) && instance.value.metadata.linked_data?.project_id) { if (!(await isOffline()) && instance.value.metadata.linked_data?.modrinth_modpack?.project_id) {
modrinthVersions.value = await useFetch( modrinthVersions.value = await useFetch(
`https://api.modrinth.com/v2/project/${instance.value.metadata.linked_data.project_id}/version`, `https://staging-api.modrinth.com/v2/project/${instance.value.metadata.linked_data?.modrinth_modpack?.project_id}/version`,
'project' 'project'
) )
} }

View File

@ -359,7 +359,7 @@
/> />
<ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" /> <ExportModal v-if="projects.length > 0" ref="exportModal" :instance="instance" />
<ModpackVersionModal <ModpackVersionModal
v-if="instance.metadata.linked_data" v-if="instance.metadata.linked_data?.modrinth_modpack"
ref="modpackVersionModal" ref="modpackVersionModal"
:instance="instance" :instance="instance"
:versions="props.versions" :versions="props.versions"
@ -443,11 +443,18 @@ const projects = ref([])
const selectionMap = ref(new Map()) const selectionMap = ref(new Map())
const showingOptions = ref(false) const showingOptions = ref(false)
const isPackLocked = computed(() => { const isPackLocked = computed(() => {
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked if (props.instance.metadata.linked_data?.shared_profile) {
return !props.instance.metadata.linked_data.shared_profile.is_owner
}
return props.instance.metadata.linked_data?.modrinth_modpack?.locked
}) })
const canUpdatePack = computed(() => { const canUpdatePack = computed(() => {
if (!props.instance.metadata.linked_data) return false if (!props.instance.metadata.linked_data) return false
return props.instance.metadata.linked_data.version_id !== props.instance.modrinth_update_version let linked_data = props.instance.metadata.linked_data
if (linked_data.modrinth_modpack) {
return linked_data.modrinth_modpack.version_id !== props.instance.sync_update_version
}
return false
}) })
const exportModal = ref(null) const exportModal = ref(null)

View File

@ -358,7 +358,7 @@
/> />
</div> </div>
</Card> </Card>
<Card v-if="instance.metadata.linked_data"> <Card v-if="instance.metadata.linked_data?.modrinth_modpack">
<div class="label"> <div class="label">
<h3> <h3>
<span class="label__title size-card-header">Modpack</span> <span class="label__title size-card-header">Modpack</span>
@ -413,8 +413,7 @@
<XIcon /> Unpair <XIcon /> Unpair
</Button> </Button>
</div> </div>
<div v-if="props.instance.metadata.linked_data?.modrinth_modpack?.project_id" class="adjacent-input">
<div v-if="props.instance.metadata.linked_data.project_id" class="adjacent-input">
<label for="change-modpack-version"> <label for="change-modpack-version">
<span class="label__title">Change modpack version</span> <span class="label__title">Change modpack version</span>
<span class="label__description"> <span class="label__description">
@ -445,6 +444,90 @@
</Button> </Button>
</div> </div>
</Card> </Card>
<Card v-if="installedSharedProfileData">
<div class="label">
<h3>
<span class="label__title size-card-header">Shared profile management</span>
</h3>
</div>
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
<label for="share-links">
<span class="label__title">Generate share link</span>
<span class="label__description">
Creates a share link to share this modpack with others. This allows them to install your instance, as well as stay up to date with any changes you make.
</span>
</label>
<Button id="share-links" @click="generateShareLink">
<GlobeIcon /> Share
</Button>
</div>
<div v-if="shareLink" class="adjacent-input">
Generated link: <code>{{ shareLink }}</code>
</div>
<div v-if="installedSharedProfileData.is_owned" class="table">
<div class="table-row table-head">
<div class="table-cell table-text name-cell actions-cell">
<Button class="transparent">
Name
</Button>
</div>
</div>
<div
v-for="user in installedSharedProfileData.users"
:key="user"
class="table-row"
>
<div class="table-cell table-text name-cell">
<div class="user-content">
<span v-tooltip="`${user}`" class="title">{{ user }}</span>
</div>
</div>
<div class="table-cell table-text manage">
<div v-tooltip="'Remove project'">
<Button icon-only @click="removeSharedPackUser(user)">
<TrashIcon />
</Button>
</div>
</div>
</div>
</div>
<div v-if="installedSharedProfileData.is_owned" class="adjacent-input">
your project
{{ props.instance.sync_update_version }}
:)
<label for="share-sync">
<span class="label__title">Sync shared profile</span>
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
You are up to date with the shared profile.
</span>
<span class="label__description" v-else>
You have changes that have not been synced to the shared profile. Click the button to upload your changes.
</span>
</label>
<Button id="share-sync-sync" @click="outboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
<GlobeIcon /> Sync
</Button>
<Button id="share-sync-revert" @click="inboundSyncSharedProfile" :disabled="props.instance.sync_update_version?.is_synced">
<GlobeIcon /> Revert
</Button>
</div>
<div v-else>
not yours
{{ props.instance.sync_update_version }}
<label for="share-sync">
<span class="label__title">Sync shared profile</span>
<span class="label__description" v-if="props.instance.sync_update_version?.is_synced">
You are up to date with the shared profile.
</span>
<span class="label__description" v-else>
You are not up to date with the shared profile. Click the button to update your instance.
</span>
</label>
<Button id="share-sync-sync" @click="inboundSyncSharedProfile">
<GlobeIcon /> Sync
</Button>
</div>
</Card>
<Card> <Card>
<div class="label"> <div class="label">
<h3> <h3>
@ -502,7 +585,7 @@
</div> </div>
</Card> </Card>
<ModpackVersionModal <ModpackVersionModal
v-if="instance.metadata.linked_data" v-if="instance.metadata.linked_data?.modrinth_modpack"
ref="modpackVersionModal" ref="modpackVersionModal"
:instance="instance" :instance="instance"
:versions="props.versions" :versions="props.versions"
@ -527,6 +610,7 @@ import {
HammerIcon, HammerIcon,
ModalConfirm, ModalConfirm,
DownloadIcon, DownloadIcon,
GlobeIcon,
ClipboardCopyIcon, ClipboardCopyIcon,
Button, Button,
Toggle, Toggle,
@ -545,6 +629,12 @@ import {
remove, remove,
update_repair_modrinth, update_repair_modrinth,
} from '@/helpers/profile.js' } from '@/helpers/profile.js'
import {
get_all,
outbound_sync,
inbound_sync,
share_generate
} from '@/helpers/shared_profiles.js'
import { computed, readonly, ref, shallowRef, watch } from 'vue' import { computed, readonly, ref, shallowRef, watch } from 'vue'
import { get_max_memory } from '@/helpers/jre.js' import { get_max_memory } from '@/helpers/jre.js'
import { get } from '@/helpers/settings.js' import { get } from '@/helpers/settings.js'
@ -659,12 +749,18 @@ const unlinkModpack = ref(false)
const inProgress = ref(false) const inProgress = ref(false)
const installing = computed(() => props.instance.install_stage !== 'installed') const installing = computed(() => props.instance.install_stage !== 'installed')
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.version_id) const installedVersion = computed(() => props.instance?.metadata?.linked_data?.modrinth_modpack?.version_id)
const installedVersionData = computed(() => { const installedVersionData = computed(() => {
if (!installedVersion.value) return null if (!installedVersion.value) return null
return props.versions.find((version) => version.id === installedVersion.value) return props.versions.find((version) => version.id === installedVersion.value)
}) })
const sharedProfiles = await get_all();
const installedSharedProfileData = computed(() => {
if (!props.instance.metadata.linked_data?.shared_profile) return null
return sharedProfiles.find((profile) => profile.id === props.instance.metadata.linked_data?.shared_profile?.profile_id)
})
watch( watch(
[ [
title, title,
@ -794,13 +890,13 @@ async function unpairProfile() {
async function unlockProfile() { async function unlockProfile() {
const editProfile = props.instance const editProfile = props.instance
editProfile.metadata.linked_data.locked = false editProfile.metadata.linked_data.modrinth_modpack.locked = false
await edit(props.instance.path, editProfile) await edit(props.instance.path, editProfile)
modalConfirmUnlock.value.hide() modalConfirmUnlock.value.hide()
} }
const isPackLocked = computed(() => { const isPackLocked = computed(() => {
return props.instance.metadata.linked_data && props.instance.metadata.linked_data.locked return props.instance.metadata.linked_data?.modrinth_modpack.locked
}) })
async function repairModpack() { async function repairModpack() {
@ -932,6 +1028,20 @@ async function saveGvLoaderEdits() {
editing.value = false editing.value = false
changeVersionsModal.value.hide() changeVersionsModal.value.hide()
} }
async function outboundSyncSharedProfile() {
await outbound_sync(props.instance.path).catch(handleError)
}
async function inboundSyncSharedProfile() {
await inbound_sync(props.instance.path).catch(handleError)
}
const shareLink = ref(null)
async function generateShareLink() {
shareLink.value = await share_generate(props.instance.path).catch(handleError)
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@ -1012,4 +1122,32 @@ async function saveGvLoaderEdits() {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
} }
.table {
margin-block-start: 0;
border-radius: var(--radius-lg);
border: 2px solid var(--color-bg);
}
.table-row {
grid-template-columns: 7fr 1fr;
}
.table-cell {
align-items: center;
}
.user-content {
display: flex;
align-items: center;
gap: 1rem;
.title {
color: var(--color-contrast);
font-weight: bolder;
margin-left: 1rem;
}
}
</style> </style>

View File

@ -314,10 +314,10 @@ async function fetchProjectData() {
categories.value, categories.value,
instance.value, instance.value,
] = await Promise.all([ ] = await Promise.all([
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project'), useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}`, 'project'),
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project'), useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`, 'project'), useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'), useFetch(`https://staging-api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'),
get_categories().catch(handleError), get_categories().catch(handleError),
route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(), route.query.i ? getInstance(route.query.i, false).catch(handleError) : Promise.resolve(),
]) ])
@ -391,7 +391,7 @@ async function install(version) {
packs.length === 0 || packs.length === 0 ||
!packs !packs
.map((value) => value.metadata) .map((value) => value.metadata)
.find((pack) => pack.linked_data?.project_id === data.value.id) .find((pack) => pack.linked_data?.modrinth_modpack?.project_id === data.value.id)
) { ) {
await packInstall( await packInstall(
data.value.id, data.value.id,