You've already forked AstralRinth
forked from didirus/AstralRinth
Analytics + more bug fixes (#144)
* Analytics + more bug fixes * debug deadlock * Fix mostly everything * merge fixes * fix rest * final fixeS
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { RouterView, RouterLink } from 'vue-router'
|
||||
import { ref, watch } from 'vue'
|
||||
import { RouterView, RouterLink, useRouter } from 'vue-router'
|
||||
import {
|
||||
HomeIcon,
|
||||
SearchIcon,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
Button,
|
||||
Notifications,
|
||||
} from 'omorphia'
|
||||
import { handleError, useLoading, useTheming } from '@/store/state'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { get } from '@/helpers/settings'
|
||||
@@ -20,31 +20,47 @@ import SplashScreen from '@/components/ui/SplashScreen.vue'
|
||||
import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||
import { useNotifications } from '@/store/notifications.js'
|
||||
import { warning_listener } from '@/helpers/events.js'
|
||||
import { isDev } from '@/helpers/utils.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const isLoading = ref(true)
|
||||
onMounted(async () => {
|
||||
const { settings, collapsed_navigation } = await get().catch(handleError)
|
||||
themeStore.setThemeState(settings)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
initialize: async () => {
|
||||
isLoading.value = false
|
||||
const { theme } = await get()
|
||||
const { theme, opt_out_analytics, collapsed_navigation, advanced_rendering } = await get()
|
||||
const dev = await isDev()
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
|
||||
mixpanel.init('014c7d6a336d0efaefe3aca91063748d', { debug: dev, persistence: 'localStorage' })
|
||||
if (opt_out_analytics) {
|
||||
mixpanel.opt_out_tracking()
|
||||
}
|
||||
mixpanel.track('Launched')
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
await warning_listener((e) =>
|
||||
notificationsWrapper.value.addNotification({
|
||||
title: 'Warning',
|
||||
text: e.message,
|
||||
type: 'warn',
|
||||
})
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
router.afterEach((to, from, failure) => {
|
||||
if (mixpanel.__loaded) {
|
||||
mixpanel.track('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
}
|
||||
})
|
||||
|
||||
const loading = useLoading()
|
||||
|
||||
const notifications = useNotifications()
|
||||
@@ -54,8 +70,6 @@ watch(notificationsWrapper, () => {
|
||||
notifications.setNotifs(notificationsWrapper.value)
|
||||
})
|
||||
|
||||
document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
document.querySelector('body').addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
|
||||
@@ -12,10 +12,16 @@ import {
|
||||
Card,
|
||||
DropdownSelect,
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
Button,
|
||||
formatCategoryHeader,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { remove } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -32,6 +38,19 @@ const props = defineProps({
|
||||
const instanceOptions = ref(null)
|
||||
const instanceComponents = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
const confirmModal = ref(null)
|
||||
|
||||
async function deleteProfile() {
|
||||
if (currentDeleteInstance.value) {
|
||||
instanceComponents.value = instanceComponents.value.filter(
|
||||
(x) => x.instance.path !== currentDeleteInstance.value
|
||||
)
|
||||
await remove(currentDeleteInstance.value).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRightClick = (event, item) => {
|
||||
const baseOptions = [
|
||||
{ name: 'add_content' },
|
||||
@@ -68,13 +87,12 @@ const handleRightClick = (event, item) => {
|
||||
}
|
||||
|
||||
const handleOptionsClick = async (args) => {
|
||||
console.log(args)
|
||||
switch (args.option) {
|
||||
case 'play':
|
||||
args.item.play()
|
||||
args.item.play(null, 'InstanceGridContextMenu')
|
||||
break
|
||||
case 'stop':
|
||||
args.item.stop()
|
||||
args.item.stop(null, 'InstanceGridContextMenu')
|
||||
break
|
||||
case 'add_content':
|
||||
await args.item.addContent()
|
||||
@@ -82,15 +100,16 @@ const handleOptionsClick = async (args) => {
|
||||
case 'edit':
|
||||
await args.item.seeInstance()
|
||||
break
|
||||
case 'delete':
|
||||
await args.item.deleteInstance()
|
||||
break
|
||||
case 'open':
|
||||
await args.item.openFolder()
|
||||
break
|
||||
case 'copy':
|
||||
await navigator.clipboard.writeText(args.item.instance.path)
|
||||
break
|
||||
case 'delete':
|
||||
currentDeleteInstance.value = args.item.instance.path
|
||||
confirmModal.value.show()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -185,6 +204,15 @@ const filteredResults = computed(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<ModalConfirm
|
||||
ref="confirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="deleteProfile"
|
||||
/>
|
||||
<Card class="header">
|
||||
<div class="iconified-input">
|
||||
<SearchIcon />
|
||||
@@ -222,7 +250,7 @@ const filteredResults = computed(() => {
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
v-for="(instanceSection, index) in Array.from(filteredResults, ([key, value]) => ({
|
||||
v-for="instanceSection in Array.from(filteredResults, ([key, value]) => ({
|
||||
key,
|
||||
value,
|
||||
}))"
|
||||
@@ -235,7 +263,7 @@ const filteredResults = computed(() => {
|
||||
</div>
|
||||
<section class="instances">
|
||||
<Instance
|
||||
v-for="instance in instanceSection.value"
|
||||
v-for="(instance, index) in instanceSection.value"
|
||||
ref="instanceComponents"
|
||||
:key="instance.id"
|
||||
:instance="instance"
|
||||
@@ -298,6 +326,10 @@ const filteredResults = computed(() => {
|
||||
|
||||
.iconified-input {
|
||||
flex-grow: 1;
|
||||
|
||||
input {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.sort-dropdown {
|
||||
|
||||
@@ -12,10 +12,14 @@ import {
|
||||
StopCircleIcon,
|
||||
ExternalIcon,
|
||||
EyeIcon,
|
||||
ModalConfirm,
|
||||
} from 'omorphia'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { remove } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -36,6 +40,19 @@ const modsRow = ref(null)
|
||||
const instanceOptions = ref(null)
|
||||
const instanceComponents = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
const confirmModal = ref(null)
|
||||
|
||||
async function deleteProfile() {
|
||||
if (currentDeleteInstance.value) {
|
||||
instanceComponents.value = instanceComponents.value.filter(
|
||||
(x) => x.instance.path !== currentDeleteInstance.value
|
||||
)
|
||||
await remove(currentDeleteInstance.value).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
const handlePaginationDisplay = () => {
|
||||
for (let i = 0; i < props.instances.length; i++) {
|
||||
let parentsRow = modsRow.value[i]
|
||||
@@ -114,10 +131,10 @@ const handleInstanceRightClick = (event, passedInstance) => {
|
||||
const handleOptionsClick = async (args) => {
|
||||
switch (args.option) {
|
||||
case 'play':
|
||||
await args.item.play()
|
||||
await args.item.play(null, 'InstanceRowContextMenu')
|
||||
break
|
||||
case 'stop':
|
||||
await args.item.stop()
|
||||
await args.item.stop(null, 'InstanceRowContextMenu')
|
||||
break
|
||||
case 'add_content':
|
||||
await args.item.addContent()
|
||||
@@ -126,7 +143,8 @@ const handleOptionsClick = async (args) => {
|
||||
await args.item.seeInstance()
|
||||
break
|
||||
case 'delete':
|
||||
await args.item.deleteInstance()
|
||||
currentDeleteInstance.value = args.item.instance.path
|
||||
confirmModal.value.show()
|
||||
break
|
||||
case 'open_folder':
|
||||
await args.item.openFolder()
|
||||
@@ -165,6 +183,15 @@ const getInstanceIndex = (rowIndex, index) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ModalConfirm
|
||||
ref="confirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="deleteProfile"
|
||||
/>
|
||||
<div class="content">
|
||||
<div v-for="(row, rowIndex) in instances" :key="row.label" class="row">
|
||||
<div class="header">
|
||||
|
||||
@@ -65,6 +65,7 @@ import {
|
||||
import { get, set } from '@/helpers/settings'
|
||||
import { WebviewWindow } from '@tauri-apps/api/window'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
defineProps({
|
||||
expanded: {
|
||||
@@ -131,6 +132,7 @@ const login = async () => {
|
||||
await setAccount(loggedIn)
|
||||
await refreshValues()
|
||||
await window.close()
|
||||
mixpanel.track('AccountLogIn')
|
||||
}
|
||||
|
||||
const logout = async (id) => {
|
||||
@@ -140,6 +142,7 @@ const logout = async (id) => {
|
||||
await setAccount(accounts.value[0])
|
||||
await refreshValues()
|
||||
}
|
||||
mixpanel.track('AccountLogOut')
|
||||
}
|
||||
|
||||
const toggle = () => {
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
<template>
|
||||
<Modal ref="incompatibleModal" header="Incompatibility warning">
|
||||
<Modal
|
||||
ref="incompatibleModal"
|
||||
header="Incompatibility warning"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
|
||||
@@ -54,9 +58,14 @@
|
||||
import { Button, Modal, XIcon, DownloadIcon, DropdownSelect, formatCategory } from 'omorphia'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
import { defineExpose, ref } from 'vue'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { handleError, useTheming } from '@/store/state.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const instance = ref(null)
|
||||
const project = ref(null)
|
||||
const projectType = ref(null)
|
||||
const projectTitle = ref(null)
|
||||
const versions = ref(null)
|
||||
const selectedVersion = ref(null)
|
||||
@@ -66,13 +75,26 @@ const installing = ref(false)
|
||||
let markInstalled = () => {}
|
||||
|
||||
defineExpose({
|
||||
show: (instanceVal, projectTitleVal, selectedVersions, extMarkInstalled) => {
|
||||
show: (
|
||||
instanceVal,
|
||||
projectTitleVal,
|
||||
selectedVersions,
|
||||
extMarkInstalled,
|
||||
projectIdVal,
|
||||
projectTypeVal
|
||||
) => {
|
||||
instance.value = instanceVal
|
||||
projectTitle.value = projectTitleVal
|
||||
versions.value = selectedVersions
|
||||
selectedVersion.value = selectedVersions[0]
|
||||
|
||||
project.value = projectIdVal
|
||||
projectType.value = projectTypeVal
|
||||
|
||||
incompatibleModal.value.show()
|
||||
markInstalled = extMarkInstalled
|
||||
|
||||
mixpanel.track('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -82,6 +104,16 @@ const install = async () => {
|
||||
installing.value = false
|
||||
markInstalled()
|
||||
incompatibleModal.value.hide()
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
id: project.value,
|
||||
version_id: selectedVersion.value.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
source: 'ProjectIncompatibilityWarningModal',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
import { Button, Modal, XIcon, DownloadIcon } from 'omorphia'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { ref } from 'vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const version = ref('')
|
||||
const title = ref('')
|
||||
@@ -11,12 +15,14 @@ const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: (id, projectId, projectTitle, projectIcon) => {
|
||||
show: (id, projectIdVal, projectTitle, projectIcon) => {
|
||||
version.value = id
|
||||
projectId.value = projectId
|
||||
projectId.value = projectIdVal
|
||||
title.value = projectTitle
|
||||
icon.value = projectIcon
|
||||
confirmModal.value.show()
|
||||
|
||||
mixpanel.track('PackInstallStart')
|
||||
},
|
||||
})
|
||||
|
||||
@@ -24,11 +30,18 @@ async function install() {
|
||||
installing.value = true
|
||||
await pack_install(projectId.value, version.value, title.value, icon.value ? icon.value : null)
|
||||
confirmModal.value.hide()
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: projectId.value,
|
||||
version_id: version.value,
|
||||
title: title.value,
|
||||
source: 'ConfirmModal',
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="confirmModal" header="Are you sure?">
|
||||
<Modal ref="confirmModal" header="Are you sure?" :noblur="!themeStore.advancedRendering">
|
||||
<div class="modal-body">
|
||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||
<div class="input-group push-right">
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Card, DownloadIcon, StopCircleIcon, Avatar, AnimatedLogo, PlayIcon } fr
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { list, remove, run } from '@/helpers/profile'
|
||||
import { list, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
@@ -16,6 +16,7 @@ import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showInFolder } from '@/helpers/utils.js'
|
||||
import InstanceInstallModal from '@/components/ui/InstanceInstallModal.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -91,6 +92,13 @@ const install = async (e) => {
|
||||
props.instance.icon_url
|
||||
).catch(handleError)
|
||||
modLoading.value = false
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: props.instance.project_id,
|
||||
version_id: versions[0].id,
|
||||
title: props.instance.title,
|
||||
source: 'InstanceCard',
|
||||
})
|
||||
} else
|
||||
confirmModal.value.show(
|
||||
props.instance.project_id,
|
||||
@@ -99,21 +107,32 @@ const install = async (e) => {
|
||||
props.instance.icon_url
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(props.instance.project_id, versions)
|
||||
modInstallModal.value.show(
|
||||
props.instance.project_id,
|
||||
versions,
|
||||
props.instance.title,
|
||||
props.instance.project_type
|
||||
)
|
||||
}
|
||||
|
||||
modLoading.value = false
|
||||
}
|
||||
|
||||
const play = async (e) => {
|
||||
const play = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
modLoading.value = true
|
||||
uuid.value = await run(props.instance.path).catch(handleError)
|
||||
modLoading.value = false
|
||||
playing.value = true
|
||||
|
||||
mixpanel.track('InstancePlay', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const stop = async (e) => {
|
||||
const stop = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
@@ -126,11 +145,13 @@ const stop = async (e) => {
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError) // If we still have the uuid, just kill it
|
||||
|
||||
uuid.value = null
|
||||
}
|
||||
mixpanel.track('InstanceStop', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
source: context,
|
||||
})
|
||||
|
||||
const deleteInstance = async () => {
|
||||
await remove(props.instance.path).catch(handleError)
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
const openFolder = async () => {
|
||||
@@ -151,7 +172,6 @@ defineExpose({
|
||||
stop,
|
||||
seeInstance,
|
||||
openFolder,
|
||||
deleteInstance,
|
||||
addContent,
|
||||
instance: props.instance,
|
||||
})
|
||||
@@ -190,7 +210,7 @@ onUnmounted(() => unlisten())
|
||||
<div
|
||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||
class="install cta button-base"
|
||||
@click="play"
|
||||
@click="(e) => play(e, 'InstanceCard')"
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
@@ -200,7 +220,7 @@ onUnmounted(() => unlisten())
|
||||
<div
|
||||
v-else-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="stop"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal ref="modal" header="Create instance">
|
||||
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
||||
<div class="modal-header">
|
||||
<Chips v-model="creationType" :items="['custom', 'from file']" />
|
||||
</div>
|
||||
@@ -116,9 +116,13 @@ import {
|
||||
} from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
const loader = ref('vanilla')
|
||||
@@ -144,6 +148,8 @@ defineExpose({
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
modal.value.show()
|
||||
|
||||
mixpanel.track('InstanceCreateStart', { source: 'CreationModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -195,6 +201,7 @@ const create_instance = async () => {
|
||||
creating.value = true
|
||||
const loader_version_value =
|
||||
loader_version.value === 'other' ? specified_loader_version.value : loader_version.value
|
||||
const loaderVersion = loader.value === 'vanilla' ? null : loader_version_value ?? 'stable'
|
||||
|
||||
modal.value.hide()
|
||||
creating.value = false
|
||||
@@ -206,6 +213,15 @@ const create_instance = async () => {
|
||||
loader.value === 'vanilla' ? null : loader_version_value ?? 'stable',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('InstanceCreate', {
|
||||
profile_name: profile_name.value,
|
||||
game_version: game_version.value,
|
||||
loader: loader.value,
|
||||
loader_version: loaderVersion,
|
||||
has_icon: !!icon.value,
|
||||
source: 'CreationModal',
|
||||
})
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
@@ -253,11 +269,20 @@ const openFile = async () => {
|
||||
|
||||
modal.value.hide()
|
||||
await install_from_file(newProject).catch(handleError)
|
||||
|
||||
mixpanel.track('InstanceCreate', {
|
||||
source: 'CreationModalFileOpen',
|
||||
})
|
||||
}
|
||||
|
||||
listen('tauri://file-drop', async (event) => {
|
||||
modal.value.hide()
|
||||
await install_from_file(event.payload[0]).catch(handleError)
|
||||
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
|
||||
await install_from_file(event.payload[0]).catch(handleError)
|
||||
mixpanel.track('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -12,16 +12,28 @@ import {
|
||||
CheckIcon,
|
||||
} from 'omorphia'
|
||||
import { computed, ref } from 'vue'
|
||||
import { add_project_from_version as installMod, check_installed, list } from '@/helpers/profile'
|
||||
import {
|
||||
add_project_from_version as installMod,
|
||||
check_installed,
|
||||
get,
|
||||
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 { create } from '@/helpers/profile'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const versions = ref([])
|
||||
const project = ref('')
|
||||
const projectTitle = ref('')
|
||||
const projectType = ref('')
|
||||
|
||||
const installModal = ref(null)
|
||||
const searchFilter = ref('')
|
||||
const showCreation = ref(false)
|
||||
@@ -33,13 +45,18 @@ const gameVersion = ref(null)
|
||||
const creatingInstance = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: async (projectId, selectedVersions) => {
|
||||
show: async (projectId, selectedVersions, title, type) => {
|
||||
project.value = projectId
|
||||
versions.value = selectedVersions
|
||||
projectTitle.value = title
|
||||
projectType.value = type
|
||||
|
||||
installModal.value.show()
|
||||
searchFilter.value = ''
|
||||
|
||||
profiles.value = await getData()
|
||||
|
||||
mixpanel.track('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -59,6 +76,16 @@ async function install(instance) {
|
||||
|
||||
instance.installedMod = true
|
||||
instance.installing = false
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: instance.metadata.loader,
|
||||
game_version: instance.metadata.game_version,
|
||||
id: project.value,
|
||||
version_id: version.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
@@ -88,6 +115,7 @@ async function getData() {
|
||||
return filtered
|
||||
}
|
||||
|
||||
const alreadySentCreation = ref(false)
|
||||
const toggleCreation = () => {
|
||||
showCreation.value = !showCreation.value
|
||||
name.value = null
|
||||
@@ -95,6 +123,11 @@ const toggleCreation = () => {
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
if (!alreadySentCreation.value) {
|
||||
alreadySentCreation.value = false
|
||||
mixpanel.track('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
}
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
@@ -119,20 +152,46 @@ const reset_icon = () => {
|
||||
|
||||
const createInstance = async () => {
|
||||
creatingInstance.value = true
|
||||
|
||||
const loader =
|
||||
versions.value[0].loaders[0] !== 'forge' ||
|
||||
versions.value[0].loaders[0] !== 'fabric' ||
|
||||
versions.value[0].loaders[0] !== 'quilt'
|
||||
? versions.value[0].loaders[0]
|
||||
: 'vanilla'
|
||||
|
||||
const id = await create(
|
||||
name.value,
|
||||
versions.value[0].game_versions[0],
|
||||
versions.value[0].loaders[0] !== 'forge' ||
|
||||
versions.value[0].loaders[0] !== 'fabric' ||
|
||||
versions.value[0].loaders[0] !== 'quilt'
|
||||
? versions.value[0].loaders[0]
|
||||
: 'vanilla',
|
||||
loader,
|
||||
'latest',
|
||||
icon.value
|
||||
).catch(handleError)
|
||||
|
||||
await installMod(id, versions.value[0].id).catch(handleError)
|
||||
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value)
|
||||
|
||||
mixpanel.track('InstanceCreate', {
|
||||
profile_name: name.value,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
loader: loader,
|
||||
loader_version: 'latest',
|
||||
has_icon: !!icon.value,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: loader,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
id: project.value,
|
||||
version_id: versions.value[0].id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
installModal.value.hide()
|
||||
creatingInstance.value = false
|
||||
}
|
||||
@@ -143,7 +202,11 @@ const check_valid = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="installModal" header="Install project to instance">
|
||||
<Modal
|
||||
ref="installModal"
|
||||
header="Install project to instance"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
@@ -159,7 +222,15 @@ const check_valid = computed(() => {
|
||||
class="profile-button"
|
||||
@click="$router.push(`/instance/${encodeURIComponent(profile.path)}`)"
|
||||
>
|
||||
<Avatar :src="convertFileSrc(profile.metadata.icon)" class="profile-image" />
|
||||
<Avatar
|
||||
:src="
|
||||
!profile.metadata.icon ||
|
||||
(profile.metadata.icon && profile.metadata.icon.startsWith('http'))
|
||||
? profile.metadata.icon
|
||||
: convertFileSrc(profile.metadata?.icon)
|
||||
"
|
||||
class="profile-image"
|
||||
/>
|
||||
{{ profile.metadata.name }}
|
||||
</Button>
|
||||
<Button :disabled="profile.installedMod || profile.installing" @click="install(profile)">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal ref="detectJavaModal" header="Select java version">
|
||||
<Modal ref="detectJavaModal" header="Select java version" :noblur="!themeStore.advancedRendering">
|
||||
<div class="auto-detect-modal">
|
||||
<div class="table">
|
||||
<div class="table-row table-head">
|
||||
@@ -44,6 +44,10 @@ import {
|
||||
get_all_jre,
|
||||
} from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const chosenInstallOptions = ref([])
|
||||
const detectJavaModal = ref(null)
|
||||
@@ -75,6 +79,10 @@ const emit = defineEmits(['submit'])
|
||||
function setJavaInstall(javaInstall) {
|
||||
emit('submit', javaInstall)
|
||||
detectJavaModal.value.hide()
|
||||
mixpanel.track('JavaAutoDetect', {
|
||||
path: javaInstall.path,
|
||||
version: javaInstall.version,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -83,7 +91,7 @@ function setJavaInstall(javaInstall) {
|
||||
|
||||
.table {
|
||||
.table-row {
|
||||
grid-template-columns: 1fr 4fr 1.5fr;
|
||||
grid-template-columns: 1fr 4fr min-content;
|
||||
}
|
||||
|
||||
span {
|
||||
|
||||
@@ -52,6 +52,7 @@ import { get_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const props = defineProps({
|
||||
version: {
|
||||
@@ -85,6 +86,11 @@ async function testJava() {
|
||||
testingJava.value = false
|
||||
testingJavaSuccess.value = !!result
|
||||
|
||||
mixpanel.track('JavaTest', {
|
||||
path: props.modelValue ? props.modelValue.path : '',
|
||||
success: !!result,
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
testingJavaSuccess.value = null
|
||||
}, 2000)
|
||||
@@ -101,6 +107,11 @@ async function handleJavaFileInput() {
|
||||
version: props.version.toString(),
|
||||
architecture: 'x86',
|
||||
}
|
||||
|
||||
mixpanel.track('JavaManualSelect', {
|
||||
path: filePath,
|
||||
version: props.version,
|
||||
})
|
||||
}
|
||||
|
||||
emit('update:modelValue', result)
|
||||
|
||||
@@ -113,6 +113,7 @@ import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -140,6 +141,12 @@ const stop = async (path) => {
|
||||
try {
|
||||
const processes = await getProfileProcesses(path ?? selectedProfile.value.path)
|
||||
await killProfile(processes[0])
|
||||
|
||||
mixpanel.track('InstanceStop', {
|
||||
loader: currentProcesses.value[0].metadata.loader,
|
||||
game_version: currentProcesses.value[0].metadata.game_version,
|
||||
source: 'AppBar',
|
||||
})
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
@@ -50,12 +50,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="install">
|
||||
<Button
|
||||
:to="`/browse/${project.slug}`"
|
||||
color="primary"
|
||||
:disabled="installed || installing"
|
||||
@click.stop="install()"
|
||||
>
|
||||
<Button color="primary" :disabled="installed || installing" @click.stop="install()">
|
||||
<DownloadIcon v-if="!installed" />
|
||||
<CheckIcon v-else />
|
||||
{{ installing ? 'Installing' : installed ? 'Installed' : 'Install' }}
|
||||
@@ -87,6 +82,7 @@ import { install as packInstall } from '@/helpers/pack.js'
|
||||
import { installVersionDependencies } from '@/helpers/utils.js'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const props = defineProps({
|
||||
@@ -159,6 +155,13 @@ async function install() {
|
||||
props.project.title,
|
||||
props.project.icon_url
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: props.project.project_id,
|
||||
version_id: queuedVersionData.id,
|
||||
title: props.project.title,
|
||||
source: 'SearchCard',
|
||||
})
|
||||
} else {
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
@@ -174,16 +177,33 @@ async function install() {
|
||||
props.instance,
|
||||
props.project.title,
|
||||
versions,
|
||||
() => (installed.value = true)
|
||||
() => (installed.value = true),
|
||||
props.project.project_id,
|
||||
props.project.project_type
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
} else {
|
||||
await installMod(props.instance.path, queuedVersionData.id).catch(handleError)
|
||||
installVersionDependencies(props.instance, queuedVersionData)
|
||||
await installVersionDependencies(props.instance, queuedVersionData)
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
id: props.project.project_id,
|
||||
project_type: props.project.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: props.project.title,
|
||||
source: 'SearchCard',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
props.modInstallModal.show(props.project.project_id, versions)
|
||||
props.modInstallModal.show(
|
||||
props.project.project_id,
|
||||
versions,
|
||||
props.project.title,
|
||||
props.project.project_type
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3,7 +3,9 @@ import { handleError } from '@/store/state.js'
|
||||
|
||||
export const useFetch = async (url, item) => {
|
||||
try {
|
||||
return await ofetch(url)
|
||||
return await ofetch(url, {
|
||||
headers: { 'User-Agent': 'modrinth/theseus (support@modrinth.com)' },
|
||||
})
|
||||
} catch (err) {
|
||||
handleError({ message: `Error fetching ${item}` })
|
||||
console.error(err)
|
||||
|
||||
@@ -27,13 +27,8 @@ export async function get_logs_by_datetime(profileUuid, datetimeString) {
|
||||
}
|
||||
|
||||
/// Get a profile's stdout only by datetime_string (the folder name, when the log was created)
|
||||
export async function get_stdout_by_datetime(profileUuid, datetimeString) {
|
||||
return await invoke('logs_get_stdout_by_datetime', { profileUuid, datetimeString })
|
||||
}
|
||||
|
||||
/// Get a profile's stderr only by datetime_string (the folder name, when the log was created)
|
||||
export async function get_stderr_by_datetime(profileUuid, datetimeString) {
|
||||
return await invoke('logs_get_stderr_by_datetime', { profileUuid, datetimeString })
|
||||
export async function get_output_by_datetime(profileUuid, datetimeString) {
|
||||
return await invoke('logs_get_output_by_datetime', { profileUuid, datetimeString })
|
||||
}
|
||||
|
||||
/// Delete a profile's log by datetime_string (the folder name, when the log was created)
|
||||
|
||||
@@ -47,16 +47,10 @@ export async function get_all_running_profiles() {
|
||||
return await invoke('process_get_all_running_profiles')
|
||||
}
|
||||
|
||||
/// Gets process stderr by UUID
|
||||
/// Returns String
|
||||
export async function get_stderr_by_uuid(uuid) {
|
||||
return await invoke('process_get_stderr_by_uuid', { uuid })
|
||||
}
|
||||
|
||||
/// Gets process stdout by UUID
|
||||
/// Returns String
|
||||
export async function get_stdout_by_uuid(uuid) {
|
||||
return await invoke('process_get_stdout_by_uuid', { uuid })
|
||||
export async function get_output_by_uuid(uuid) {
|
||||
return await invoke('process_get_output_by_uuid', { uuid })
|
||||
}
|
||||
|
||||
/// Kills a process by UUID
|
||||
|
||||
@@ -3,6 +3,10 @@ import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function isDev() {
|
||||
return await invoke('is_dev')
|
||||
}
|
||||
|
||||
export async function showInFolder(path) {
|
||||
return await invoke('show_in_folder', { path })
|
||||
}
|
||||
@@ -23,6 +27,8 @@ export const releaseColor = (releaseType) => {
|
||||
export const installVersionDependencies = async (profile, version) => {
|
||||
for (const dep of version.dependencies) {
|
||||
if (dep.dependency_type !== 'required') continue
|
||||
// disallow fabric api install on quilt
|
||||
if (dep.project_id === 'P7dR8mSH' && profile.metadata.loader === 'quilt') continue
|
||||
if (dep.version_id) {
|
||||
if (
|
||||
dep.project_id &&
|
||||
@@ -45,7 +51,9 @@ export const installVersionDependencies = async (profile, version) => {
|
||||
v.game_versions.includes(profile.metadata.game_version) &&
|
||||
v.loaders.includes(profile.metadata.loader)
|
||||
)
|
||||
await installMod(profile.path, latest.id).catch(handleError)
|
||||
if (latest) {
|
||||
await installMod(profile.path, latest.id).catch(handleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -476,7 +476,7 @@ const showLoaders = computed(
|
||||
<div class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<div v-if="instanceContext" class="small-instance">
|
||||
<div class="instance">
|
||||
<router-link :to="`/instance/${encodeURIComponent(instanceContext.path)}`" class="instance">
|
||||
<Avatar
|
||||
:src="
|
||||
!instanceContext.metadata.icon ||
|
||||
@@ -497,18 +497,20 @@ const showLoaders = computed(
|
||||
{{ instanceContext.metadata.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
<Checkbox
|
||||
v-model="ignoreInstanceGameVersions"
|
||||
label="Override game versions"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="onSearchChangeToTop(1)"
|
||||
@click.prevent.stop
|
||||
/>
|
||||
<Checkbox
|
||||
v-model="ignoreInstanceLoaders"
|
||||
label="Override loaders"
|
||||
class="filter-checkbox"
|
||||
@update:model-value="onSearchChangeToTop(1)"
|
||||
@click.prevent.stop
|
||||
/>
|
||||
</div>
|
||||
<Card class="search-panel-card">
|
||||
|
||||
@@ -5,6 +5,7 @@ import { handleError, useTheming } from '@/store/state'
|
||||
import { get, set } from '@/helpers/settings'
|
||||
import { get_max_memory } from '@/helpers/jre'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -26,6 +27,12 @@ watch(
|
||||
async (oldSettings, newSettings) => {
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
|
||||
if (setSettings.opt_out_analytics) {
|
||||
mixpanel.opt_out_tracking()
|
||||
} else {
|
||||
mixpanel.opt_in_tracking()
|
||||
}
|
||||
|
||||
if (setSettings.java_globals.JAVA_8?.path === '') {
|
||||
setSettings.java_globals.JAVA_8 = undefined
|
||||
}
|
||||
@@ -116,6 +123,26 @@ watch(
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="advanced-rendering">
|
||||
<span class="label__title">Advanced rendering</span>
|
||||
<span class="label__description">
|
||||
Enables advanced rendering such as blur effects that may cause performance issues
|
||||
without hardware-accelerated rendering.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle
|
||||
id="advanced-rendering"
|
||||
:model-value="themeStore.advancedRendering"
|
||||
:checked="themeStore.advancedRendering"
|
||||
@update:model-value="
|
||||
(e) => {
|
||||
themeStore.advancedRendering = e
|
||||
settings.advanced_rendering = themeStore.advancedRendering
|
||||
}
|
||||
"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
@@ -158,6 +185,23 @@ watch(
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
<span class="label__title size-card-header">Privacy</span>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="adjacent-input">
|
||||
<label for="opt-out-analytics">
|
||||
<span class="label__title">Disable analytics</span>
|
||||
<span class="label__description">
|
||||
Modrinth collects anonymized analytics and usage data to improve our user experience and
|
||||
customize your experience. Opting out will disable this data collection.
|
||||
</span>
|
||||
</label>
|
||||
<Toggle id="opt-out-analytics" v-model="settings.opt_out_analytics" />
|
||||
</div>
|
||||
</Card>
|
||||
<Card>
|
||||
<div class="label">
|
||||
<h3>
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
v-else-if="playing === true"
|
||||
color="danger"
|
||||
class="instance-button"
|
||||
@click="stopInstance"
|
||||
@click="stopInstance('InstancePage')"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
@@ -35,7 +35,7 @@
|
||||
v-else-if="playing === false && loading === false"
|
||||
color="primary"
|
||||
class="instance-button"
|
||||
@click="startInstance"
|
||||
@click="startInstance('InstancePage')"
|
||||
@mouseover="checkProcess"
|
||||
>
|
||||
<PlayIcon />
|
||||
@@ -93,8 +93,6 @@
|
||||
<template #open_folder> <ClipboardCopyIcon /> Open Folder </template>
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy Link </template>
|
||||
<template #open_link> <ClipboardCopyIcon /> Open In Modrinth <ExternalIcon /> </template>
|
||||
<template #repair> <HammerIcon /> Repair </template>
|
||||
<template #delete> <TrashIcon /> Delete </template>
|
||||
</ContextMenu>
|
||||
</template>
|
||||
<script setup>
|
||||
@@ -109,14 +107,12 @@ import {
|
||||
PlayIcon,
|
||||
StopCircleIcon,
|
||||
EditIcon,
|
||||
HammerIcon,
|
||||
TrashIcon,
|
||||
FolderOpenIcon,
|
||||
ClipboardCopyIcon,
|
||||
PlusIcon,
|
||||
ExternalIcon,
|
||||
} from 'omorphia'
|
||||
import { get, install, remove, run } from '@/helpers/profile'
|
||||
import { get, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
@@ -129,6 +125,7 @@ import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
||||
import { showInFolder } from '@/helpers/utils.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
@@ -151,11 +148,17 @@ const playing = ref(false)
|
||||
const loading = ref(false)
|
||||
const options = ref(null)
|
||||
|
||||
const startInstance = async () => {
|
||||
const startInstance = async (context) => {
|
||||
loading.value = true
|
||||
uuid.value = await run(route.params.id).catch(handleError)
|
||||
loading.value = false
|
||||
playing.value = true
|
||||
|
||||
mixpanel.track('InstanceStart', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
@@ -171,25 +174,21 @@ const checkProcess = async () => {
|
||||
|
||||
await checkProcess()
|
||||
|
||||
const stopInstance = async () => {
|
||||
const stopInstance = async (context) => {
|
||||
playing.value = false
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(instance.value.path).catch(handleError)
|
||||
uuid.value = uuids[0] // populate Uuid to listen for in the process_listener
|
||||
uuids.forEach(async (u) => await kill_by_uuid(u).catch(handleError))
|
||||
} else await kill_by_uuid(uuid.value).catch(handleError)
|
||||
|
||||
mixpanel.track('InstanceStop', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
}
|
||||
})
|
||||
|
||||
const unlistenProcesses = await process_listener((e) => {
|
||||
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
||||
})
|
||||
|
||||
const handleRightClick = (event) => {
|
||||
const baseOptions = [
|
||||
{ name: 'add_content' },
|
||||
@@ -197,15 +196,6 @@ const handleRightClick = (event) => {
|
||||
{ name: 'edit' },
|
||||
{ name: 'open_folder' },
|
||||
{ name: 'copy_path' },
|
||||
{ type: 'divider' },
|
||||
{
|
||||
name: 'repair',
|
||||
color: 'contrast',
|
||||
},
|
||||
{
|
||||
name: 'delete',
|
||||
color: 'danger',
|
||||
},
|
||||
]
|
||||
|
||||
options.value.showMenu(
|
||||
@@ -233,10 +223,10 @@ const handleOptionsClick = async (args) => {
|
||||
console.log(args)
|
||||
switch (args.option) {
|
||||
case 'play':
|
||||
await startInstance()
|
||||
await startInstance('InstancePageContextMenu')
|
||||
break
|
||||
case 'stop':
|
||||
await stopInstance()
|
||||
await stopInstance('InstancePageContextMenu')
|
||||
break
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
@@ -249,33 +239,25 @@ const handleOptionsClick = async (args) => {
|
||||
path: `/instance/${encodeURIComponent(route.params.id)}/options`,
|
||||
})
|
||||
break
|
||||
case 'repair':
|
||||
await install(instance.value.path).catch(handleError)
|
||||
break
|
||||
case 'delete':
|
||||
await remove(instance.value.path).catch(handleError)
|
||||
break
|
||||
case 'open_folder':
|
||||
await showInFolder(instance.value.path)
|
||||
break
|
||||
case 'copy_path':
|
||||
await navigator.clipboard.writeText(instance.value.path)
|
||||
break
|
||||
case 'open_link':
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: args.item.link,
|
||||
},
|
||||
})
|
||||
break
|
||||
case 'copy_link':
|
||||
await navigator.clipboard.writeText(args.item.link)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
const unlistenProfiles = await profile_listener(async (event) => {
|
||||
if (event.path === route.params.id) {
|
||||
instance.value = await get(route.params.id).catch(handleError)
|
||||
}
|
||||
})
|
||||
|
||||
const unlistenProcesses = await process_listener((e) => {
|
||||
if (e.event === 'finished' && uuid.value === e.uuid) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
unlistenProcesses()
|
||||
unlistenProfiles()
|
||||
|
||||
@@ -51,11 +51,11 @@ import {
|
||||
SendIcon,
|
||||
TrashIcon,
|
||||
} from 'omorphia'
|
||||
import { delete_logs_by_datetime, get_logs, get_stdout_by_datetime } from '@/helpers/logs.js'
|
||||
import { delete_logs_by_datetime, get_logs, get_output_by_datetime } from '@/helpers/logs.js'
|
||||
import { nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import calendar from 'dayjs/plugin/calendar'
|
||||
import { get_stdout_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import { get_output_by_uuid, get_uuids_by_profile_path } from '@/helpers/process.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { process_listener } from '@/helpers/events.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
@@ -78,7 +78,7 @@ async function getLiveLog() {
|
||||
if (uuids.length === 0) {
|
||||
returnValue = 'No live game detected. \nStart your game to proceed'
|
||||
} else {
|
||||
returnValue = await get_stdout_by_uuid(uuids[0]).catch(handleError)
|
||||
returnValue = await get_output_by_uuid(uuids[0]).catch(handleError)
|
||||
}
|
||||
|
||||
return { name: 'Live Log', stdout: returnValue, live: true }
|
||||
@@ -120,13 +120,17 @@ watch(selectedLogIndex, async (newIndex) => {
|
||||
|
||||
if (newIndex !== 0) {
|
||||
logs.value[newIndex].stdout = 'Loading...'
|
||||
logs.value[newIndex].stdout = await get_stdout_by_datetime(
|
||||
logs.value[newIndex].stdout = await get_output_by_datetime(
|
||||
props.instance.uuid,
|
||||
logs.value[newIndex].datetime_string
|
||||
).catch(handleError)
|
||||
}
|
||||
})
|
||||
|
||||
if (logs.value.length >= 1) {
|
||||
selectedLogIndex.value = 1
|
||||
}
|
||||
|
||||
const deleteLog = async () => {
|
||||
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
||||
let deleteIndex = selectedLogIndex.value
|
||||
@@ -165,9 +169,13 @@ interval.value = setInterval(async () => {
|
||||
}, 250)
|
||||
|
||||
const unlistenProcesses = await process_listener(async (e) => {
|
||||
if (e.event === 'launched') {
|
||||
selectedLogIndex.value = 0
|
||||
}
|
||||
if (e.event === 'finished') {
|
||||
userScrolled.value = false
|
||||
await setLogs()
|
||||
selectedLogIndex.value = 1
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
>
|
||||
<template #search>
|
||||
<SearchIcon />
|
||||
<span class="no-wrap"> Search addons </span>
|
||||
<span class="no-wrap"> Add content </span>
|
||||
</template>
|
||||
<template #from_file>
|
||||
<FolderOpenIcon />
|
||||
@@ -216,6 +216,7 @@ import {
|
||||
update_project,
|
||||
} from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
@@ -255,6 +256,7 @@ const initProjects = (initInstance) => {
|
||||
updateVersion: project.metadata.update_version,
|
||||
outdated: !!project.metadata.update_version,
|
||||
project_type: project.metadata.project.project_type,
|
||||
id: project.metadata.project.id,
|
||||
})
|
||||
} else if (project.metadata.type === 'inferred') {
|
||||
projects.value.push({
|
||||
@@ -286,6 +288,11 @@ const initProjects = (initInstance) => {
|
||||
|
||||
initProjects(props.instance)
|
||||
|
||||
watch(
|
||||
() => props.instance.projects,
|
||||
() => initProjects(props.instance)
|
||||
)
|
||||
|
||||
const searchFilter = ref('')
|
||||
const selectAll = ref(false)
|
||||
const sortFilter = ref('')
|
||||
@@ -385,6 +392,13 @@ async function updateAll() {
|
||||
for (const project of setProjects) {
|
||||
projects.value[project].updating = false
|
||||
}
|
||||
|
||||
mixpanel.track('InstanceUpdateAll', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
count: setProjects.length,
|
||||
selected: selected.value.length > 1,
|
||||
})
|
||||
}
|
||||
|
||||
async function updateProject(mod) {
|
||||
@@ -395,30 +409,54 @@ async function updateProject(mod) {
|
||||
mod.outdated = false
|
||||
mod.version = mod.updateVersion.version_number
|
||||
mod.updateVersion = null
|
||||
|
||||
mixpanel.track('InstanceProjectUpdate', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
})
|
||||
}
|
||||
|
||||
async function toggleDisableMod(mod) {
|
||||
mod.path = await toggle_disable_project(props.instance.path, mod.path).catch(handleError)
|
||||
mod.disabled = !mod.disabled
|
||||
|
||||
mixpanel.track('InstanceProjectDisable', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
disabled: mod.disabled,
|
||||
})
|
||||
}
|
||||
|
||||
async function removeMod(mod) {
|
||||
await remove_project(props.instance.path, mod.path).catch(handleError)
|
||||
projects.value = projects.value.filter((x) => mod.path !== x.path)
|
||||
|
||||
mixpanel.track('InstanceProjectRemove', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
id: mod.id,
|
||||
name: mod.name,
|
||||
project_type: mod.project_type,
|
||||
})
|
||||
}
|
||||
|
||||
const handleContentOptionClick = async (args) => {
|
||||
if (args.option === 'search') {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
} else if (args.option === 'from_file') {
|
||||
const newProject = await open({ multiple: true })
|
||||
console.log(newProject)
|
||||
if (!newProject) return
|
||||
|
||||
for (const project of newProject) {
|
||||
console.log(project)
|
||||
await add_project_from_path(props.instance.path, project, 'mod').catch(handleError)
|
||||
initProjects(await get(props.instance.path).catch(handleError))
|
||||
}
|
||||
|
||||
@@ -5,9 +5,14 @@
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="removeProfile"
|
||||
/>
|
||||
<Modal ref="changeVersionsModal" header="Change instance versions">
|
||||
<Modal
|
||||
ref="changeVersionsModal"
|
||||
header="Change instance versions"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
@@ -334,6 +339,8 @@ import { open } from '@tauri-apps/api/dialog'
|
||||
import { get_fabric_versions, get_forge_versions, get_quilt_versions } from '@/helpers/metadata.js'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -344,6 +351,8 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const title = ref(props.instance.metadata.name)
|
||||
const icon = ref(props.instance.metadata.icon)
|
||||
const groups = ref(props.instance.metadata.groups)
|
||||
@@ -360,6 +369,7 @@ const availableGroups = ref([
|
||||
async function resetIcon() {
|
||||
icon.value = null
|
||||
await edit_icon(props.instance.path, null).catch(handleError)
|
||||
mixpanel.track('InstanceRemoveIcon')
|
||||
}
|
||||
|
||||
async function setIcon() {
|
||||
@@ -377,6 +387,8 @@ async function setIcon() {
|
||||
|
||||
icon.value = value
|
||||
await edit_icon(props.instance.path, icon.value).catch(handleError)
|
||||
|
||||
mixpanel.track('InstanceSetIcon')
|
||||
}
|
||||
|
||||
const globalSettings = await get().catch(handleError)
|
||||
@@ -428,6 +440,7 @@ watch(
|
||||
metadata: {
|
||||
name: title.value.trim().substring(0, 16) ?? 'Instance',
|
||||
groups: groups.value.map((x) => x.trim().substring(0, 32)).filter((x) => x.length > 0),
|
||||
loader_version: props.instance.metadata.loader_version,
|
||||
},
|
||||
java: {},
|
||||
}
|
||||
@@ -481,6 +494,11 @@ async function repairProfile() {
|
||||
repairing.value = true
|
||||
await install(props.instance.path).catch(handleError)
|
||||
repairing.value = false
|
||||
|
||||
mixpanel.track('InstanceRepair', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
})
|
||||
}
|
||||
|
||||
const removing = ref(false)
|
||||
@@ -489,6 +507,11 @@ async function removeProfile() {
|
||||
await remove(props.instance.path).catch(handleError)
|
||||
removing.value = false
|
||||
|
||||
mixpanel.track('InstanceRemove', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
})
|
||||
|
||||
await router.push({ path: '/' })
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ export default {
|
||||
|
||||
<style scoped lang="scss">
|
||||
.markdown-body {
|
||||
:deep(table) {
|
||||
width: auto;
|
||||
}
|
||||
|
||||
:deep(hr),
|
||||
:deep(h1),
|
||||
:deep(h2) {
|
||||
|
||||
@@ -93,6 +93,7 @@ import {
|
||||
Button,
|
||||
} from 'omorphia'
|
||||
import { ref } from 'vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
@@ -111,6 +112,10 @@ const nextImage = () => {
|
||||
expandedGalleryIndex.value = 0
|
||||
}
|
||||
expandedGalleryItem.value = props.project.gallery[expandedGalleryIndex.value]
|
||||
mixpanel.track('GalleryImageNext', {
|
||||
project_id: props.project.id,
|
||||
url: expandedGalleryItem.value.url,
|
||||
})
|
||||
}
|
||||
|
||||
const previousImage = () => {
|
||||
@@ -119,12 +124,21 @@ const previousImage = () => {
|
||||
expandedGalleryIndex.value = props.project.gallery.length - 1
|
||||
}
|
||||
expandedGalleryItem.value = props.project.gallery[expandedGalleryIndex.value]
|
||||
mixpanel.track('GalleryImagePrevious', {
|
||||
project_id: props.project.id,
|
||||
url: expandedGalleryItem.value,
|
||||
})
|
||||
}
|
||||
|
||||
const expandImage = (item, index) => {
|
||||
expandedGalleryItem.value = item
|
||||
expandedGalleryIndex.value = index
|
||||
zoomedIn.value = false
|
||||
|
||||
mixpanel.track('GalleryImageExpand', {
|
||||
project_id: props.project.id,
|
||||
url: item.url,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="root-container">
|
||||
<div v-if="data" class="project-sidebar">
|
||||
<div v-if="instance" class="small-instance">
|
||||
<div class="instance">
|
||||
<router-link class="instance" :to="`/instance/${encodeURIComponent(instance.path)}`">
|
||||
<Avatar
|
||||
:src="
|
||||
!instance.metadata.icon ||
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ instance.metadata.game_version }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
</div>
|
||||
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
||||
<Avatar size="lg" :src="data.icon_url" />
|
||||
@@ -32,17 +32,11 @@
|
||||
</div>
|
||||
<Categories
|
||||
class="tags"
|
||||
type=""
|
||||
:categories="[
|
||||
...categories.filter(
|
||||
:categories="
|
||||
categories.filter(
|
||||
(cat) => data.categories.includes(cat.name) && cat.project_type === 'mod'
|
||||
),
|
||||
...loaders.filter(
|
||||
(loader) =>
|
||||
data.categories.includes(loader.name) &&
|
||||
loader.supported_project_types?.includes('modpack')
|
||||
),
|
||||
]"
|
||||
)
|
||||
"
|
||||
>
|
||||
<EnvironmentIndicator
|
||||
:client-side="data.client_side"
|
||||
@@ -258,7 +252,7 @@ import {
|
||||
KoFiIcon,
|
||||
OpenCollectiveIcon,
|
||||
} from '@/assets/external'
|
||||
import { get_categories, get_loaders } from '@/helpers/tags'
|
||||
import { get_categories } from '@/helpers/tags'
|
||||
import { install as packInstall } from '@/helpers/pack'
|
||||
import {
|
||||
list,
|
||||
@@ -268,7 +262,7 @@ import {
|
||||
} from '@/helpers/profile'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
@@ -279,9 +273,9 @@ import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
const confirmModal = ref(null)
|
||||
@@ -291,32 +285,46 @@ const incompatibilityWarning = ref(null)
|
||||
const options = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
const [data, versions, members, dependencies, categories, loaders, instance] = await Promise.all([
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project').then(shallowRef),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project').then(
|
||||
shallowRef
|
||||
),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`, 'project').then(
|
||||
shallowRef
|
||||
),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project').then(
|
||||
shallowRef
|
||||
),
|
||||
get_loaders().then(ref).catch(handleError),
|
||||
get_categories().then(ref).catch(handleError),
|
||||
route.query.i ? getInstance(route.query.i, true).then(ref) : Promise.resolve().then(ref),
|
||||
])
|
||||
const data = shallowRef(null)
|
||||
const versions = shallowRef([])
|
||||
const members = shallowRef([])
|
||||
const dependencies = shallowRef([])
|
||||
const categories = shallowRef([])
|
||||
const instance = ref(null)
|
||||
|
||||
const installed = ref(
|
||||
instance.value && (await check_installed(instance.value.path, data.value.id).catch(handleError))
|
||||
)
|
||||
const installed = ref(false)
|
||||
|
||||
breadcrumbs.setName('Project', data.value.title)
|
||||
async function fetchProjectData() {
|
||||
;[
|
||||
data.value,
|
||||
versions.value,
|
||||
members.value,
|
||||
dependencies.value,
|
||||
categories.value,
|
||||
instance.value,
|
||||
] = await Promise.all([
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/version`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/members`, 'project'),
|
||||
useFetch(`https://api.modrinth.com/v2/project/${route.params.id}/dependencies`, 'project'),
|
||||
get_categories().catch(handleError),
|
||||
route.query.i ? getInstance(route.query.i, true).catch(handleError) : Promise.resolve(),
|
||||
])
|
||||
|
||||
installed.value =
|
||||
instance.value?.path &&
|
||||
(await check_installed(instance.value.path, data.value.id).catch(handleError))
|
||||
breadcrumbs.setName('Project', data.value.title)
|
||||
}
|
||||
|
||||
await fetchProjectData()
|
||||
|
||||
watch(
|
||||
() => route.params.id,
|
||||
() => {
|
||||
if (route.params.id) router.go()
|
||||
async () => {
|
||||
if (route.params.id && route.path.startsWith('/project')) {
|
||||
await fetchProjectData()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -356,6 +364,13 @@ async function install(version) {
|
||||
data.value.title,
|
||||
data.value.icon_url
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: data.value.id,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
confirmModal.value.show(
|
||||
data.value.id,
|
||||
@@ -381,14 +396,26 @@ async function install(version) {
|
||||
instance.value,
|
||||
data.value.title,
|
||||
versions.value,
|
||||
markInstalled
|
||||
markInstalled,
|
||||
data.value.id,
|
||||
data.value.project_type
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
} else {
|
||||
queuedVersionData = selectedVersion
|
||||
await installMod(instance.value.path, selectedVersion.id).catch(handleError)
|
||||
installVersionDependencies(instance.value, queuedVersionData)
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
id: data.value.id,
|
||||
project_type: data.value.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
const gameVersion = instance.value.metadata.game_version
|
||||
@@ -403,12 +430,24 @@ async function install(version) {
|
||||
if (compatible) {
|
||||
await installMod(instance.value.path, queuedVersionData.id).catch(handleError)
|
||||
await installVersionDependencies(instance.value, queuedVersionData)
|
||||
|
||||
mixpanel.track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
id: data.value.id,
|
||||
project_type: data.value.project_type,
|
||||
version_id: queuedVersionData.id,
|
||||
title: data.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
incompatibilityWarning.value.show(
|
||||
instance.value,
|
||||
data.value.title,
|
||||
[queuedVersionData],
|
||||
markInstalled
|
||||
markInstalled,
|
||||
data.value.id,
|
||||
data.value.project_type
|
||||
)
|
||||
installing.value = false
|
||||
return
|
||||
@@ -416,13 +455,12 @@ async function install(version) {
|
||||
}
|
||||
installed.value = true
|
||||
} else {
|
||||
if (version) {
|
||||
modInstallModal.value.show(data.value.id, [
|
||||
versions.value.find((v) => v.id === queuedVersionData.id),
|
||||
])
|
||||
} else {
|
||||
modInstallModal.value.show(data.value.id, versions.value)
|
||||
}
|
||||
modInstallModal.value.show(
|
||||
data.value.id,
|
||||
version ? [versions.value.find((v) => v.id === queuedVersionData.id)] : versions.value,
|
||||
data.value.title,
|
||||
data.value.project_type
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,7 +660,7 @@ const handleOptionsClick = (args) => {
|
||||
.instance {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
margin-bottom: 0;
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
<template>
|
||||
<div>
|
||||
<Card>
|
||||
<Breadcrumbs
|
||||
:current-title="version.name"
|
||||
:link-stack="[
|
||||
{
|
||||
href: `/project/${route.params.id}/versions`,
|
||||
label: 'Versions',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
<div class="version-title">
|
||||
<h2>{{ version.name }}</h2>
|
||||
<span v-if="version.featured">Auto-Featured</span>
|
||||
</div>
|
||||
<div class="button-group">
|
||||
<Button color="primary" :action="() => install(version.id)" :disabled="installed">
|
||||
@@ -11,10 +19,6 @@
|
||||
<CheckIcon v-else />
|
||||
{{ installed ? 'Installed' : 'Install' }}
|
||||
</Button>
|
||||
<Button :link="`/project/${route.params.id}/versions`">
|
||||
<LeftArrowIcon />
|
||||
Back to list
|
||||
</Button>
|
||||
<Button>
|
||||
<ReportIcon />
|
||||
Report
|
||||
@@ -65,10 +69,15 @@
|
||||
</Button>
|
||||
</Card>
|
||||
</Card>
|
||||
<Card v-if="displayDependencies[0]">
|
||||
<Card v-if="displayDependencies.length > 0">
|
||||
<h2>Dependencies</h2>
|
||||
<div v-for="dependency in displayDependencies" :key="dependency.title">
|
||||
<router-link v-if="dependency.link" class="btn dependency" :to="dependency.link">
|
||||
<router-link
|
||||
v-if="dependency.link"
|
||||
class="btn dependency"
|
||||
:to="dependency.link"
|
||||
@click="testTest"
|
||||
>
|
||||
<Avatar size="sm" :src="dependency.icon" />
|
||||
<div>
|
||||
<span class="title"> {{ dependency.title }} </span> <br />
|
||||
@@ -173,17 +182,17 @@ import {
|
||||
DownloadIcon,
|
||||
FileIcon,
|
||||
Avatar,
|
||||
LeftArrowIcon,
|
||||
ReportIcon,
|
||||
Badge,
|
||||
ExternalIcon,
|
||||
CopyCode,
|
||||
CheckIcon,
|
||||
Breadcrumbs,
|
||||
formatBytes,
|
||||
renderString,
|
||||
} from 'omorphia'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
import { ref, defineProps } from 'vue'
|
||||
import { ref, defineProps, watch, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
|
||||
@@ -221,9 +230,21 @@ const props = defineProps({
|
||||
const version = ref(props.versions.find((version) => version.id === route.params.version))
|
||||
breadcrumbs.setName('Version', version.value.name)
|
||||
|
||||
const author = ref(props.members.find((member) => member.user.id === version.value.author_id))
|
||||
watch(
|
||||
() => props.versions,
|
||||
async () => {
|
||||
if (route.params.version) {
|
||||
version.value = props.versions.find((version) => version.id === route.params.version)
|
||||
breadcrumbs.setName('Version', version.value.name)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
const displayDependencies = ref(
|
||||
const author = computed(() =>
|
||||
props.members.find((member) => member.user.id === version.value.author_id)
|
||||
)
|
||||
|
||||
const displayDependencies = computed(() =>
|
||||
version.value.dependencies.map((dependency) => {
|
||||
const version = props.dependencies.versions.find((obj) => obj.id === dependency.version_id)
|
||||
if (version) {
|
||||
@@ -236,13 +257,25 @@ const displayDependencies = ref(
|
||||
subtitle: `Version ${version.version_number} is ${dependency.dependency_type}`,
|
||||
link: `/project/${project.slug}/version/${version.id}`,
|
||||
}
|
||||
} else
|
||||
return {
|
||||
icon: null,
|
||||
title: dependency.file_name,
|
||||
subtitle: `Added via overrides`,
|
||||
link: null,
|
||||
} else {
|
||||
const project = props.dependencies.projects.find((obj) => obj.id === dependency.project_id)
|
||||
|
||||
if (project) {
|
||||
return {
|
||||
icon: project?.icon_url,
|
||||
title: project?.title || project?.name,
|
||||
subtitle: `${dependency.dependency_type}`,
|
||||
link: `/project/${project.slug}`,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
icon: null,
|
||||
title: dependency.file_name,
|
||||
subtitle: `Added via overrides`,
|
||||
link: null,
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -4,6 +4,7 @@ export const useTheming = defineStore('themeStore', {
|
||||
state: () => ({
|
||||
themeOptions: ['dark'],
|
||||
collapsedNavigation: false,
|
||||
advancedRendering: true,
|
||||
selectedTheme: 'dark',
|
||||
darkTheme: true,
|
||||
}),
|
||||
|
||||
Reference in New Issue
Block a user