You've already forked AstralRinth
forked from didirus/AstralRinth
Migrate to SQLite for Internal Launcher Data (#1300)
* initial migration * barebones profiles * Finish profiles * Add back file watcher * UI support progress * Finish most of cache * Fix options page * Fix forge, finish modrinth auth * Accounts, process cache * Run SQLX prepare * Finish * Run lint + actions * Fix version to be compat with windows * fix lint * actually fix lint * actually fix lint again
This commit is contained in:
@@ -127,46 +127,46 @@ const sortBy = ref('Name')
|
||||
|
||||
const filteredResults = computed(() => {
|
||||
let instances = props.instances.filter((instance) => {
|
||||
return instance.metadata.name.toLowerCase().includes(search.value.toLowerCase())
|
||||
return instance.name.toLowerCase().includes(search.value.toLowerCase())
|
||||
})
|
||||
|
||||
if (sortBy.value === 'Name') {
|
||||
instances.sort((a, b) => {
|
||||
return a.metadata.name.localeCompare(b.metadata.name)
|
||||
return a.name.localeCompare(b.name)
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Game version') {
|
||||
instances.sort((a, b) => {
|
||||
return a.metadata.game_version.localeCompare(b.metadata.game_version)
|
||||
return a.game_version.localeCompare(b.game_version)
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Last played') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.last_played ?? 0).diff(dayjs(a.metadata.last_played ?? 0))
|
||||
return dayjs(b.last_played ?? 0).diff(dayjs(a.last_played ?? 0))
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Date created') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.date_created).diff(dayjs(a.metadata.date_created))
|
||||
return dayjs(b.date_created).diff(dayjs(a.date_created))
|
||||
})
|
||||
}
|
||||
|
||||
if (sortBy.value === 'Date modified') {
|
||||
instances.sort((a, b) => {
|
||||
return dayjs(b.metadata.date_modified).diff(dayjs(a.metadata.date_modified))
|
||||
return dayjs(b.date_modified).diff(dayjs(a.date_modified))
|
||||
})
|
||||
}
|
||||
|
||||
if (filters.value === 'Custom instances') {
|
||||
instances = instances.filter((instance) => {
|
||||
return !instance.metadata?.linked_data
|
||||
return !instance.linked_data
|
||||
})
|
||||
} else if (filters.value === 'Downloaded modpacks') {
|
||||
instances = instances.filter((instance) => {
|
||||
return instance.metadata?.linked_data
|
||||
return instance.linked_data
|
||||
})
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ const filteredResults = computed(() => {
|
||||
|
||||
if (group.value === 'Loader') {
|
||||
instances.forEach((instance) => {
|
||||
const loader = formatCategoryHeader(instance.metadata.loader)
|
||||
const loader = formatCategoryHeader(instance.loader)
|
||||
if (!instanceMap.has(loader)) {
|
||||
instanceMap.set(loader, [])
|
||||
}
|
||||
@@ -183,19 +183,19 @@ const filteredResults = computed(() => {
|
||||
})
|
||||
} else if (group.value === 'Game version') {
|
||||
instances.forEach((instance) => {
|
||||
if (!instanceMap.has(instance.metadata.game_version)) {
|
||||
instanceMap.set(instance.metadata.game_version, [])
|
||||
if (!instanceMap.has(instance.game_version)) {
|
||||
instanceMap.set(instance.game_version, [])
|
||||
}
|
||||
|
||||
instanceMap.get(instance.metadata.game_version).push(instance)
|
||||
instanceMap.get(instance.game_version).push(instance)
|
||||
})
|
||||
} else if (group.value === 'Category') {
|
||||
instances.forEach((instance) => {
|
||||
if (instance.metadata.groups.length === 0) {
|
||||
instance.metadata.groups.push('None')
|
||||
if (instance.groups.length === 0) {
|
||||
instance.groups.push('None')
|
||||
}
|
||||
|
||||
for (const category of instance.metadata.groups) {
|
||||
for (const category of instance.groups) {
|
||||
if (!instanceMap.has(category)) {
|
||||
instanceMap.set(category, [])
|
||||
}
|
||||
|
||||
@@ -17,22 +17,15 @@ import Instance from '@/components/ui/Instance.vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import ProjectCard from '@/components/ui/ProjectCard.vue'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
kill_by_uuid,
|
||||
} from '@/helpers/process.js'
|
||||
import { get_by_profile_path } from '@/helpers/process.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { duplicate, remove, run } from '@/helpers/profile.js'
|
||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { install as pack_install } from '@/helpers/pack.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
@@ -58,9 +51,7 @@ const modsRow = ref(null)
|
||||
const instanceOptions = ref(null)
|
||||
const instanceComponents = ref(null)
|
||||
const rows = ref(null)
|
||||
const confirmModal = ref(null)
|
||||
const deleteConfirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
@@ -90,23 +81,24 @@ const handleInstanceRightClick = async (event, passedInstance) => {
|
||||
},
|
||||
]
|
||||
|
||||
const running = await get_all_running_profile_paths().catch(handleError)
|
||||
const runningProcesses = await get_by_profile_path(passedInstance.path).catch(handleError)
|
||||
|
||||
const options = running.includes(passedInstance.path)
|
||||
? [
|
||||
{
|
||||
name: 'stop',
|
||||
color: 'danger',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'play',
|
||||
color: 'primary',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
const options =
|
||||
runningProcesses.length > 0
|
||||
? [
|
||||
{
|
||||
name: 'stop',
|
||||
color: 'danger',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
: [
|
||||
{
|
||||
name: 'play',
|
||||
color: 'primary',
|
||||
},
|
||||
...baseOptions,
|
||||
]
|
||||
|
||||
instanceOptions.value.showMenu(event, passedInstance, options)
|
||||
}
|
||||
@@ -132,22 +124,20 @@ const handleOptionsClick = async (args) => {
|
||||
case 'play':
|
||||
await run(args.item.path).catch(handleSevereError)
|
||||
mixpanel_track('InstanceStart', {
|
||||
loader: args.item.metadata.loader,
|
||||
game_version: args.item.metadata.game_version,
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
break
|
||||
case 'stop':
|
||||
for (const u of await get_uuids_by_profile_path(args.item.path).catch(handleError)) {
|
||||
await kill_by_uuid(u).catch(handleError)
|
||||
}
|
||||
await kill(args.item.path).catch(handleError)
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: args.item.metadata.loader,
|
||||
game_version: args.item.metadata.game_version,
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
break
|
||||
case 'add_content':
|
||||
await router.push({
|
||||
path: `/browse/${args.item.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
path: `/browse/${args.item.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: args.item.path },
|
||||
})
|
||||
break
|
||||
@@ -170,21 +160,8 @@ const handleOptionsClick = async (args) => {
|
||||
await navigator.clipboard.writeText(args.item.path)
|
||||
break
|
||||
case 'install': {
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${args.item.project_id}/version`,
|
||||
'project versions',
|
||||
)
|
||||
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu')
|
||||
|
||||
if (args.item.project_type === 'modpack') {
|
||||
await pack_install(
|
||||
args.item.project_id,
|
||||
versions[0].id,
|
||||
args.item.title,
|
||||
args.item.icon_url,
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(args.item.project_id, versions)
|
||||
}
|
||||
break
|
||||
}
|
||||
case 'open_link':
|
||||
@@ -243,7 +220,7 @@ onUnmounted(() => {
|
||||
<router-link :to="row.route">{{ row.label }}</router-link>
|
||||
<ChevronRightIcon />
|
||||
</div>
|
||||
<section v-if="row.instances[0].metadata" ref="modsRow" class="instances">
|
||||
<section v-if="row.instance" ref="modsRow" class="instances">
|
||||
<Instance
|
||||
v-for="instance in row.instances.slice(0, maxInstancesPerRow)"
|
||||
:key="(instance?.project_id || instance?.id) + instance.install_stage"
|
||||
@@ -258,8 +235,6 @@ onUnmounted(() => {
|
||||
ref="instanceComponents"
|
||||
class="item"
|
||||
:project="project"
|
||||
:confirm-modal="confirmModal"
|
||||
:mod-install-modal="modInstallModal"
|
||||
@contextmenu.prevent.stop="(event) => handleProjectClick(event, project)"
|
||||
/>
|
||||
</section>
|
||||
@@ -278,8 +253,6 @@ onUnmounted(() => {
|
||||
<template #open_link> <GlobeIcon /> Open in Modrinth <ExternalIcon /> </template>
|
||||
<template #copy_link> <ClipboardCopyIcon /> Copy link </template>
|
||||
</ContextMenu>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { Button, OverflowMenu } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { add_project_from_path, get } from '@/helpers/profile.js'
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -20,14 +20,13 @@ const handleAddContentFromFile = async () => {
|
||||
if (!newProject) return
|
||||
|
||||
for (const project of newProject) {
|
||||
await add_project_from_path(props.instance.path, project, 'mod').catch(handleError)
|
||||
await add_project_from_path(props.instance.path, project).catch(handleError)
|
||||
}
|
||||
props.instance.initProjects(await get(props.instance.path).catch(handleError))
|
||||
}
|
||||
|
||||
const handleSearchContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
|
||||
@@ -60,9 +60,9 @@ defineExpose({
|
||||
})
|
||||
|
||||
const isLinkedData = (item) => {
|
||||
if (item.instance != undefined && item.instance.metadata.linked_data) {
|
||||
if (item.instance != undefined && item.instance.linked_data) {
|
||||
return true
|
||||
} else if (item.metadata != undefined && item.metadata.linked_data) {
|
||||
} else if (item != undefined && item.linked_data) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -23,7 +23,7 @@ defineExpose({
|
||||
})
|
||||
|
||||
const exportModal = ref(null)
|
||||
const nameInput = ref(props.instance.metadata.name)
|
||||
const nameInput = ref(props.instance.name)
|
||||
const exportDescription = ref('')
|
||||
const versionInput = ref('1.0.0')
|
||||
const files = ref([])
|
||||
|
||||
@@ -1,22 +1,14 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, watch } from 'vue'
|
||||
import { onUnmounted, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { DownloadIcon, StopCircleIcon, PlayIcon } from '@modrinth/assets'
|
||||
import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
|
||||
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import InstallConfirmModal from '@/components/ui/InstallConfirmModal.vue'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { list, run } from '@/helpers/profile'
|
||||
import {
|
||||
get_all_running_profile_paths,
|
||||
get_uuids_by_profile_path,
|
||||
kill_by_uuid,
|
||||
} from '@/helpers/process'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
@@ -29,107 +21,31 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const modInstallModal = ref(null)
|
||||
const playing = ref(false)
|
||||
|
||||
const uuid = ref(null)
|
||||
const modLoading = ref(
|
||||
props.instance.install_stage ? props.instance.install_stage !== 'installed' : false,
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.instance,
|
||||
() => {
|
||||
modLoading.value = props.instance.install_stage
|
||||
? props.instance.install_stage !== 'installed'
|
||||
: false
|
||||
},
|
||||
)
|
||||
const modLoading = computed(() => props.instance.install_stage !== 'installed')
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const seeInstance = async () => {
|
||||
const instancePath = props.instance.metadata
|
||||
? `/instance/${encodeURIComponent(props.instance.path)}/`
|
||||
: `/project/${encodeURIComponent(props.instance.project_id)}/`
|
||||
|
||||
await router.push(instancePath)
|
||||
await router.push(`/instance/${encodeURIComponent(props.instance.path)}/`)
|
||||
}
|
||||
|
||||
const checkProcess = async () => {
|
||||
const runningPaths = await get_all_running_profile_paths().catch(handleError)
|
||||
const runningProcesses = await get_by_profile_path(props.instance.path).catch(handleError)
|
||||
|
||||
if (runningPaths.includes(props.instance.path)) {
|
||||
playing.value = true
|
||||
return
|
||||
}
|
||||
|
||||
playing.value = false
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
const install = async (e) => {
|
||||
e?.stopPropagation()
|
||||
modLoading.value = true
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${props.instance.project_id}/version`,
|
||||
'project versions',
|
||||
)
|
||||
|
||||
if (props.instance.project_type === 'modpack') {
|
||||
const packs = Object.values(await list(true).catch(handleError))
|
||||
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.instance.project_id)
|
||||
) {
|
||||
modLoading.value = true
|
||||
await pack_install(
|
||||
props.instance.project_id,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
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,
|
||||
versions[0].id,
|
||||
props.instance.title,
|
||||
props.instance.icon_url,
|
||||
)
|
||||
} else {
|
||||
modInstallModal.value.show(
|
||||
props.instance.project_id,
|
||||
versions,
|
||||
props.instance.title,
|
||||
props.instance.project_type,
|
||||
)
|
||||
}
|
||||
|
||||
modLoading.value = false
|
||||
playing.value = runningProcesses.length > 0
|
||||
}
|
||||
|
||||
const play = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
modLoading.value = true
|
||||
uuid.value = await run(props.instance.path).catch(handleSevereError)
|
||||
await run(props.instance.path).catch(handleSevereError)
|
||||
modLoading.value = false
|
||||
playing.value = true
|
||||
|
||||
mixpanel_track('InstancePlay', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
}
|
||||
@@ -138,22 +54,13 @@ const stop = async (e, context) => {
|
||||
e?.stopPropagation()
|
||||
playing.value = false
|
||||
|
||||
// If we lost the uuid for some reason, such as a user navigating
|
||||
// from-then-back to this page, we will get all uuids by the instance path.
|
||||
// For-each uuid, kill the process.
|
||||
if (!uuid.value) {
|
||||
const uuids = await get_uuids_by_profile_path(props.instance.path).catch(handleError)
|
||||
uuid.value = uuids[0]
|
||||
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
|
||||
await kill(props.instance.path).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: props.instance.metadata.loader,
|
||||
game_version: props.instance.metadata.game_version,
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
})
|
||||
|
||||
uuid.value = null
|
||||
}
|
||||
|
||||
const openFolder = async () => {
|
||||
@@ -162,14 +69,12 @@ const openFolder = async () => {
|
||||
|
||||
const addContent = async () => {
|
||||
await router.push({
|
||||
path: `/browse/${props.instance.metadata.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
path: `/browse/${props.instance.loader === 'vanilla' ? 'datapack' : 'mod'}`,
|
||||
query: { i: props.instance.path },
|
||||
})
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
install,
|
||||
playing,
|
||||
play,
|
||||
stop,
|
||||
seeInstance,
|
||||
@@ -179,7 +84,7 @@ defineExpose({
|
||||
})
|
||||
|
||||
const unlisten = await process_listener((e) => {
|
||||
if (e.event === 'finished' && e.uuid === uuid.value) playing.value = false
|
||||
if (e.event === 'finished' && e.profile_path_id === props.instance.path) playing.value = false
|
||||
})
|
||||
|
||||
onUnmounted(() => unlisten())
|
||||
@@ -190,46 +95,32 @@ onUnmounted(() => unlisten())
|
||||
<Card class="instance-card-item button-base" @click="seeInstance" @mouseenter="checkProcess">
|
||||
<Avatar
|
||||
size="lg"
|
||||
:src="
|
||||
props.instance.metadata
|
||||
? !props.instance.metadata.icon ||
|
||||
(props.instance.metadata.icon && props.instance.metadata.icon.startsWith('http'))
|
||||
? props.instance.metadata.icon
|
||||
: convertFileSrc(props.instance.metadata?.icon)
|
||||
: props.instance.icon_url
|
||||
"
|
||||
:src="props.instance.icon_path ? convertFileSrc(props.instance.icon_path) : null"
|
||||
alt="Mod card"
|
||||
class="mod-image"
|
||||
/>
|
||||
<div class="project-info">
|
||||
<p class="title">{{ props.instance.metadata?.name || props.instance.title }}</p>
|
||||
<p class="title">{{ props.instance.name }}</p>
|
||||
<p class="description">
|
||||
{{ props.instance.metadata?.loader }}
|
||||
{{ props.instance.metadata?.game_version || props.instance.latest_version }}
|
||||
{{ props.instance.loader }}
|
||||
{{ props.instance.game_version }}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
<div
|
||||
v-if="props.instance.metadata && playing === false && modLoading === false"
|
||||
class="install cta button-base"
|
||||
@click="(e) => play(e, 'InstanceCard')"
|
||||
>
|
||||
<PlayIcon />
|
||||
</div>
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="playing === true"
|
||||
v-if="playing === true"
|
||||
class="stop cta button-base"
|
||||
@click="(e) => stop(e, 'InstanceCard')"
|
||||
@mousehover="checkProcess"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="install"><DownloadIcon /></div>
|
||||
<InstallConfirmModal ref="confirmModal" />
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
<div v-else-if="modLoading === true && playing === false" class="cta loading-cta">
|
||||
<AnimatedLogo class="loading-indicator" />
|
||||
</div>
|
||||
<div v-else class="install cta button-base" @click="(e) => play(e, 'InstanceCard')">
|
||||
<PlayIcon />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -213,13 +213,7 @@ import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import {
|
||||
get_game_versions,
|
||||
get_fabric_versions,
|
||||
get_forge_versions,
|
||||
get_quilt_versions,
|
||||
get_neoforge_versions,
|
||||
} from '@/helpers/metadata'
|
||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
@@ -304,10 +298,10 @@ const [
|
||||
all_game_versions,
|
||||
loaders,
|
||||
] = await Promise.all([
|
||||
get_fabric_versions().then(shallowRef).catch(handleError),
|
||||
get_forge_versions().then(shallowRef).catch(handleError),
|
||||
get_quilt_versions().then(shallowRef).catch(handleError),
|
||||
get_neoforge_versions().then(shallowRef).catch(handleError),
|
||||
get_loader_versions('fabric').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('forge').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('quilt').then(shallowRef).catch(handleError),
|
||||
get_loader_versions('neo').then(shallowRef).catch(handleError),
|
||||
get_game_versions().then(shallowRef).catch(handleError),
|
||||
get_loaders()
|
||||
.then((value) =>
|
||||
|
||||
@@ -53,9 +53,6 @@ defineExpose({
|
||||
show: async (version, currentSelectedJava) => {
|
||||
chosenInstallOptions.value = await find_filtered_jres(version).catch(handleError)
|
||||
|
||||
console.log(chosenInstallOptions.value)
|
||||
console.log(version)
|
||||
|
||||
currentSelected.value = currentSelectedJava
|
||||
if (!currentSelected.value) {
|
||||
currentSelected.value = { path: '', version: '' }
|
||||
|
||||
@@ -162,7 +162,6 @@ async function reinstallJava() {
|
||||
const path = await auto_install_java(props.version).catch(handleError)
|
||||
let result = await get_jre(path)
|
||||
|
||||
console.log('java result ' + result)
|
||||
if (!result) {
|
||||
result = {
|
||||
path: path,
|
||||
@@ -205,6 +204,10 @@ async function reinstallJava() {
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0;
|
||||
|
||||
.btn {
|
||||
width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.test-success {
|
||||
|
||||
@@ -29,7 +29,7 @@ const filteredVersions = computed(() => {
|
||||
})
|
||||
|
||||
const modpackVersionModal = ref(null)
|
||||
const installedVersion = computed(() => props.instance?.metadata?.linked_data?.version_id)
|
||||
const installedVersion = computed(() => props.instance?.linked_data?.version_id)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
const inProgress = ref(false)
|
||||
|
||||
@@ -50,7 +50,7 @@ const switchVersion = async (versionId) => {
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<Card v-if="instance.metadata.linked_data" class="mod-card">
|
||||
<Card v-if="instance.linked_data" class="mod-card">
|
||||
<div class="table">
|
||||
<div class="table-row with-columns table-head">
|
||||
<div class="table-cell table-text download-cell" />
|
||||
|
||||
@@ -6,10 +6,8 @@ import { computed, ref } from 'vue'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { install as pack_install } from '@/helpers/pack.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const router = useRouter()
|
||||
@@ -22,18 +20,6 @@ const props = defineProps({
|
||||
return {}
|
||||
},
|
||||
},
|
||||
confirmModal: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
modInstallModal: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const toColor = computed(() => {
|
||||
@@ -65,40 +51,15 @@ const toTransparent = computed(() => {
|
||||
const install = async (e) => {
|
||||
e?.stopPropagation()
|
||||
installing.value = true
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`,
|
||||
'project versions',
|
||||
)
|
||||
|
||||
if (props.project.project_type === 'modpack') {
|
||||
const packs = Object.values(await list(true).catch(handleError))
|
||||
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
installing.value = true
|
||||
await pack_install(
|
||||
props.project.project_id,
|
||||
versions[0].id,
|
||||
props.project.title,
|
||||
props.project.icon_url,
|
||||
).catch(handleError)
|
||||
await installVersion(
|
||||
props.project.project_id,
|
||||
null,
|
||||
props.instance ? props.instance.path : null,
|
||||
'ProjectCard',
|
||||
() => {
|
||||
installing.value = false
|
||||
} else
|
||||
props.confirmModal.show(
|
||||
props.project.project_id,
|
||||
versions[0].id,
|
||||
props.project.title,
|
||||
props.project.icon_url,
|
||||
)
|
||||
} else {
|
||||
props.modInstallModal.show(props.project.project_id, versions)
|
||||
}
|
||||
|
||||
installing.value = false
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -15,15 +15,15 @@
|
||||
</Button>
|
||||
<div v-if="offline" class="status">
|
||||
<span class="circle stopped" />
|
||||
<div class="running-text clickable" @click="refreshInternet()">
|
||||
<div class="running-text">
|
||||
<span> Offline </span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="selectedProfile" class="status">
|
||||
<div v-if="selectedProcess" class="status">
|
||||
<span class="circle running" />
|
||||
<div ref="profileButton" class="running-text">
|
||||
<router-link :to="`/instance/${encodeURIComponent(selectedProfile.path)}`">
|
||||
{{ selectedProfile.metadata.name }}
|
||||
<router-link :to="`/instance/${encodeURIComponent(selectedProcess.profile.path)}`">
|
||||
{{ selectedProcess.profile.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-if="currentProcesses.length > 1"
|
||||
@@ -34,7 +34,12 @@
|
||||
<DropdownIcon />
|
||||
</div>
|
||||
</div>
|
||||
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop()">
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click="stop(selectedProcess)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
|
||||
@@ -75,17 +80,17 @@
|
||||
class="profile-card"
|
||||
>
|
||||
<Button
|
||||
v-for="profile in currentProcesses"
|
||||
:key="profile.id"
|
||||
v-for="process in currentProcesses"
|
||||
:key="process.pid"
|
||||
class="profile-button"
|
||||
@click="selectProfile(profile)"
|
||||
@click="selectedProcess(process)"
|
||||
>
|
||||
<div class="text"><span class="circle running" /> {{ profile.metadata.name }}</div>
|
||||
<div class="text"><span class="circle running" /> {{ process.profile.name }}</div>
|
||||
<Button
|
||||
v-tooltip="'Stop instance'"
|
||||
icon-only
|
||||
class="icon-button stop"
|
||||
@click.stop="stop(profile.path)"
|
||||
@click.stop="stop(process)"
|
||||
>
|
||||
<StopCircleIcon />
|
||||
</Button>
|
||||
@@ -93,7 +98,7 @@
|
||||
v-tooltip="'View logs'"
|
||||
icon-only
|
||||
class="icon-button"
|
||||
@click.stop="goToTerminal(profile.path)"
|
||||
@click.stop="goToTerminal(process.profile.path)"
|
||||
>
|
||||
<TerminalSquareIcon />
|
||||
</Button>
|
||||
@@ -106,19 +111,15 @@
|
||||
import { DownloadIcon, StopCircleIcon, TerminalSquareIcon, DropdownIcon } from '@modrinth/assets'
|
||||
import { Button, Card } from '@modrinth/ui'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import {
|
||||
get_all_running_profiles as getRunningProfiles,
|
||||
kill_by_uuid as killProfile,
|
||||
get_uuids_by_profile_path as getProfileProcesses,
|
||||
} from '@/helpers/process'
|
||||
import { loading_listener, process_listener, offline_listener } from '@/helpers/events'
|
||||
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
|
||||
import { loading_listener, process_listener } from '@/helpers/events'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
import { refreshOffline, isOffline } from '@/helpers/utils.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -129,38 +130,44 @@ const showCard = ref(false)
|
||||
|
||||
const showProfiles = ref(false)
|
||||
|
||||
const currentProcesses = ref(await getRunningProfiles().catch(handleError))
|
||||
const selectedProfile = ref(currentProcesses.value[0])
|
||||
const currentProcesses = ref([])
|
||||
const selectedProcess = ref()
|
||||
|
||||
const offline = ref(await isOffline().catch(handleError))
|
||||
const refreshInternet = async () => {
|
||||
offline.value = await refreshOffline().catch(handleError)
|
||||
const refresh = async () => {
|
||||
const processes = await getRunningProcesses().catch(handleError)
|
||||
const profiles = await get_many(processes.map((x) => x.profile_path)).catch(handleError)
|
||||
|
||||
currentProcesses.value = processes.map((x) => ({
|
||||
profile: profiles.find((prof) => x.profile_path === prof.path),
|
||||
...x,
|
||||
}))
|
||||
if (!selectedProcess.value || !currentProcesses.value.includes(selectedProcess.value)) {
|
||||
selectedProcess.value = currentProcesses.value[0]
|
||||
}
|
||||
}
|
||||
|
||||
await refresh()
|
||||
|
||||
const offline = ref(!navigator.onLine)
|
||||
window.addEventListener('offline', () => {
|
||||
offline.value = true
|
||||
})
|
||||
window.addEventListener('online', () => {
|
||||
offline.value = false
|
||||
})
|
||||
|
||||
const unlistenProcess = await process_listener(async () => {
|
||||
await refresh()
|
||||
})
|
||||
|
||||
const unlistenRefresh = await offline_listener(async (b) => {
|
||||
offline.value = b
|
||||
await refresh()
|
||||
})
|
||||
|
||||
const refresh = async () => {
|
||||
currentProcesses.value = await getRunningProfiles().catch(handleError)
|
||||
if (!currentProcesses.value.includes(selectedProfile.value)) {
|
||||
selectedProfile.value = currentProcesses.value[0]
|
||||
}
|
||||
}
|
||||
|
||||
const stop = async (path) => {
|
||||
const stop = async (process) => {
|
||||
try {
|
||||
const processes = await getProfileProcesses(path ?? selectedProfile.value.path)
|
||||
await killProfile(processes[0])
|
||||
console.log(process.pid)
|
||||
await killProcess(process.pid).catch(handleError)
|
||||
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: currentProcesses.value[0].metadata.loader,
|
||||
game_version: currentProcesses.value[0].metadata.game_version,
|
||||
loader: process.profile.loader,
|
||||
game_version: process.profile.game_version,
|
||||
source: 'AppBar',
|
||||
})
|
||||
} catch (e) {
|
||||
@@ -170,7 +177,7 @@ const stop = async (path) => {
|
||||
}
|
||||
|
||||
const goToTerminal = (path) => {
|
||||
router.push(`/instance/${encodeURIComponent(path ?? selectedProfile.value.path)}/logs`)
|
||||
router.push(`/instance/${encodeURIComponent(path ?? selectedProcess.value.profile.path)}/logs`)
|
||||
}
|
||||
|
||||
const currentLoadingBars = ref([])
|
||||
@@ -182,8 +189,8 @@ const refreshInfo = async () => {
|
||||
if (x.bar_type.type === 'java_download') {
|
||||
x.title = 'Downloading Java ' + x.bar_type.version
|
||||
}
|
||||
if (x.bar_type.profile_name) {
|
||||
x.title = x.bar_type.profile_name
|
||||
if (x.bar_type.profile_path) {
|
||||
x.title = x.bar_type.profile_path
|
||||
}
|
||||
if (x.bar_type.pack_name) {
|
||||
x.title = x.bar_type.pack_name
|
||||
@@ -215,8 +222,8 @@ const unlistenLoading = await loading_listener(async () => {
|
||||
await refreshInfo()
|
||||
})
|
||||
|
||||
const selectProfile = (profile) => {
|
||||
selectedProfile.value = profile
|
||||
const selectProcess = (process) => {
|
||||
selectedProcess.value = process
|
||||
showProfiles.value = false
|
||||
}
|
||||
|
||||
@@ -267,7 +274,6 @@ onBeforeUnmount(() => {
|
||||
window.removeEventListener('click', handleClickOutsideProfile)
|
||||
unlistenProcess()
|
||||
unlistenLoading()
|
||||
unlistenRefresh()
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -69,12 +69,7 @@ import { formatNumber, formatCategory } from '@modrinth/utils'
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import { ref } from 'vue'
|
||||
import { add_project_from_version as installMod, list } from '@/helpers/profile.js'
|
||||
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_track } from '@/helpers/mixpanel'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
dayjs.extend(relativeTime)
|
||||
|
||||
const props = defineProps({
|
||||
@@ -94,18 +89,6 @@ const props = defineProps({
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
confirmModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
modInstallModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
incompatibilityWarningModal: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
featured: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -123,93 +106,19 @@ const installed = ref(props.installed)
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
const versions = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${props.project.project_id}/version`,
|
||||
'project versions',
|
||||
)
|
||||
let queuedVersionData
|
||||
|
||||
if (!props.instance) {
|
||||
queuedVersionData = versions[0]
|
||||
} else {
|
||||
queuedVersionData = versions.find(
|
||||
(v) =>
|
||||
v.game_versions.includes(props.instance.metadata.game_version) &&
|
||||
(props.project.project_type !== 'mod' ||
|
||||
v.loaders.includes(props.instance.metadata.loader)),
|
||||
)
|
||||
}
|
||||
|
||||
if (props.project.project_type === 'modpack') {
|
||||
const packs = Object.values(await list().catch(handleError))
|
||||
if (
|
||||
packs.length === 0 ||
|
||||
!packs
|
||||
.map((value) => value.metadata)
|
||||
.find((pack) => pack.linked_data?.project_id === props.project.project_id)
|
||||
) {
|
||||
await packInstall(
|
||||
props.project.project_id,
|
||||
queuedVersionData.id,
|
||||
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,
|
||||
queuedVersionData.id,
|
||||
props.project.title,
|
||||
props.project.icon_url,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (props.instance) {
|
||||
if (!queuedVersionData) {
|
||||
props.incompatibilityWarningModal.show(
|
||||
props.instance,
|
||||
props.project.title,
|
||||
versions,
|
||||
() => (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)
|
||||
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.project.title,
|
||||
props.project.project_type,
|
||||
)
|
||||
await installVersion(
|
||||
props.project.project_id,
|
||||
null,
|
||||
props.instance ? props.instance.path : null,
|
||||
'SearchCard',
|
||||
(version) => {
|
||||
installing.value = false
|
||||
return
|
||||
}
|
||||
if (props.instance) installed.value = true
|
||||
}
|
||||
|
||||
installing.value = false
|
||||
if (props.instance && version) {
|
||||
installed.value = true
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,77 +1,37 @@
|
||||
<script setup>
|
||||
import { Modal, Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import ModInstallModal from '@/components/ui/ModInstallModal.vue'
|
||||
import { get_version, get_project } from '@/helpers/cache.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
const version = ref(null)
|
||||
const categories = ref(null)
|
||||
const installing = ref(false)
|
||||
const modInstallModal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
async show(event) {
|
||||
if (event.event === 'InstallVersion') {
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(event.id)}`,
|
||||
'version',
|
||||
)
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(version.value.project_id)}`,
|
||||
'project',
|
||||
)
|
||||
version.value = await get_version(event.id).catch(handleError)
|
||||
project.value = await get_project(version.value.project_id).catch(handleError)
|
||||
} else {
|
||||
project.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/project/${encodeURIComponent(event.id)}`,
|
||||
'project',
|
||||
)
|
||||
version.value = await useFetch(
|
||||
`https://api.modrinth.com/v2/version/${encodeURIComponent(project.value.versions[0])}`,
|
||||
'version',
|
||||
)
|
||||
project.value = await get_project(event.id).catch(handleError)
|
||||
version.value = await get_version(project.value.versions[0]).catch(handleError)
|
||||
}
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod',
|
||||
)
|
||||
confirmModal.value.show()
|
||||
categories.value = (await get_categories().catch(handleError)).filter(
|
||||
(cat) => project.value.categories.includes(cat.name) && cat.project_type === 'mod',
|
||||
)
|
||||
confirmModal.value.show()
|
||||
},
|
||||
})
|
||||
|
||||
async function install() {
|
||||
confirmModal.value.hide()
|
||||
if (project.value.project_type === 'modpack') {
|
||||
await packInstall(
|
||||
project.value.id,
|
||||
version.value.id,
|
||||
project.value.title,
|
||||
project.value.icon_url,
|
||||
).catch(handleError)
|
||||
|
||||
mixpanel.track('PackInstall', {
|
||||
id: project.value.id,
|
||||
version_id: version.value.id,
|
||||
title: project.value.title,
|
||||
source: 'ProjectPage',
|
||||
})
|
||||
} else {
|
||||
modInstallModal.value.show(
|
||||
project.value.id,
|
||||
[version.value],
|
||||
project.value.title,
|
||||
project.value.project_type,
|
||||
)
|
||||
}
|
||||
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -96,7 +56,6 @@ async function install() {
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<ModInstallModal ref="modInstallModal" />
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
ref="incompatibleModal"
|
||||
header="Incompatibility warning"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
@@ -12,13 +13,11 @@
|
||||
</p>
|
||||
<table>
|
||||
<tr class="header">
|
||||
<th>{{ instance?.metadata.name }}</th>
|
||||
<th>{{ projectTitle }}</th>
|
||||
<th>{{ instance?.name }}</th>
|
||||
<th>{{ project.title }}</th>
|
||||
</tr>
|
||||
<tr class="content">
|
||||
<td class="data">
|
||||
{{ instance?.metadata.loader }} {{ instance?.metadata.game_version }}
|
||||
</td>
|
||||
<td class="data">{{ instance?.loader }} {{ instance?.game_version }}</td>
|
||||
<td>
|
||||
<DropdownSelect
|
||||
v-if="versions?.length > 1"
|
||||
@@ -68,34 +67,25 @@ 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)
|
||||
const incompatibleModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let markInstalled = () => {}
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (
|
||||
instanceVal,
|
||||
projectTitleVal,
|
||||
selectedVersions,
|
||||
extMarkInstalled,
|
||||
projectIdVal,
|
||||
projectTypeVal,
|
||||
) => {
|
||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
||||
instance.value = instanceVal
|
||||
projectTitle.value = projectTitleVal
|
||||
versions.value = selectedVersions
|
||||
selectedVersion.value = selectedVersions[0]
|
||||
versions.value = projectVersions
|
||||
selectedVersion.value = projectVersions[0]
|
||||
|
||||
project.value = projectIdVal
|
||||
projectType.value = projectTypeVal
|
||||
project.value = projectVal
|
||||
|
||||
onInstall.value = callback
|
||||
installing.value = false
|
||||
|
||||
incompatibleModal.value.show()
|
||||
markInstalled = extMarkInstalled
|
||||
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
},
|
||||
@@ -105,16 +95,16 @@ const install = async () => {
|
||||
installing.value = true
|
||||
await installMod(instance.value.path, selectedVersion.value.id).catch(handleError)
|
||||
installing.value = false
|
||||
markInstalled()
|
||||
onInstall.value(selectedVersion.value.id)
|
||||
incompatibleModal.value.hide()
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.value.metadata.loader,
|
||||
game_version: instance.value.metadata.game_version,
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
id: project.value,
|
||||
version_id: selectedVersion.value.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectIncompatibilityWarningModal',
|
||||
})
|
||||
}
|
||||
@@ -9,47 +9,55 @@ import { handleError } from '@/store/state.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const version = ref('')
|
||||
const title = ref('')
|
||||
const projectId = ref('')
|
||||
const icon = ref('')
|
||||
const versionId = ref()
|
||||
const project = ref()
|
||||
const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (projectIdVal, versionId, projectTitle, projectIcon) => {
|
||||
projectId.value = projectIdVal
|
||||
version.value = versionId
|
||||
title.value = projectTitle
|
||||
icon.value = projectIcon
|
||||
show: (projectVal, versionIdVal, callback) => {
|
||||
project.value = projectVal
|
||||
versionId.value = versionIdVal
|
||||
installing.value = false
|
||||
confirmModal.value.show()
|
||||
|
||||
onInstall.value = callback
|
||||
|
||||
mixpanel_track('PackInstallStart')
|
||||
},
|
||||
})
|
||||
|
||||
async function install() {
|
||||
installing.value = true
|
||||
console.log(`Installing ${projectId.value} ${version.value} ${title.value} ${icon.value}`)
|
||||
confirmModal.value.hide()
|
||||
|
||||
await pack_install(
|
||||
projectId.value,
|
||||
version.value,
|
||||
title.value,
|
||||
icon.value ? icon.value : null,
|
||||
project.value.id,
|
||||
versionId.value,
|
||||
project.value.title,
|
||||
project.value.icon_url,
|
||||
).catch(handleError)
|
||||
mixpanel_track('PackInstall', {
|
||||
id: projectId.value,
|
||||
version_id: version.value,
|
||||
title: title.value,
|
||||
id: project.value.id,
|
||||
version_id: versionId.value,
|
||||
title: project.value.title,
|
||||
source: 'ConfirmModal',
|
||||
})
|
||||
|
||||
onInstall.value(versionId.value)
|
||||
installing.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="confirmModal" header="Are you sure?" :noblur="!themeStore.advancedRendering">
|
||||
<Modal
|
||||
ref="confirmModal"
|
||||
header="Are you sure?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<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">
|
||||
@@ -14,10 +14,10 @@ import {
|
||||
check_installed,
|
||||
get,
|
||||
list,
|
||||
create,
|
||||
} from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { installVersionDependencies } from '@/helpers/utils'
|
||||
import { installVersionDependencies } from '@/store/install.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
@@ -27,13 +27,12 @@ import { tauri } from '@tauri-apps/api'
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref([])
|
||||
const project = ref('')
|
||||
const projectTitle = ref('')
|
||||
const projectType = ref('')
|
||||
const versions = ref()
|
||||
const project = ref()
|
||||
|
||||
const installModal = ref(null)
|
||||
const installModal = ref()
|
||||
const searchFilter = ref('')
|
||||
|
||||
const showCreation = ref(false)
|
||||
const icon = ref(null)
|
||||
const name = ref(null)
|
||||
@@ -42,33 +41,65 @@ const loader = ref(null)
|
||||
const gameVersion = ref(null)
|
||||
const creatingInstance = ref(false)
|
||||
|
||||
defineExpose({
|
||||
show: async (projectId, selectedVersions, title, type) => {
|
||||
project.value = projectId
|
||||
versions.value = selectedVersions
|
||||
projectTitle.value = title
|
||||
projectType.value = type
|
||||
const profiles = ref([])
|
||||
|
||||
installModal.value.show()
|
||||
const shownProfiles = computed(() =>
|
||||
profiles.value
|
||||
.filter((profile) => {
|
||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
let loaders = versions.value.flatMap((v) => v.loaders)
|
||||
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? loaders.includes(profile.loader) || loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: async (projectVal, versionsVal, callback) => {
|
||||
project.value = projectVal
|
||||
versions.value = versionsVal
|
||||
searchFilter.value = ''
|
||||
|
||||
profiles.value = await getData()
|
||||
showCreation.value = false
|
||||
name.value = null
|
||||
icon.value = null
|
||||
display_icon.value = null
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
onInstall.value = callback
|
||||
|
||||
const profilesVal = await list().catch(handleError)
|
||||
for (let profile of profilesVal) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
||||
handleError,
|
||||
)
|
||||
}
|
||||
profiles.value = profilesVal
|
||||
|
||||
installModal.value.show()
|
||||
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
},
|
||||
})
|
||||
|
||||
const profiles = ref([])
|
||||
|
||||
async function install(instance) {
|
||||
instance.installing = true
|
||||
const version = versions.value.find((v) => {
|
||||
return (
|
||||
v.game_versions.includes(instance.metadata.game_version) &&
|
||||
(v.loaders.includes(instance.metadata.loader) ||
|
||||
v.loaders.includes('minecraft') ||
|
||||
v.loaders.includes('iris') ||
|
||||
v.loaders.includes('optifine'))
|
||||
v.game_versions.includes(instance.game_version) &&
|
||||
(project.value.project_type === 'mod'
|
||||
? v.loaders.includes(instance.loader) || v.loaders.includes('minecraft')
|
||||
: true)
|
||||
)
|
||||
})
|
||||
|
||||
@@ -85,45 +116,18 @@ async function install(instance) {
|
||||
instance.installing = false
|
||||
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.metadata.loader,
|
||||
game_version: instance.metadata.game_version,
|
||||
id: project.value,
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.value.id,
|
||||
version_id: version.id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(version.id)
|
||||
}
|
||||
|
||||
async function getData() {
|
||||
const projects = await list(true).then(Object.values).catch(handleError)
|
||||
|
||||
const filtered = projects
|
||||
.filter((profile) => {
|
||||
return profile.metadata.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.metadata.game_version) &&
|
||||
versions.value
|
||||
.flatMap((v) => v.loaders)
|
||||
.some(
|
||||
(value) =>
|
||||
value === profile.metadata.loader ||
|
||||
['minecraft', 'iris', 'optifine'].includes(value),
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
for (let profile of filtered) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value).catch(handleError)
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
const alreadySentCreation = ref(false)
|
||||
const toggleCreation = () => {
|
||||
showCreation.value = !showCreation.value
|
||||
name.value = null
|
||||
@@ -132,8 +136,7 @@ const toggleCreation = () => {
|
||||
gameVersion.value = null
|
||||
loader.value = null
|
||||
|
||||
if (!alreadySentCreation.value) {
|
||||
alreadySentCreation.value = false
|
||||
if (showCreation.value) {
|
||||
mixpanel_track('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
}
|
||||
}
|
||||
@@ -197,18 +200,16 @@ const createInstance = async () => {
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
id: project.value,
|
||||
version_id: versions.value[0].id,
|
||||
project_type: projectType.value,
|
||||
title: projectTitle.value,
|
||||
project_type: project.value.project_type,
|
||||
title: project.value.title,
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
onInstall.value(versions.value[0].id)
|
||||
|
||||
if (installModal.value) installModal.value.hide()
|
||||
creatingInstance.value = false
|
||||
}
|
||||
|
||||
const check_valid = computed(() => {
|
||||
return name.value
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -216,6 +217,7 @@ const check_valid = computed(() => {
|
||||
ref="installModal"
|
||||
header="Install project to instance"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<input
|
||||
@@ -226,34 +228,27 @@ const check_valid = computed(() => {
|
||||
placeholder="Search for an instance"
|
||||
/>
|
||||
<div class="profiles" :class="{ 'hide-creation': !showCreation }">
|
||||
<div v-for="profile in profiles" :key="profile.metadata.name" class="option">
|
||||
<Button
|
||||
transparent
|
||||
class="profile-button"
|
||||
@click="$router.push(`/instance/${encodeURIComponent(profile.path)}`)"
|
||||
<div v-for="profile in shownProfiles" :key="profile.name" class="option">
|
||||
<router-link
|
||||
class="btn btn-transparent profile-button"
|
||||
:to="`/instance/${encodeURIComponent(profile.path)}`"
|
||||
@click="installModal.hide()"
|
||||
>
|
||||
<Avatar
|
||||
:src="
|
||||
!profile.metadata.icon ||
|
||||
(profile.metadata.icon && profile.metadata.icon.startsWith('http'))
|
||||
? profile.metadata.icon
|
||||
: tauri.convertFileSrc(profile.metadata?.icon)
|
||||
"
|
||||
:src="profile.icon_path ? tauri.convertFileSrc(profile.icon_path) : null"
|
||||
class="profile-image"
|
||||
/>
|
||||
{{ profile.metadata.name }}
|
||||
</Button>
|
||||
{{ profile.name }}
|
||||
</router-link>
|
||||
<div
|
||||
v-tooltip="
|
||||
profile.metadata.linked_data?.locked && !profile.installedMod
|
||||
profile.linked_data?.locked && !profile.installedMod
|
||||
? 'Unpair or unlock an instance to add mods.'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<Button
|
||||
:disabled="
|
||||
profile.installedMod || profile.installing || profile.metadata.linked_data?.locked
|
||||
"
|
||||
:disabled="profile.installedMod || profile.installing || profile.linked_data?.locked"
|
||||
@click="install(profile)"
|
||||
>
|
||||
<DownloadIcon v-if="!profile.installedMod && !profile.installing" />
|
||||
@@ -263,7 +258,7 @@ const check_valid = computed(() => {
|
||||
? 'Installing...'
|
||||
: profile.installedMod
|
||||
? 'Installed'
|
||||
: profile.metadata.linked_data && profile.metadata.linked_data.locked
|
||||
: profile.linked_data && profile.linked_data.locked
|
||||
? 'Paired'
|
||||
: 'Install'
|
||||
}}
|
||||
@@ -294,7 +289,7 @@ const check_valid = computed(() => {
|
||||
placeholder="Name"
|
||||
class="creation-input"
|
||||
/>
|
||||
<Button :disabled="creatingInstance === true || !check_valid" @click="createInstance()">
|
||||
<Button :disabled="creatingInstance === true || !name" @click="createInstance()">
|
||||
<RightArrowIcon />
|
||||
{{ creatingInstance ? 'Creating...' : 'Create' }}
|
||||
</Button>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
||||
import { Button, Card, Checkbox } from '@modrinth/ui'
|
||||
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
|
||||
import {
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
@@ -9,31 +9,57 @@ import {
|
||||
SteamIcon,
|
||||
GitLabIcon,
|
||||
} from '@/assets/external'
|
||||
import {
|
||||
authenticate_begin_flow,
|
||||
authenticate_await_completion,
|
||||
login_2fa,
|
||||
create_account,
|
||||
login_pass,
|
||||
} from '@/helpers/mr_auth.js'
|
||||
import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.js'
|
||||
import { handleError, useNotifications } from '@/store/state.js'
|
||||
import { onMounted, ref } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
const props = defineProps({
|
||||
nextPage: {
|
||||
callback: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
prevPage: {
|
||||
type: Function,
|
||||
required: true,
|
||||
},
|
||||
modal: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref()
|
||||
const turnstileToken = ref()
|
||||
const widgetId = ref()
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
modal.value.show()
|
||||
|
||||
if (window.turnstile === null || !window.turnstile) {
|
||||
const script = document.createElement('script')
|
||||
script.src =
|
||||
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onloadTurnstileCallback'
|
||||
script.async = true
|
||||
script.defer = true
|
||||
document.head.appendChild(script)
|
||||
|
||||
window.onloadTurnstileCallback = loadWidget
|
||||
} else {
|
||||
loadWidget()
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
function loadWidget() {
|
||||
widgetId.value = window.turnstile.render('#turnstile-container', {
|
||||
sitekey: '0x4AAAAAAAW3guHM6Eunbgwu',
|
||||
callback: (token) => (turnstileToken.value = token),
|
||||
expiredCallback: () => (turnstileToken.value = null),
|
||||
})
|
||||
}
|
||||
|
||||
function removeWidget() {
|
||||
if (widgetId.value) {
|
||||
window.turnstile.remove(widgetId.value)
|
||||
widgetId.value = null
|
||||
turnstileToken.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const loggingIn = ref(true)
|
||||
const twoFactorFlow = ref(null)
|
||||
const twoFactorCode = ref('')
|
||||
@@ -45,22 +71,13 @@ const confirmPassword = ref('')
|
||||
const subscribe = ref(true)
|
||||
|
||||
async function signInOauth(provider) {
|
||||
const url = await authenticate_begin_flow(provider).catch(handleError)
|
||||
|
||||
await window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: url,
|
||||
},
|
||||
})
|
||||
|
||||
const creds = await authenticate_await_completion().catch(handleError)
|
||||
const creds = await login(provider).catch(handleSevereError)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.nextPage()
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,22 +85,22 @@ async function signIn2fa() {
|
||||
const creds = await login_2fa(twoFactorCode.value, twoFactorFlow.value).catch(handleError)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.nextPage()
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function signIn() {
|
||||
const creds = await login_pass(
|
||||
username.value,
|
||||
password.value,
|
||||
window.turnstile.getResponse(),
|
||||
).catch(handleError)
|
||||
window.turnstile.reset()
|
||||
const creds = await login_pass(username.value, password.value, turnstileToken.value).catch(
|
||||
handleError,
|
||||
)
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.type === 'two_factor_required') {
|
||||
twoFactorFlow.value = creds.flow
|
||||
} else if (creds && creds.session) {
|
||||
props.nextPage()
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,117 +119,128 @@ async function createAccount() {
|
||||
username.value,
|
||||
email.value,
|
||||
password.value,
|
||||
window.turnstile.getResponse(),
|
||||
turnstileToken.value,
|
||||
subscribe.value,
|
||||
).catch(handleError)
|
||||
window.turnstile.reset()
|
||||
window.turnstile.reset(widgetId.value)
|
||||
|
||||
if (creds && creds.session) {
|
||||
props.nextPage()
|
||||
props.callback()
|
||||
modal.value.hide()
|
||||
}
|
||||
}
|
||||
|
||||
async function goToNextPage() {
|
||||
props.nextPage()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (window.turnstile === null || !window.turnstile) {
|
||||
const script = document.createElement('script')
|
||||
script.src = 'https://challenges.cloudflare.com/turnstile/v0/api.js'
|
||||
script.async = true
|
||||
script.defer = true
|
||||
document.head.appendChild(script)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Card>
|
||||
<div class="cf-turnstile" data-sitekey="0x4AAAAAAAHWfmKCm7cUG869"></div>
|
||||
<template v-if="twoFactorFlow">
|
||||
<h1>Enter two-factor code</h1>
|
||||
<p>Please enter a two-factor code to proceed.</p>
|
||||
<input v-model="twoFactorCode" maxlength="11" type="text" placeholder="Enter code..." />
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 v-if="loggingIn">Login to Modrinth</h1>
|
||||
<h1 v-else>Create an account</h1>
|
||||
<div class="button-grid">
|
||||
<Button class="discord" large @click="signInOauth('discord')">
|
||||
<DiscordIcon />
|
||||
Discord
|
||||
</Button>
|
||||
<Button class="github" large @click="signInOauth('github')">
|
||||
<GithubIcon />
|
||||
Github
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('microsoft')">
|
||||
<MicrosoftIcon />
|
||||
Microsoft
|
||||
</Button>
|
||||
<Button class="google" large @click="signInOauth('google')">
|
||||
<GoogleIcon />
|
||||
Google
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('steam')">
|
||||
<SteamIcon />
|
||||
Steam
|
||||
</Button>
|
||||
<Button class="gitlab" large @click="signInOauth('gitlab')">
|
||||
<GitLabIcon />
|
||||
GitLab
|
||||
</Button>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<hr />
|
||||
<p>Or</p>
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<MailIcon />
|
||||
<input v-model="email" type="text" placeholder="Email" />
|
||||
</div>
|
||||
<div class="iconified-input username">
|
||||
<UserIcon />
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
:placeholder="loggingIn ? 'Email or username' : 'Username'"
|
||||
<Modal ref="modal" :on-hide="removeWidget">
|
||||
<Card>
|
||||
<template v-if="twoFactorFlow">
|
||||
<h1>Enter two-factor code</h1>
|
||||
<p>Please enter a two-factor code to proceed.</p>
|
||||
<input v-model="twoFactorCode" maxlength="11" type="text" placeholder="Enter code..." />
|
||||
</template>
|
||||
<template v-else>
|
||||
<h1 v-if="loggingIn">Login to Modrinth</h1>
|
||||
<h1 v-else>Create an account</h1>
|
||||
<div class="button-grid">
|
||||
<Button class="discord" large @click="signInOauth('discord')">
|
||||
<DiscordIcon />
|
||||
Discord
|
||||
</Button>
|
||||
<Button class="github" large @click="signInOauth('github')">
|
||||
<GithubIcon />
|
||||
Github
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('microsoft')">
|
||||
<MicrosoftIcon />
|
||||
Microsoft
|
||||
</Button>
|
||||
<Button class="google" large @click="signInOauth('google')">
|
||||
<GoogleIcon />
|
||||
Google
|
||||
</Button>
|
||||
<Button class="white" large @click="signInOauth('steam')">
|
||||
<SteamIcon />
|
||||
Steam
|
||||
</Button>
|
||||
<Button class="gitlab" large @click="signInOauth('gitlab')">
|
||||
<GitLabIcon />
|
||||
GitLab
|
||||
</Button>
|
||||
</div>
|
||||
<div class="divider">
|
||||
<hr />
|
||||
<p>Or</p>
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<MailIcon />
|
||||
<input v-model="email" type="text" placeholder="Email" />
|
||||
</div>
|
||||
<div class="iconified-input username">
|
||||
<UserIcon />
|
||||
<input
|
||||
v-model="username"
|
||||
type="text"
|
||||
:placeholder="loggingIn ? 'Email or username' : 'Username'"
|
||||
/>
|
||||
</div>
|
||||
<div class="iconified-input" :class="{ username: !loggingIn }">
|
||||
<LockIcon />
|
||||
<input v-model="password" type="password" placeholder="Password" />
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<LockIcon />
|
||||
<input v-model="confirmPassword" type="password" placeholder="Confirm password" />
|
||||
</div>
|
||||
<div class="turnstile">
|
||||
<div id="turnstile-container"></div>
|
||||
<div id="turnstile-container-2"></div>
|
||||
</div>
|
||||
<Checkbox
|
||||
v-if="!loggingIn"
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
label="Subscribe to updates about Modrinth"
|
||||
/>
|
||||
<div class="link-row">
|
||||
<a v-if="loggingIn" class="button-base" @click="loggingIn = false"> Create account </a>
|
||||
<a v-else class="button-base" @click="loggingIn = true">Sign in</a>
|
||||
<a class="button-base" href="https://modrinth.com/auth/reset-password">
|
||||
Forgot password?
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" large>Close</Button>
|
||||
<Button v-if="twoFactorCode" color="primary" large @click="signIn2fa"> Login </Button>
|
||||
<Button
|
||||
v-else-if="loggingIn"
|
||||
color="primary"
|
||||
large
|
||||
@click="signIn"
|
||||
:disabled="!turnstileToken"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
|
||||
Create account
|
||||
</Button>
|
||||
</div>
|
||||
<div class="iconified-input" :class="{ username: !loggingIn }">
|
||||
<LockIcon />
|
||||
<input v-model="password" type="password" placeholder="Password" />
|
||||
</div>
|
||||
<div v-if="!loggingIn" class="iconified-input username">
|
||||
<LockIcon />
|
||||
<input v-model="confirmPassword" type="password" placeholder="Confirm password" />
|
||||
</div>
|
||||
<Checkbox
|
||||
v-if="!loggingIn"
|
||||
v-model="subscribe"
|
||||
class="subscribe-btn"
|
||||
label="Subscribe to updates about Modrinth"
|
||||
/>
|
||||
<div class="link-row">
|
||||
<a v-if="loggingIn" class="button-base" @click="loggingIn = false"> Create account </a>
|
||||
<a v-else class="button-base" @click="loggingIn = true">Sign in</a>
|
||||
<a class="button-base" href="https://modrinth.com/auth/reset-password">
|
||||
Forgot password?
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<div class="button-row">
|
||||
<Button class="transparent" large @click="prevPage"> {{ modal ? 'Close' : 'Back' }} </Button>
|
||||
<Button v-if="twoFactorCode" color="primary" large @click="signIn2fa"> Login </Button>
|
||||
<Button v-else-if="loggingIn" color="primary" large @click="signIn"> Login </Button>
|
||||
<Button v-else color="primary" large @click="createAccount"> Create account </Button>
|
||||
<Button v-if="!modal" class="transparent" large @click="goToNextPage"> Next </Button>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.modal-container) {
|
||||
.modal-body {
|
||||
width: auto;
|
||||
|
||||
.content {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 25rem;
|
||||
}
|
||||
@@ -321,4 +349,19 @@ onMounted(() => {
|
||||
:deep(.checkbox) {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.turnstile {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
border-radius: var(--radius-md);
|
||||
border: 2px solid var(--color-button-bg);
|
||||
height: 66px;
|
||||
margin-top: var(--gap-md);
|
||||
|
||||
iframe {
|
||||
margin: -1px;
|
||||
min-width: calc(100% + 2px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,7 +34,7 @@ const prevPage = () => {
|
||||
const finishOnboarding = async () => {
|
||||
mixpanel.track('OnboardingFinish')
|
||||
const settings = await get()
|
||||
settings.fully_onboarded = true
|
||||
settings.onboarded = true
|
||||
await set(settings)
|
||||
props.finish()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user