Fix auto updater, add failure message, fix modals (#2335)

* Fix auto updater, add failure message, fix modals

* Fix ads hiding, updater UI

* dummy version, fix gh actions cache

* fix release conf

* actual version bump

* Fix ads hiding sometimes

* Fix event state init

* fix remaining bugs

* Fix lint on linux

* Fix deep linking on Windows

* Fix ad links opening multiple times
This commit is contained in:
Geometrically
2024-08-30 10:42:58 -07:00
committed by GitHub
parent 016c3d779b
commit bd61f5d591
70 changed files with 941 additions and 310 deletions

View File

@@ -44,7 +44,24 @@ jobs:
- name: Setup rust cache
uses: actions/cache@v4
with:
path: target/**
path: |
target/**
!target/*/release/bundle/*/*.dmg
!target/*/release/bundle/*/*.app.tar.gz
!target/*/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/*/*.dmg
!target/release/bundle/*/*.app.tar.gz
!target/release/bundle/*/*.app.tar.gz.sig
!target/release/bundle/*/*.AppImage
!target/release/bundle/*/*.AppImage.tar.gz
!target/release/bundle/*/*.AppImage.tar.gz.sig
!target/release/bundle/*/*.deb
!target/release/bundle/*/*.rpm
!target/release/bundle/*/*.msi
!target/release/bundle/*/*.msi.zip
!target/release/bundle/*/*.msi.zip.sig
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-rust-target-
@@ -124,7 +141,7 @@ jobs:
target/release/bundle/*/*.AppImage.tar.gz.sig
target/release/bundle/*/*.deb
target/release/bundle/*/*.rpm
target/release/bundle/*/*.msi
target/release/bundle/*/*.msi.zip
target/release/bundle/*/*.msi.zip.sig

90
Cargo.lock generated
View File

@@ -166,6 +166,30 @@ dependencies = [
"zstd-safe 7.2.1",
]
[[package]]
name = "async-executor"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7ebdfa2ebdab6b1760375fa7d6f382b9f486eac35fc994625a00e89280bdbb7"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"slab",
]
[[package]]
name = "async-fs"
version = "2.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ebcd09b382f40fcd159c2d695175b2ae620ffa5f3bd6f664131efff4e8b9e04a"
dependencies = [
"async-lock",
"blocking",
"futures-lite",
]
[[package]]
name = "async-io"
version = "2.3.4"
@@ -5053,9 +5077,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "swift-rs"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bbdb58577b6301f8d17ae2561f32002a5bae056d444e0f69e611e504a276204"
checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7"
dependencies = [
"base64 0.21.7",
"serde",
@@ -5227,9 +5251,9 @@ checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1"
[[package]]
name = "tauri"
version = "2.0.0-rc.6"
version = "2.0.0-rc.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "997e79de4c7a13b494a02c8104aa146a5d871ce83e5943e522bc5f8f35c8dab8"
checksum = "e8345ccc676ef16e26b61fc0f5340b4e770678b1e1f53f08c69ebdac5e56b422"
dependencies = [
"anyhow",
"bytes",
@@ -5277,9 +5301,9 @@ dependencies = [
[[package]]
name = "tauri-build"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "032b966611a9324c2185fb9039ccfb938dbe00ec96fa1fe1596c9a1a98a6c87b"
checksum = "7d5ad5fcfaf02cf79aa6727f6c5df38567d8dce172b00b62690c6bc46c08b7ce"
dependencies = [
"anyhow",
"cargo_toml",
@@ -5301,9 +5325,9 @@ dependencies = [
[[package]]
name = "tauri-codegen"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f4138f3ee5fafa703c4504da58b6b94693655d0ddff8daf1e831b6dc04f4125"
checksum = "809ef6316726fc72593d296cf6f4e7461326e310c313d6a6c42b6e7f1e2671cf"
dependencies = [
"base64 0.22.1",
"brotli",
@@ -5328,9 +5352,9 @@ dependencies = [
[[package]]
name = "tauri-macros"
version = "2.0.0-rc.5"
version = "2.0.0-rc.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5995206394cd30411fc5c8ae195e498357f63e11ed960ea32b53512dcb2a5a5"
checksum = "1359e8861d210d25731f8b1bfbb4d111dd06406cf73c59659366ef450364d811"
dependencies = [
"heck 0.5.0",
"proc-macro2",
@@ -5342,9 +5366,9 @@ dependencies = [
[[package]]
name = "tauri-plugin"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4658d4bfb0e9c8abc8fa9d3e45b4e5fcbfe1be850316d96cefa6a1d4ffc215be"
checksum = "a7dded420c86183f592d0fe925ef9447f41e26fa79f0bdfef8d3f17bfbcdbfb7"
dependencies = [
"anyhow",
"glob",
@@ -5453,6 +5477,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "tauri-plugin-single-instance"
version = "2.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d73c92c98d44d4daba0118d905f45243dfcd6eaac82216c3382a02d17cb74cf2"
dependencies = [
"log",
"serde",
"serde_json",
"tauri",
"thiserror",
"windows-sys 0.59.0",
"zbus",
]
[[package]]
name = "tauri-plugin-updater"
version = "2.0.0-rc.1"
@@ -5499,9 +5538,9 @@ dependencies = [
[[package]]
name = "tauri-runtime"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c0830152f7e56a6c43080ced8f1c30a785a237ca3cfaa559ddf52d4be633275"
checksum = "75c72b844f387bfc3341c355f3e16b8cbf4161848fa4e348670effb222cd3ba5"
dependencies = [
"dpi",
"gtk",
@@ -5518,9 +5557,9 @@ dependencies = [
[[package]]
name = "tauri-runtime-wry"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f185bd051f52bece7ef2b197e1f285dab57e3891faa8eacc991459792b077c1"
checksum = "73accf936a7cd01d1382de7850726fdf6c1f6ab3b01ccb7a0950cb852e332596"
dependencies = [
"cocoa 0.26.0",
"gtk",
@@ -5542,9 +5581,9 @@ dependencies = [
[[package]]
name = "tauri-utils"
version = "2.0.0-rc.6"
version = "2.0.0-rc.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f04e02a821a99d544d93b44870799aaf75c8c0dda1853baf064261da3070b892"
checksum = "d53d9fe87e985b273696ae22ce2b9f099a8f1b44bc8fb127467bda5fcb3e4371"
dependencies = [
"brotli",
"cargo_metadata",
@@ -5611,7 +5650,7 @@ dependencies = [
[[package]]
name = "theseus"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"async-recursion",
"async-tungstenite",
@@ -5662,7 +5701,7 @@ dependencies = [
[[package]]
name = "theseus_gui"
version = "0.8.4"
version = "0.8.5"
dependencies = [
"chrono",
"cocoa 0.25.0",
@@ -5686,6 +5725,7 @@ dependencies = [
"tauri-plugin-dialog",
"tauri-plugin-os",
"tauri-plugin-shell",
"tauri-plugin-single-instance",
"tauri-plugin-updater",
"tauri-plugin-window-state",
"theseus",
@@ -6068,9 +6108,9 @@ dependencies = [
[[package]]
name = "tray-icon"
version = "0.15.1"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b92252d649d771105448969f2b2dda4342ba48b77731b60d37c93665e26615b"
checksum = "131a65b2cef2081bc14dbcd414c906edbfa3bb5323dd7e748cc298614681196b"
dependencies = [
"core-graphics 0.24.0",
"crossbeam-channel",
@@ -7114,9 +7154,15 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb97012beadd29e654708a0fdb4c84bc046f537aecfde2c3ee0a9e4b4d48c725"
dependencies = [
"async-broadcast",
"async-executor",
"async-fs",
"async-io",
"async-lock",
"async-process",
"async-recursion",
"async-task",
"async-trait",
"blocking",
"enumflags2",
"event-listener",
"futures-core",

View File

@@ -3,7 +3,7 @@
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Modrinth App</title>
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />

View File

@@ -1,7 +1,7 @@
{
"name": "@modrinth/app-frontend",
"private": true,
"version": "0.8.4",
"version": "0.8.5",
"type": "module",
"scripts": {
"dev": "vite",
@@ -18,6 +18,7 @@
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
"@vintl/vintl": "^4.4.1",
"dayjs": "^1.11.10",
"floating-vue": "^5.2.2",

View File

@@ -1,7 +1,15 @@
<script setup>
import { computed, ref, onMounted } from 'vue'
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, XIcon } from '@modrinth/assets'
import {
HomeIcon,
SearchIcon,
LibraryIcon,
PlusIcon,
SettingsIcon,
XIcon,
DownloadIcon,
} from '@modrinth/assets'
import { Button, Notifications } from '@modrinth/ui'
import { useLoading, useTheming } from '@/store/state'
import AccountsCard from '@/components/ui/AccountsCard.vue'
@@ -16,7 +24,7 @@ import { handleError, useNotifications } from '@/store/notifications.js'
import { command_listener, warning_listener } from '@/helpers/events.js'
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
import { type } from '@tauri-apps/plugin-os'
import { isDev, getOS } from '@/helpers/utils.js'
import { isDev, getOS, restartApp } from '@/helpers/utils.js'
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { getVersion } from '@tauri-apps/api/app'
@@ -32,6 +40,9 @@ import { invoke } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-shell'
import { get_opening_command, initialize_state } from '@/helpers/state'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { renderString } from '@modrinth/utils'
import { useFetch } from '@/helpers/fetch.js'
import { check } from '@tauri-apps/plugin-updater'
const themeStore = useTheming()
@@ -52,6 +63,8 @@ const os = ref('')
const stateInitialized = ref(false)
const criticalErrorMessage = ref()
onMounted(async () => {
await useCheckDisableMouseover()
})
@@ -108,7 +121,18 @@ async function setupApp() {
}),
)
useFetch(
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
'criticalAnnouncements',
true,
).then((res) => {
if (res && res.header && res.body) {
criticalErrorMessage.value = res
}
})
get_opening_command().then(handleCommand)
checkUpdates()
}
const stateFailed = ref(false)
@@ -218,6 +242,20 @@ async function handleCommand(e) {
urlModal.value.show(e)
}
}
const updateAvailable = ref(false)
async function checkUpdates() {
const update = await check()
console.log(update)
updateAvailable.value = !!update
setTimeout(
() => {
checkUpdates()
},
5 * 1000 * 60,
)
}
</script>
<template>
@@ -251,6 +289,14 @@ async function handleCommand(e) {
</div>
</div>
<div class="settings pages-list">
<button
v-if="updateAvailable"
v-tooltip="'Install update'"
class="btn btn-outline btn-primary icon-only collapsed-button"
@click="restartApp()"
>
<DownloadIcon />
</button>
<Button
v-tooltip="'Create profile'"
class="sleek-primary collapsed-button"
@@ -266,6 +312,10 @@ async function handleCommand(e) {
</div>
</div>
<div class="view">
<div v-if="criticalErrorMessage" class="critical-error-banner" data-tauri-drag-region>
<h1>{{ criticalErrorMessage.header }}</h1>
<div class="markdown-body" v-html="renderString(criticalErrorMessage.body ?? '')"></div>
</div>
<div class="appbar-row">
<div data-tauri-drag-region class="appbar">
<section class="navigation-controls">
@@ -378,6 +428,16 @@ async function handleCommand(e) {
width: calc(100% - var(--sidebar-width));
background-color: var(--color-raised-bg);
.critical-error-banner {
margin-top: -1.25rem;
padding: 1rem;
background-color: rgba(203, 34, 69, 0.1);
border-left: 2px solid var(--color-red);
border-bottom: 2px solid var(--color-red);
border-right: 2px solid var(--color-red);
border-radius: 1rem;
}
.appbar {
display: flex;
align-items: center;

View File

@@ -12,13 +12,13 @@ import {
SearchIcon,
XIcon,
} from '@modrinth/assets'
import { ConfirmModal, Button, Card, DropdownSelect } from '@modrinth/ui'
import { Button, Card, DropdownSelect } from '@modrinth/ui'
import { formatCategoryHeader } from '@modrinth/utils'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import dayjs from 'dayjs'
import { useTheming } from '@/store/theme.js'
import { duplicate, remove } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
const props = defineProps({
instances: {
@@ -35,7 +35,6 @@ const props = defineProps({
const instanceOptions = ref(null)
const instanceComponents = ref(null)
const themeStore = useTheming()
const currentDeleteInstance = ref(null)
const confirmModal = ref(null)
@@ -230,13 +229,12 @@ const filteredResults = computed(() => {
})
</script>
<template>
<ConfirmModal
<ConfirmModalWrapper
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">

View File

@@ -12,7 +12,7 @@ import {
EyeIcon,
ChevronRightIcon,
} from '@modrinth/assets'
import { ConfirmModal } from '@modrinth/ui'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import Instance from '@/components/ui/Instance.vue'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
@@ -22,7 +22,6 @@ import { handleError } from '@/store/notifications.js'
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
import { useRouter } from 'vue-router'
import { showProfileInFolder } from '@/helpers/utils.js'
import { useTheming } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
import { handleSevereError } from '@/store/error.js'
import { install as installVersion } from '@/store/install.js'
@@ -53,7 +52,6 @@ const instanceComponents = ref(null)
const rows = ref(null)
const deleteConfirmModal = ref(null)
const themeStore = useTheming()
const currentDeleteInstance = ref(null)
async function deleteProfile() {
@@ -207,13 +205,12 @@ onUnmounted(() => {
</script>
<template>
<ConfirmModal
<ConfirmModalWrapper
ref="deleteConfirmModal"
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">

View File

@@ -5,7 +5,7 @@
v-tooltip.right="'Minecraft accounts'"
class="button-base avatar-button"
:class="{ expanded: mode === 'expanded' }"
@click="showCard = !showCard"
@click="toggleMenu"
>
<Avatar
:size="mode === 'expanded' ? 'xs' : 'sm'"
@@ -73,6 +73,7 @@ import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events'
import { handleSevereError } from '@/store/error.js'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
defineProps({
mode: {
@@ -144,7 +145,20 @@ const handleClickOutside = (event) => {
!elements.includes(card.value.$el) &&
!button.value.contains(event.target)
) {
toggleMenu(false)
}
}
function toggleMenu(override = true) {
if (showCard.value || !override) {
if (showCard.value) {
show_ads_window()
}
showCard.value = false
} else {
hide_ads_window()
showCard.value = true
}
}

View File

@@ -20,7 +20,7 @@ const handleAddContentFromFile = async () => {
if (!newProject) return
for (const project of newProject) {
await add_project_from_path(props.instance.path, project).catch(handleError)
await add_project_from_path(props.instance.path, project.path).catch(handleError)
}
}

View File

@@ -25,6 +25,7 @@
<script setup>
import { onBeforeUnmount, onMounted, ref } from 'vue'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
const emit = defineEmits(['menu-closed', 'option-clicked'])
@@ -37,6 +38,7 @@ const shown = ref(false)
defineExpose({
showMenu: (event, passedItem, passedOptions) => {
hide_ads_window()
item.value = passedItem
options.value = passedOptions
@@ -69,6 +71,9 @@ const isLinkedData = (item) => {
}
const hideContextMenu = () => {
if (shown.value) {
show_ads_window()
}
shown.value = false
emit('menu-closed')
}

View File

@@ -1,6 +1,5 @@
<script setup>
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
import { Modal } from '@modrinth/ui'
import { ChatIcon } from '@/assets/icons'
import { ref } from 'vue'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
@@ -9,6 +8,7 @@ import { handleSevereError } from '@/store/error.js'
import { cancel_directory_change } from '@/helpers/settings.js'
import { install } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const errorModal = ref()
const error = ref()
@@ -121,7 +121,7 @@ async function repairInstance() {
</script>
<template>
<Modal ref="errorModal" :header="title" :closable="closable">
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
<div class="modal-body">
<div class="markdown-body">
<template v-if="errorType === 'minecraft_auth'">
@@ -272,7 +272,7 @@ async function repairInstance() {
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style>

View File

@@ -1,12 +1,12 @@
<script setup>
import { XIcon, PlusIcon } from '@modrinth/assets'
import { Button, Checkbox, Modal } from '@modrinth/ui'
import { Button, Checkbox } from '@modrinth/ui'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import { ref } from 'vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { open } from '@tauri-apps/plugin-dialog'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps({
instance: {
@@ -30,8 +30,6 @@ const files = ref([])
const folders = ref([])
const showingFiles = ref(false)
const themeStore = useTheming()
const initFiles = async () => {
const newFolders = new Map()
const sep = '/'
@@ -106,7 +104,7 @@ const exportPack = async () => {
</script>
<template>
<Modal ref="exportModal" header="Export modpack" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="exportModal" header="Export modpack">
<div class="modal-body">
<div class="labeled_input">
<p>Modpack Name</p>
@@ -208,7 +206,7 @@ const exportPack = async () => {
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">

View File

@@ -1,5 +1,5 @@
<template>
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="modal" header="Create instance">
<div class="modal-header">
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
</div>
@@ -193,10 +193,11 @@
/>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import {
PlusIcon,
UploadIcon,
@@ -207,7 +208,7 @@ import {
FolderSearchIcon,
UpdatedIcon,
} from '@modrinth/assets'
import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
import { Avatar, Button, Chips, Checkbox } from '@modrinth/ui'
import { computed, onUnmounted, ref, shallowRef } from 'vue'
import { get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile'
@@ -217,7 +218,6 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
import { handleError } from '@/store/notifications.js'
import Multiselect from 'vue-multiselect'
import { trackEvent } from '@/helpers/analytics'
import { useTheming } from '@/store/state.js'
import { listen } from '@tauri-apps/api/event'
import { install_from_file } from '@/helpers/pack.js'
import {
@@ -227,8 +227,6 @@ import {
} from '@/helpers/import.js'
import ProgressBar from '@/components/ui/ProgressBar.vue'
const themeStore = useTheming()
const profile_name = ref('')
const game_version = ref('')
const loader = ref('vanilla')
@@ -371,7 +369,7 @@ const create_instance = async () => {
}
const upload_icon = async () => {
icon.value = await open({
const res = await open({
multiple: false,
filters: [
{
@@ -381,6 +379,8 @@ const upload_icon = async () => {
],
})
icon.value = res ? res.path : null
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
}
@@ -417,7 +417,7 @@ const openFile = async () => {
const newProject = await open({ multiple: false })
if (!newProject) return
hide()
await install_from_file(newProject).catch(handleError)
await install_from_file(newProject.path).catch(handleError)
trackEvent('InstanceCreate', {
source: 'CreationModalFileOpen',

View File

@@ -1,5 +1,5 @@
<template>
<Modal ref="detectJavaModal" header="Select java version" :noblur="!themeStore.advancedRendering">
<ModalWrapper ref="detectJavaModal" header="Select java version">
<div class="auto-detect-modal">
<div class="table">
<div class="table-row table-head">
@@ -32,18 +32,16 @@
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
import { Modal, Button } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { ref } from 'vue'
import { find_filtered_jres } from '@/helpers/jre.js'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme.js'
import { trackEvent } from '@/helpers/analytics'
const themeStore = useTheming()
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const chosenInstallOptions = ref([])
const detectJavaModal = ref(null)

View File

@@ -127,17 +127,17 @@ async function handleJavaFileInput() {
let filePath = await open()
if (filePath) {
let result = await get_jre(filePath)
let result = await get_jre(filePath.path)
if (!result) {
result = {
path: filePath,
path: filePath.path,
version: props.version.toString(),
architecture: 'x86',
}
}
trackEvent('JavaManualSelect', {
path: filePath,
path: filePath.path,
version: props.version,
})

View File

@@ -1,11 +1,11 @@
<script setup>
import { CheckIcon } from '@modrinth/assets'
import { Button, Modal, Badge } from '@modrinth/ui'
import { Button, Badge } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { useTheming } from '@/store/theme'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { releaseColor } from '@/helpers/utils'
import { SwapIcon } from '@/assets/icons/index.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps({
versions: {
@@ -33,8 +33,6 @@ const installedVersion = computed(() => props.instance?.linked_data?.version_id)
const installing = computed(() => props.instance.install_stage !== 'installed')
const inProgress = ref(false)
const themeStore = useTheming()
const switchVersion = async (versionId) => {
inProgress.value = true
await update_managed_modrinth_version(props.instance.path, versionId)
@@ -43,11 +41,10 @@ const switchVersion = async (versionId) => {
</script>
<template>
<Modal
<ModalWrapper
ref="modpackVersionModal"
class="modpack-version-modal"
header="Change modpack version"
:noblur="!themeStore.advancedRendering"
>
<div class="modal-body">
<Card v-if="instance.linked_data" class="mod-card">
@@ -111,7 +108,7 @@ const switchVersion = async (versionId) => {
</div>
</Card>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">

View File

@@ -32,26 +32,26 @@ let intersectionObserver
let mutationObserver
onMounted(() => {
if (showAd.value) {
updateAdPosition()
updateAdPosition(true)
resizeObserver = new ResizeObserver(updateAdPosition)
resizeObserver = new ResizeObserver(() => updateAdPosition())
resizeObserver.observe(adsWrapper.value)
intersectionObserver = new IntersectionObserver(updateAdPosition)
intersectionObserver = new IntersectionObserver(() => updateAdPosition())
intersectionObserver.observe(adsWrapper.value)
mutationObserver = new MutationObserver(updateAdPosition)
mutationObserver = new MutationObserver(() => updateAdPosition())
mutationObserver.observe(adsWrapper.value, { attributes: true, childList: true, subtree: true })
// Add scroll event listener
scrollHandler = () => {
requestAnimationFrame(updateAdPosition)
requestAnimationFrame(() => updateAdPosition())
}
window.addEventListener('scroll', scrollHandler, { passive: true })
}
})
function updateAdPosition() {
function updateAdPosition(overrideShown = false) {
if (adsWrapper.value) {
const rect = adsWrapper.value.getBoundingClientRect()
@@ -69,7 +69,7 @@ function updateAdPosition() {
}
}
init_ads_window(rect.left + window.scrollX, y, rect.right - rect.left, height)
init_ads_window(rect.left + window.scrollX, y, rect.right - rect.left, height, overrideShown)
}
}

View File

@@ -130,9 +130,16 @@ const os = ref('')
getOS().then((x) => (os.value = x))
loading_listener(async (e) => {
console.log(e)
if (e.event.type === 'directory_move') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating app directory...'
} else if (e.event.type === 'launcher_update') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Updating Modrinth App...'
} else if (e.event.type === 'checking_for_updates') {
loadingProgress.value = 100 * (e.fraction ?? 1)
message.value = 'Checking for updates...'
}
})

View File

@@ -1,11 +1,12 @@
<script setup>
import { Modal, Button } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { ref } from 'vue'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get_categories } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js'
import { get_version, get_project } from '@/helpers/cache.js'
import { install as installVersion } from '@/store/install.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const confirmModal = ref(null)
const project = ref(null)
@@ -41,7 +42,7 @@ async function install() {
</script>
<template>
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
<div class="modal-body">
<SearchCard
:project="project"
@@ -60,7 +61,7 @@ async function install() {
</div>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">

View File

@@ -1,10 +1,5 @@
<template>
<Modal
ref="incompatibleModal"
header="Incompatibility warning"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="incompatibleModal" header="Incompatibility warning" :on-hide="onInstall">
<div class="modal-body">
<p>
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
@@ -51,20 +46,19 @@
</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { XIcon, DownloadIcon } from '@modrinth/assets'
import { Button, Modal, DropdownSelect } from '@modrinth/ui'
import { Button, DropdownSelect } from '@modrinth/ui'
import { formatCategory } from '@modrinth/utils'
import { add_project_from_version as installMod } from '@/helpers/profile'
import { ref } from 'vue'
import { handleError, useTheming } from '@/store/state.js'
import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
const themeStore = useTheming()
const instance = ref(null)
const project = ref(null)
const versions = ref(null)

View File

@@ -1,13 +1,11 @@
<script setup>
import { XIcon, DownloadIcon } from '@modrinth/assets'
import { Button, Modal } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { install as pack_install } from '@/helpers/pack'
import { ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import { useTheming } from '@/store/theme.js'
import { handleError } from '@/store/state.js'
const themeStore = useTheming()
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const versionId = ref()
const project = ref()
@@ -52,12 +50,7 @@ async function install() {
</script>
<template>
<Modal
ref="confirmModal"
header="Are you sure?"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="confirmModal" header="Are you sure?" :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">
@@ -67,7 +60,7 @@ async function install() {
>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style lang="scss" scoped>

View File

@@ -7,7 +7,7 @@ import {
RightArrowIcon,
CheckIcon,
} from '@modrinth/assets'
import { Avatar, Modal, Button, Card } from '@modrinth/ui'
import { Avatar, Button, Card } from '@modrinth/ui'
import { computed, ref } from 'vue'
import {
add_project_from_version as installMod,
@@ -19,12 +19,11 @@ import {
import { open } from '@tauri-apps/plugin-dialog'
import { installVersionDependencies } from '@/store/install.js'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme.js'
import { useRouter } from 'vue-router'
import { convertFileSrc } from '@tauri-apps/api/core'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const themeStore = useTheming()
const router = useRouter()
const versions = ref()
@@ -142,7 +141,7 @@ const toggleCreation = () => {
}
const upload_icon = async () => {
icon.value = await open({
const res = await open({
multiple: false,
filters: [
{
@@ -151,6 +150,7 @@ const upload_icon = async () => {
},
],
})
icon.value = res ? res.path : null
if (!icon.value) return
display_icon.value = convertFileSrc(icon.value)
@@ -213,12 +213,7 @@ const createInstance = async () => {
</script>
<template>
<Modal
ref="installModal"
header="Install project to instance"
:noblur="!themeStore.advancedRendering"
:on-hide="onInstall"
>
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
<div class="modal-body">
<input
v-model="searchFilter"
@@ -304,7 +299,7 @@ const createInstance = async () => {
<Button @click="installModal.hide()">Cancel</Button>
</div>
</div>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">

View File

@@ -0,0 +1,69 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ConfirmModal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
defineProps({
confirmationText: {
type: String,
default: '',
},
hasToType: {
type: Boolean,
default: false,
},
title: {
type: String,
default: 'No title defined',
required: true,
},
description: {
type: String,
default: 'No description defined',
required: true,
},
proceedLabel: {
type: String,
default: 'Proceed',
},
})
const emit = defineEmits(['proceed'])
const modal = ref(null)
defineExpose({
show: () => {
hide_ads_window()
modal.value.show()
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
show_ads_window()
}
function proceed() {
emit('proceed')
}
</script>
<template>
<ConfirmModal
ref="modal"
:confirmation-text="confirmationText"
:has-to-type="hasToType"
:title="title"
:description="description"
:proceed-label="proceedLabel"
:on-hide="onModalHide"
:noblur="!themeStore.advancedRendering"
@proceed="proceed"
/>
</template>

View File

@@ -0,0 +1,49 @@
<script setup lang="ts">
import { ref } from 'vue'
import { Modal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
const props = defineProps({
header: {
type: String,
default: null,
},
closable: {
type: Boolean,
default: true,
},
onHide: {
type: Function,
default() {
return () => {}
},
},
})
const modal = ref(null)
defineExpose({
show: () => {
hide_ads_window()
modal.value.show()
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
show_ads_window()
props.onHide()
}
</script>
<template>
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
<slot />
</Modal>
</template>

View File

@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ShareModal } from '@modrinth/ui'
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.js'
const themeStore = useTheming()
defineProps({
header: {
type: String,
default: 'Share',
},
shareTitle: {
type: String,
default: 'Modrinth',
},
shareText: {
type: String,
default: null,
},
link: {
type: Boolean,
default: false,
},
openInNewTab: {
type: Boolean,
default: true,
},
})
const modal = ref(null)
defineExpose({
show: (passedContent) => {
hide_ads_window()
modal.value.show(passedContent)
},
hide: () => {
onModalHide()
modal.value.hide()
},
})
function onModalHide() {
show_ads_window()
}
</script>
<template>
<ShareModal
ref="modal"
:header="header"
:share-title="shareTitle"
:share-text="shareText"
:link="link"
:open-in-new-tab="openInNewTab"
:on-hide="onModalHide"
:noblur="!themeStore.advancedRendering"
/>
</template>

View File

@@ -1,6 +1,6 @@
<script setup>
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
import { Button, Card, Checkbox } from '@modrinth/ui'
import {
DiscordIcon,
GithubIcon,
@@ -13,6 +13,7 @@ import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.
import { handleError, useNotifications } from '@/store/state.js'
import { ref } from 'vue'
import { handleSevereError } from '@/store/error.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
const props = defineProps({
callback: {
@@ -132,7 +133,7 @@ async function createAccount() {
</script>
<template>
<Modal ref="modal" :on-hide="removeWidget">
<ModalWrapper ref="modal" :on-hide="removeWidget">
<Card>
<template v-if="twoFactorFlow">
<h1>Enter two-factor code</h1>
@@ -217,17 +218,17 @@ async function createAccount() {
v-else-if="loggingIn"
color="primary"
large
@click="signIn"
:disabled="!turnstileToken"
@click="signIn"
>
Login
</Button>
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
<Button v-else color="primary" large :disabled="!turnstileToken" @click="createAccount">
Create account
</Button>
</div>
</Card>
</Modal>
</ModalWrapper>
</template>
<style scoped lang="scss">

View File

@@ -1,9 +1,13 @@
import { invoke } from '@tauri-apps/api/core'
export async function init_ads_window(x, y, width, height) {
return await invoke('plugin:ads|init_ads_window', { x, y, width, height })
export async function init_ads_window(x, y, width, height, overrideShown = false) {
return await invoke('plugin:ads|init_ads_window', { x, y, width, height, overrideShown })
}
export async function hide_ads_window() {
return await invoke('plugin:ads|hide_ads_window')
export async function show_ads_window() {
return await invoke('plugin:ads|show_ads_window')
}
export async function hide_ads_window(reset) {
return await invoke('plugin:ads|hide_ads_window', { reset })
}

View File

@@ -33,6 +33,10 @@ export async function highlightModInProfile(profilePath, projectPath) {
return await highlightInFolder(fullPath)
}
export async function restartApp() {
return await invoke('restart_app')
}
export const releaseColor = (releaseType) => {
switch (releaseType) {
case 'release':

View File

@@ -884,6 +884,7 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
height: fit-content;
min-height: calc(100vh - 3.25rem);
max-height: calc(100vh - 3.25rem);
width: 20rem;
overflow-y: auto;
-ms-overflow-style: none;
scrollbar-width: none;
@@ -902,8 +903,8 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
}
.search {
margin: 0 1rem 0.5rem calc(300px + 2.5rem);
width: calc(100% - calc(300px + 2.5rem));
margin: 0 1rem 0.5rem calc(20rem + 1rem);
width: calc(100% - calc(20rem + 1rem));
.offline {
margin: 1rem;

View File

@@ -11,7 +11,7 @@ import { get_search_results } from '@/helpers/cache.js'
import { hide_ads_window } from '@/helpers/ads.js'
onMounted(() => {
hide_ads_window()
hide_ads_window(true)
})
const featuredModpacks = ref({})

View File

@@ -13,7 +13,7 @@ import { NewInstanceImage } from '@/assets/icons'
import { hide_ads_window } from '@/helpers/ads.js'
onMounted(() => {
hide_ads_window()
hide_ads_window(true)
})
const route = useRoute()

View File

@@ -1,7 +1,7 @@
<script setup>
import { ref, watch, onMounted } from 'vue'
import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import { Card, Slider, DropdownSelect, Toggle, ConfirmModal, Button } from '@modrinth/ui'
import { Card, Slider, DropdownSelect, Toggle, Button } from '@modrinth/ui'
import { handleError, useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings'
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
@@ -14,6 +14,7 @@ import { getOS } from '@/helpers/utils.js'
import { getVersion } from '@tauri-apps/api/app'
import { get_user, purge_cache_types } from '@/helpers/cache.js'
import { hide_ads_window } from '@/helpers/ads.js'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
onMounted(() => {
hide_ads_window()
@@ -174,13 +175,12 @@ async function purgeCache() {
Sign in
</button>
</div>
<ConfirmModal
<ConfirmModalWrapper
ref="purgeCacheConfirmModal"
title="Are you sure you want to purge the cache?"
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
:has-to-type="false"
proceed-label="Purge cache"
:noblur="!themeStore.advancedRendering"
@proceed="purgeCache"
/>
<div class="adjacent-input">

View File

@@ -76,7 +76,7 @@
</div>
</RecycleScroller>
</div>
<ShareModal
<ShareModalWrapper
ref="shareModal"
header="Share Log"
share-title="Instance Log"
@@ -89,7 +89,7 @@
<script setup>
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
import { Button, Card, ShareModal, Checkbox, DropdownSelect } from '@modrinth/ui'
import { Button, Card, Checkbox, DropdownSelect } from '@modrinth/ui'
import {
delete_logs_by_filename,
get_logs,
@@ -107,6 +107,7 @@ import { handleError } from '@/store/notifications.js'
import { ofetch } from 'ofetch'
import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
dayjs.extend(isToday)
dayjs.extend(isYesterday)

View File

@@ -284,7 +284,7 @@
:link-function="(page) => `?page=${page}`"
@switch-page="switchPage"
/>
<Modal ref="deleteWarning" header="Are you sure?">
<ModalWrapper ref="deleteWarning" header="Are you sure?">
<div class="modal-body">
<div class="markdown-body">
<p>
@@ -302,8 +302,8 @@
</Button>
</div>
</div>
</Modal>
<Modal ref="deleteDisabledWarning" header="Are you sure?">
</ModalWrapper>
<ModalWrapper ref="deleteDisabledWarning" header="Are you sure?">
<div class="modal-body">
<div class="markdown-body">
<p>
@@ -325,8 +325,8 @@
</Button>
</div>
</div>
</Modal>
<ShareModal
</ModalWrapper>
<ShareModalWrapper
ref="shareModal"
share-title="Sharing modpack content"
share-text="Check out the projects I'm using in my modpack!"
@@ -360,8 +360,6 @@ import {
import {
Pagination,
DropdownSelect,
ShareModal,
Modal,
Checkbox,
AnimatedLogo,
Avatar,
@@ -393,6 +391,8 @@ import {
get_version_many,
} from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
const props = defineProps({
instance: {
@@ -784,6 +784,7 @@ const deleteDisabled = async () => {
}
const shareNames = async () => {
console.log(functionValues.value)
await shareModal.value.show(functionValues.value.map((x) => x.name).join('\n'))
}

View File

@@ -1,18 +1,13 @@
<template>
<ConfirmModal
<ConfirmModalWrapper
ref="modal_confirm"
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="removeProfile"
/>
<Modal
ref="modalConfirmUnlock"
header="Are you sure you want to unlock this instance?"
:noblur="!themeStore.advancedRendering"
>
<ModalWrapper ref="modalConfirmUnlock" header="Are you sure you want to unlock this instance?">
<div class="modal-delete">
<div
class="markdown-body"
@@ -31,13 +26,9 @@
</button>
</div>
</div>
</Modal>
</ModalWrapper>
<Modal
ref="modalConfirmUnpair"
header="Are you sure you want to unpair this instance?"
:noblur="!themeStore.advancedRendering"
>
<ModalWrapper ref="modalConfirmUnpair" header="Are you sure you want to unpair this instance?">
<div class="modal-delete">
<div
class="markdown-body"
@@ -56,13 +47,9 @@
</button>
</div>
</div>
</Modal>
</ModalWrapper>
<Modal
ref="changeVersionsModal"
header="Change instance versions"
:noblur="!themeStore.advancedRendering"
>
<ModalWrapper ref="changeVersionsModal" header="Change instance versions">
<div class="change-versions-modal universal-body">
<div class="input-row">
<p class="input-label">Loader</p>
@@ -106,7 +93,7 @@
</button>
</div>
</div>
</Modal>
</ModalWrapper>
<section class="card">
<div class="label">
<h3>
@@ -511,18 +498,7 @@ import {
DownloadIcon,
ClipboardCopyIcon,
} from '@modrinth/assets'
import {
Button,
Toggle,
ConfirmModal,
Card,
Slider,
Checkbox,
Avatar,
Modal,
Chips,
DropdownSelect,
} from '@modrinth/ui'
import { Button, Toggle, Card, Slider, Checkbox, Avatar, Chips, DropdownSelect } from '@modrinth/ui'
import { SwapIcon } from '@/assets/icons'
import { Multiselect } from 'vue-multiselect'
@@ -546,10 +522,11 @@ import { open } from '@tauri-apps/plugin-dialog'
import { get_loader_versions } from '@/helpers/metadata.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme.js'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
const breadcrumbs = useBreadcrumbs()
@@ -570,8 +547,6 @@ const props = defineProps({
},
})
const themeStore = useTheming()
const title = ref(props.instance.name)
const icon = ref(props.instance.icon_path)
const groups = ref(props.instance.groups)
@@ -606,7 +581,7 @@ async function setIcon() {
if (!value) return
icon.value = value
icon.value = value.path
await edit_icon(props.instance.path, icon.value).catch(handleError)
trackEvent('InstanceSetIcon')

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus_gui"
version = "0.8.4"
version = "0.8.5"
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
license = "GPL-3.0-only"
repository = "https://github.com/modrinth/code/apps/app/"
@@ -22,7 +22,8 @@ tauri-plugin-deep-link = "2.0.0-rc"
tauri-plugin-os = "2.0.0-rc"
tauri-plugin-shell = "2.0.0-rc"
tauri-plugin-dialog = "2.0.0-rc"
tauri-plugin-updater = { version = "2.0.0-rc.1", optional = true }
tauri-plugin-updater = { version = "2.0.0-rc" }
tauri-plugin-single-instance = { version = "2.0.0-rc" }
tokio = { version = "1", features = ["full"] }
thiserror = "1.0"
@@ -67,4 +68,4 @@ default = ["custom-protocol"]
# this feature is used for production builds where `devPath` points to the filesystem
# DO NOT remove this
custom-protocol = ["tauri/custom-protocol"]
updater = ["dep:tauri-plugin-updater"]
updater = []

View File

@@ -62,7 +62,7 @@ fn main() {
InlinedPlugin::new()
.commands(&[
"get_java_versions",
"set_java_versions",
"set_java_version",
"jre_find_filtered_jres",
"jre_get_jre",
"jre_test_jre",
@@ -225,6 +225,7 @@ fn main() {
"init_ads_window",
"hide_ads_window",
"scroll_ads_window",
"show_ads_window",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,

View File

@@ -38,4 +38,4 @@
"utils:default",
"ads:default"
]
}
}

View File

@@ -0,0 +1,11 @@
{
"identifier": "updater",
"description": "",
"local": true,
"windows": [
"main"
],
"permissions": [
"updater:default"
]
}

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]}}
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}

View File

@@ -327,6 +327,13 @@
"ads:allow-scroll-ads-window"
]
},
{
"description": "ads:allow-show-ads-window -> Enables the show_ads_window command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-show-ads-window"
]
},
{
"description": "ads:deny-hide-ads-window -> Denies the hide_ads_window command without any pre-configured scope.",
"type": "string",
@@ -348,6 +355,13 @@
"ads:deny-scroll-ads-window"
]
},
{
"description": "ads:deny-show-ads-window -> Denies the show_ads_window command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-show-ads-window"
]
},
{
"description": "auth:default -> Default plugin permissions.",
"type": "string",
@@ -2834,10 +2848,10 @@
]
},
{
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
"type": "string",
"enum": [
"jre:allow-set-java-versions"
"jre:allow-set-java-version"
]
},
{
@@ -2883,10 +2897,10 @@
]
},
{
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
"type": "string",
"enum": [
"jre:deny-set-java-versions"
"jre:deny-set-java-version"
]
},
{
@@ -3904,6 +3918,69 @@
"tags:deny-tags-get-report-types"
]
},
{
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
"type": "string",
"enum": [
"updater:default"
]
},
{
"description": "updater:allow-check -> Enables the check command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "utils:default -> Default plugin permissions.",
"type": "string",

View File

@@ -327,6 +327,13 @@
"ads:allow-scroll-ads-window"
]
},
{
"description": "ads:allow-show-ads-window -> Enables the show_ads_window command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:allow-show-ads-window"
]
},
{
"description": "ads:deny-hide-ads-window -> Denies the hide_ads_window command without any pre-configured scope.",
"type": "string",
@@ -348,6 +355,13 @@
"ads:deny-scroll-ads-window"
]
},
{
"description": "ads:deny-show-ads-window -> Denies the show_ads_window command without any pre-configured scope.",
"type": "string",
"enum": [
"ads:deny-show-ads-window"
]
},
{
"description": "auth:default -> Default plugin permissions.",
"type": "string",
@@ -2834,10 +2848,10 @@
]
},
{
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
"type": "string",
"enum": [
"jre:allow-set-java-versions"
"jre:allow-set-java-version"
]
},
{
@@ -2883,10 +2897,10 @@
]
},
{
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
"type": "string",
"enum": [
"jre:deny-set-java-versions"
"jre:deny-set-java-version"
]
},
{
@@ -3904,6 +3918,69 @@
"tags:deny-tags-get-report-types"
]
},
{
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
"type": "string",
"enum": [
"updater:default"
]
},
{
"description": "updater:allow-check -> Enables the check command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-check"
]
},
{
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download"
]
},
{
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-download-and-install"
]
},
{
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:allow-install"
]
},
{
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-check"
]
},
{
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download"
]
},
{
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-download-and-install"
]
},
{
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
"type": "string",
"enum": [
"updater:deny-install"
]
},
{
"description": "utils:default -> Default plugin permissions.",
"type": "string",

View File

@@ -1,16 +1,19 @@
document.addEventListener('click', function (e) {
let target = e.target
while (target != null) {
if (target.matches('a')) {
e.preventDefault()
if (target.href) {
window.top.postMessage({ modrinthOpenUrl: target.href }, 'https://modrinth.com')
if (!window.modrinthClickListener) {
window.modrinthClickListener = true
document.addEventListener('click', function (e) {
let target = e.target
while (target != null) {
if (target.matches('a')) {
e.preventDefault()
if (target.href) {
window.top.postMessage({ modrinthOpenUrl: target.href }, 'https://modrinth.com')
}
break
}
break
target = target.parentElement
}
target = target.parentElement
}
})
})
}
window.open = (url, target, features) => {
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')

View File

@@ -1,51 +1,105 @@
use serde::Serialize;
use tauri::plugin::TauriPlugin;
use tauri::{
Emitter, LogicalPosition, LogicalSize, Manager, Runtime, WebviewUrl,
};
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
use tokio::sync::RwLock;
pub struct AdsState {
pub shown: bool,
pub size: Option<LogicalSize<f32>>,
pub position: Option<LogicalPosition<f32>>,
}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::<R>::new("ads")
.setup(|app, _api| {
app.manage(RwLock::new(AdsState {
shown: true,
size: None,
position: None,
}));
Ok(())
})
.invoke_handler(tauri::generate_handler![
init_ads_window,
hide_ads_window,
scroll_ads_window,
show_ads_window,
])
.build()
}
const LINK_SCRIPT: &str = include_str!("ads-init.js");
// TODO: make ads work on linux
#[tauri::command]
#[cfg(not(target_os = "linux"))]
pub async fn init_ads_window<R: Runtime>(
app: tauri::AppHandle<R>,
x: f32,
y: f32,
width: f32,
height: f32,
override_shown: bool,
) -> crate::api::Result<()> {
#[cfg(not(target_os = "linux"))]
{
if let Some(webview) = app.webviews().get("ads-window") {
use tauri::WebviewUrl;
const LINK_SCRIPT: &str = include_str!("ads-init.js");
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
state.size = Some(LogicalSize::new(width, height));
state.position = Some(LogicalPosition::new(x, y));
if override_shown {
state.shown = true;
}
if let Some(webview) = app.webviews().get("ads-window") {
if state.shown {
let _ = webview.set_position(LogicalPosition::new(x, y));
let _ = webview.set_size(LogicalSize::new(width, height));
} else if let Some(window) = app.get_window("main") {
let _ = window.add_child(
tauri::webview::WebviewBuilder::new(
"ads-window",
WebviewUrl::External(
"https://modrinth.com/wrapper/app-ads".parse().unwrap(),
),
)
.initialization_script(LINK_SCRIPT)
.user_agent("ModrinthApp Ads Webview")
.zoom_hotkeys_enabled(false)
.transparent(true),
LogicalPosition::new(x, y),
LogicalSize::new(width, height),
);
}
} else if let Some(window) = app.get_window("main") {
let _ = window.add_child(
tauri::webview::WebviewBuilder::new(
"ads-window",
WebviewUrl::External(
"https://modrinth.com/wrapper/app-ads".parse().unwrap(),
),
)
.initialization_script(LINK_SCRIPT)
.user_agent("ModrinthApp Ads Webview")
.zoom_hotkeys_enabled(false)
.transparent(true),
if state.shown {
LogicalPosition::new(x, y)
} else {
LogicalPosition::new(-1000.0, -1000.0)
},
LogicalSize::new(width, height),
);
}
Ok(())
}
// TODO: make ads work on linux
#[tauri::command]
#[cfg(target_os = "linux")]
pub async fn init_ads_window() {}
#[tauri::command]
pub async fn show_ads_window<R: Runtime>(
app: tauri::AppHandle<R>,
) -> crate::api::Result<()> {
if let Some(webview) = app.webviews().get("ads-window") {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
state.shown = true;
if let Some(size) = state.size {
let _ = webview.set_size(size);
}
if let Some(position) = state.position {
let _ = webview.set_position(position);
}
}
@@ -55,12 +109,19 @@ pub async fn init_ads_window<R: Runtime>(
#[tauri::command]
pub async fn hide_ads_window<R: Runtime>(
app: tauri::AppHandle<R>,
reset: Option<bool>,
) -> crate::api::Result<()> {
#[cfg(not(target_os = "linux"))]
{
if let Some(webview) = app.webviews().get("ads-window") {
let _ = webview.set_position(LogicalPosition::new(-1000, -1000));
if let Some(webview) = app.webviews().get("ads-window") {
let state = app.state::<RwLock<AdsState>>();
let mut state = state.write().await;
state.shown = false;
if reset.unwrap_or(false) {
state.size = None;
state.position = None;
}
let _ = webview.set_position(LogicalPosition::new(-1000, -1000));
}
Ok(())

View File

@@ -40,6 +40,10 @@ pub enum TheseusSerializableError {
#[error("Tauri error: {0}")]
Tauri(#[from] tauri::Error),
#[cfg(feature = "updater")]
#[error("Tauri updater error: {0}")]
TauriUpdater(#[from] tauri_plugin_updater::Error),
}
// Generic implementation of From<T> for ErrorTypeA
@@ -87,7 +91,15 @@ macro_rules! impl_serialize {
}
// Use the macro to implement Serialize for TheseusSerializableError
#[cfg(not(feature = "updater"))]
impl_serialize! {
IO,
Tauri,
}
#[cfg(feature = "updater")]
impl_serialize! {
IO,
Tauri,
TauriUpdater,
}

View File

@@ -26,7 +26,67 @@ extern crate objc;
#[tauri::command]
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
theseus::EventState::init(app.clone()).await?;
State::init().await?;
#[cfg(feature = "updater")]
{
use tauri_plugin_updater::UpdaterExt;
let updater = app.updater_builder().build()?;
let update_fut = updater.check();
State::init().await?;
let check_bar = theseus::init_loading(
theseus::LoadingBarType::CheckingForUpdates,
1.0,
"Checking for updates...",
)
.await?;
let update = update_fut.await;
drop(check_bar);
if let Some(update) = update.ok().flatten() {
tracing::info!("Update found: {:?}", update.download_url);
let loader_bar_id = theseus::init_loading(
theseus::LoadingBarType::LauncherUpdate {
version: update.version.clone(),
current_version: update.current_version.clone(),
},
1.0,
"Updating Modrinth App...",
)
.await?;
// 100 MiB
const DEFAULT_CONTENT_LENGTH: u64 = 1024 * 1024 * 100;
update
.download_and_install(
|chunk_length, content_length| {
let _ = theseus::emit_loading(
&loader_bar_id,
(chunk_length as f64)
/ (content_length
.unwrap_or(DEFAULT_CONTENT_LENGTH)
as f64),
None,
);
},
|| {},
)
.await?;
app.restart();
}
}
#[cfg(not(feature = "updater"))]
{
State::init().await?;
}
let state = State::get().await?;
app.asset_protocol_scope()
@@ -75,6 +135,11 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
Ok(())
}
#[tauri::command]
fn restart_app(app: tauri::AppHandle) {
app.restart();
}
// if Tauri app is called with arguments, then those arguments will be treated as commands
// ie: deep links or filepaths for .mrpacks
fn main() {
@@ -104,6 +169,19 @@ fn main() {
}
builder = builder
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
if let Some(payload) = args.get(1) {
tracing::info!("Handling deep link from arg {payload}");
let payload = payload.clone();
tauri::async_runtime::spawn(api::utils::handle_command(
payload,
));
}
if let Some(win) = app.get_window("main") {
let _ = win.set_focus();
}
}))
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_deep_link::init())
@@ -188,6 +266,7 @@ fn main() {
toggle_decorations,
api::mr_auth::modrinth_auth_login,
show_window,
restart_app,
]);
#[cfg(target_os = "macos")]

View File

@@ -5,10 +5,15 @@
"build": {
"features": ["updater"]
},
"app": {
"security": {
"capabilities": ["ads", "core", "plugins", "updater"]
}
},
"plugins": {
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
}
}
}
}

View File

@@ -48,7 +48,7 @@
]
},
"productName": "Modrinth App",
"version": "0.8.4",
"version": "0.8.5",
"identifier": "ModrinthApp",
"plugins": {
"deep-link": {
@@ -86,6 +86,7 @@
],
"enable": true
},
"capabilities": ["ads", "core", "plugins"],
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://*.cloudflare.com https://api.mclo.gs https://cmp.inmobi.com",

View File

@@ -78,16 +78,6 @@
}
});
document.getElementById("plus-link").addEventListener("click", (event) => {
event.preventDefault();
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
window.__TAURI_INTERNALS__.invoke("plugin:shell|open", {
path: this.href,
});
}
});
document.addEventListener("contextmenu", (event) => event.preventDefault());
</script>
</body>

View File

@@ -1,6 +1,6 @@
[package]
name = "theseus"
version = "0.8.4"
version = "0.8.5"
authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2021"

View File

@@ -68,7 +68,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
pub name: PathBuf,
}
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
emit_loading(&loading_bar, 0.0, Some("Fetching java version"))?;
let packages = fetch_json::<Vec<Package>>(
Method::GET,
&format!(
@@ -80,7 +80,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
&state.fetch_semaphore,
&state.pool,
).await?;
emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?;
emit_loading(&loading_bar, 10.0, Some("Downloading java version"))?;
if let Some(download) = packages.first() {
let file = fetch_advanced(
@@ -115,13 +115,13 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
}
}
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
emit_loading(&loading_bar, 0.0, Some("Extracting java"))?;
archive.extract(&path).map_err(|_| {
crate::Error::from(crate::ErrorKind::InputError(
"Failed to extract java zip".to_string(),
))
})?;
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
emit_loading(&loading_bar, 10.0, Some("Done extracting java"))?;
let mut base_path = path.join(
download
.name

View File

@@ -287,7 +287,7 @@ pub async fn copy_dotminecraft(
fetch::copy(&src_child, &dst_child, io_semaphore).await?;
emit_loading(&loading_bar, 1.0, None).await?;
emit_loading(&loading_bar, 1.0, None)?;
}
Ok(loading_bar)
}

View File

@@ -191,7 +191,7 @@ pub async fn generate_pack_from_version_id(
let state = State::get().await?;
let loading_bar = if let Some(bar) = initialized_loading_bar {
emit_loading(&bar, 0.0, Some("Downloading pack file")).await?;
emit_loading(&bar, 0.0, Some("Downloading pack file"))?;
bar
} else {
init_loading(
@@ -207,7 +207,7 @@ pub async fn generate_pack_from_version_id(
.await?
};
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
emit_loading(&loading_bar, 0.0, Some("Fetching version"))?;
let version = CachedEntry::get_version(
&version_id,
None,
@@ -220,7 +220,7 @@ pub async fn generate_pack_from_version_id(
"Invalid version ID specified!".to_string(),
)
})?;
emit_loading(&loading_bar, 10.0, None).await?;
emit_loading(&loading_bar, 10.0, None)?;
let (url, hash) =
if let Some(file) = version.files.iter().find(|x| x.primary) {
@@ -248,7 +248,7 @@ pub async fn generate_pack_from_version_id(
&state.pool,
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata")).await?;
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata"))?;
let project = CachedEntry::get_project(
&version.project_id,
@@ -263,7 +263,7 @@ pub async fn generate_pack_from_version_id(
)
})?;
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
emit_loading(&loading_bar, 10.0, Some("Retrieving icon"))?;
let icon = if let Some(icon_url) = project.icon_url {
let state = State::get().await?;
let icon_bytes =
@@ -287,7 +287,7 @@ pub async fn generate_pack_from_version_id(
} else {
None
};
emit_loading(&loading_bar, 10.0, None).await?;
emit_loading(&loading_bar, 10.0, None)?;
Ok(CreatePack {
file,

View File

@@ -203,7 +203,7 @@ pub async fn install_zipped_mrpack_files(
)
.await?;
emit_loading(&loading_bar, 0.0, Some("Extracting overrides")).await?;
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))?;
let mut total_len = 0;
@@ -270,8 +270,7 @@ pub async fn install_zipped_mrpack_files(
"Extracting override {}/{}",
index, total_len
)),
)
.await?;
)?;
}
}

View File

@@ -510,7 +510,7 @@ pub async fn export_mrpack(
// Iterate over every file in the folder
// Every file that is NOT in the config file is added to the zip, in overrides
for path in path_list {
emit_loading(&loading_bar, 1.0, None).await?;
emit_loading(&loading_bar, 1.0, None)?;
let relative_path = pack_get_relative_path(&profile_base_path, &path)?;

View File

@@ -33,7 +33,7 @@ const CLI_PROGRESS_BAR_TOTAL: u64 = 1000;
pub async fn loading_function() -> crate::Result<()> {
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Loading something long...").await;
for i in 0..100 {
emit_loading(&loading_bar, 1.0, None).await?;
emit_loading(&loading_bar, 1.0, None)?;
tokio::time::sleep(Duration::from_millis(100)).await;
}
}
@@ -62,7 +62,7 @@ pub async fn init_loading_unsafe(
total: f64,
title: &str,
) -> crate::Result<LoadingBarId> {
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
let key = LoadingBarId(Uuid::new_v4());
event_state.loading_bars.insert(
@@ -91,7 +91,7 @@ pub async fn init_loading_unsafe(
},
);
// attempt an initial loading_emit event to the frontend
emit_loading(&key, 0.0, None).await?;
emit_loading(&key, 0.0, None)?;
Ok(key)
}
@@ -118,7 +118,7 @@ pub async fn edit_loading(
total: f64,
title: &str,
) -> crate::Result<()> {
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
if let Some(mut bar) = event_state.loading_bars.get_mut(&id.0) {
bar.bar_type = bar_type;
@@ -132,7 +132,7 @@ pub async fn edit_loading(
}
};
emit_loading(id, 0.0, None).await?;
emit_loading(id, 0.0, None)?;
Ok(())
}
@@ -144,12 +144,12 @@ pub async fn edit_loading(
#[allow(unused_variables)]
#[tracing::instrument(level = "debug")]
pub async fn emit_loading(
pub fn emit_loading(
key: &LoadingBarId,
increment_frac: f64,
message: Option<&str>,
) -> crate::Result<()> {
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
let mut loading_bar = match event_state.loading_bars.get_mut(&key.0) {
Some(f) => f,
@@ -211,7 +211,7 @@ pub async fn emit_loading(
pub async fn emit_warning(message: &str) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
@@ -235,7 +235,7 @@ pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
tracing::debug!("Command: {}", serde_json::to_string(&command)?);
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
event_state
.app
.emit("command", command)
@@ -254,7 +254,7 @@ pub async fn emit_process(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
@@ -279,7 +279,7 @@ pub async fn emit_profile(
) -> crate::Result<()> {
#[cfg(feature = "tauri")]
{
let event_state = crate::EventState::get().await?;
let event_state = crate::EventState::get()?;
event_state
.app
.emit(
@@ -329,7 +329,7 @@ macro_rules! loading_join {
async move {
let res = $task.await;
if let Some(key) = key {
$crate::event::emit::emit_loading(key, increment, message).await?;
$crate::event::emit::emit_loading(key, increment, message)?;
}
res
}
@@ -376,8 +376,7 @@ where
async move {
f.await?;
if let Some(key) = key {
emit_loading(key, total / (num_futs as f64), message)
.await?;
emit_loading(key, total / (num_futs as f64), message)?;
}
Ok(())
}

View File

@@ -45,21 +45,14 @@ impl EventState {
.cloned()
}
#[cfg(feature = "tauri")]
pub async fn get() -> crate::Result<Arc<Self>> {
pub fn get() -> crate::Result<Arc<Self>> {
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
}
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
#[cfg(not(feature = "tauri"))]
pub async fn get() -> crate::Result<Arc<Self>> {
Self::init().await
}
// Values provided should not be used directly, as they are clones and are not guaranteed to be up-to-date
pub async fn list_progress_bars() -> crate::Result<DashMap<Uuid, LoadingBar>>
{
let value = Self::get().await?;
let value = Self::get()?;
Ok(value.loading_bars.clone())
}
@@ -67,7 +60,7 @@ impl EventState {
pub async fn get_main_window() -> crate::Result<Option<tauri::WebviewWindow>>
{
use tauri::Manager;
let value = Self::get().await?;
let value = Self::get()?;
Ok(value.app.get_webview_window("main"))
}
}
@@ -95,7 +88,7 @@ impl Drop for LoadingBarId {
fn drop(&mut self) {
let loader_uuid = self.0;
tokio::spawn(async move {
if let Ok(event_state) = EventState::get().await {
if let Ok(event_state) = EventState::get() {
#[cfg(any(feature = "tauri", feature = "cli"))]
if let Some((_, bar)) =
event_state.loading_bars.remove(&loader_uuid)
@@ -180,6 +173,11 @@ pub enum LoadingBarType {
import_location: PathBuf,
profile_name: String,
},
CheckingForUpdates,
LauncherUpdate {
version: String,
current_version: String,
},
}
#[derive(Serialize, Clone)]

View File

@@ -111,7 +111,7 @@ pub async fn download_version_info(
}?;
if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 5.0, None).await?;
emit_loading(loading_bar, 5.0, None)?;
}
tracing::debug!("Loaded version info for Minecraft {version_id}");
@@ -154,7 +154,7 @@ pub async fn download_client(
tracing::trace!("Fetched client version {version}");
}
if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 9.0, None).await?;
emit_loading(loading_bar, 9.0, None)?;
}
tracing::debug!("Client loaded for version {version}!");
@@ -196,7 +196,7 @@ pub async fn download_assets_index(
}?;
if let Some(loading_bar) = loading_bar {
emit_loading(loading_bar, 5.0, None).await?;
emit_loading(loading_bar, 5.0, None)?;
}
tracing::debug!("Assets index successfully loaded!");
Ok(res)

View File

@@ -336,8 +336,7 @@ pub async fn install_minecraft(
server => "";
}
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
.await?;
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))?;
let total_length = processors.len();
// Forge processors (90-100)
@@ -402,8 +401,7 @@ pub async fn install_minecraft(
"Running forge processor {}/{}",
index, total_length
)),
)
.await?;
)?;
}
}
}
@@ -414,7 +412,7 @@ pub async fn install_minecraft(
async { Ok(()) }
})
.await?;
emit_loading(&loading_bar, 1.0, Some("Finished installing")).await?;
emit_loading(&loading_bar, 1.0, Some("Finished installing"))?;
Ok(())
}

View File

@@ -20,6 +20,9 @@ mod state;
pub use api::*;
pub use error::*;
pub use event::{EventState, LoadingBar, LoadingBarType};
pub use event::{
emit::emit_loading, emit::init_loading, EventState, LoadingBar,
LoadingBarType,
};
pub use logger::start_logger;
pub use state::State;

View File

@@ -330,8 +330,7 @@ impl DirectoryInfo {
&loader_bar_id,
10.0 / (MOVE_DIRS.len() as f64),
None,
)
.await?;
)?;
}
let paths_len = paths.len();
@@ -380,8 +379,7 @@ impl DirectoryInfo {
&loader_bar_id,
90.0 / paths_len as f64,
None,
)
.await;
);
success_idxs.insert(idx);
@@ -433,8 +431,7 @@ impl DirectoryInfo {
&loader_bar_id,
((x.size as f64) / (total_size as f64)) * 60.0,
None,
)
.await;
);
Ok::<(), crate::Error>(())
}
@@ -453,8 +450,7 @@ impl DirectoryInfo {
&loader_bar_id,
30.0 / paths_len as f64,
None,
)
.await?;
)?;
Ok::<(), crate::Error>(())
};

View File

@@ -119,7 +119,7 @@ pub(crate) async fn watch_profile(
{
let path = profile_path.join(folder);
if !path.exists() {
if !path.exists() && !path.is_symlink() {
crate::util::io::create_dir_all(&path).await?;
}

View File

@@ -130,8 +130,7 @@ pub async fn fetch_advanced(
(chunk.len() as f64 / total_size as f64)
* total,
None,
)
.await?;
)?;
}
Ok(bytes::Bytes::from(bytes))

View File

@@ -886,7 +886,7 @@ a,
a {
cursor: pointer;
color: var(--color-link);
color: var(--color-blue);
&:focus-visible,
&:hover {

View File

@@ -797,7 +797,7 @@ function openVideoModal() {
.markdown-resource-link {
cursor: pointer;
color: var(--color-link);
color: var(--color-blue);
&:focus-visible,
&:hover {

View File

@@ -1,5 +1,5 @@
<template>
<NewModal ref="modal" :noblur="noblur" danger>
<NewModal ref="modal" :noblur="noblur" danger :on-hide="onHide">
<template #title>
<slot name="title">
<span class="font-extrabold text-contrast text-lg">{{ title }}</span>
@@ -76,6 +76,12 @@ const props = defineProps({
type: Boolean,
default: false,
},
onHide: {
type: Function,
default() {
return () => {}
},
},
})
const emit = defineEmits(['proceed'])

View File

@@ -58,6 +58,7 @@ const props = withDefaults(
closeOnEsc?: boolean
warnOnClose?: boolean
header?: string
onHide?: () => void
}>(),
{
type: true,
@@ -65,6 +66,7 @@ const props = withDefaults(
danger: false,
closeOnEsc: true,
warnOnClose: false,
onHide: () => {},
},
)
@@ -87,6 +89,7 @@ function show(event?: MouseEvent) {
}
function hide() {
props.onHide()
visible.value = false
window.removeEventListener('mousedown', updateMousePosition)
window.removeEventListener('keydown', handleKeyDown)

View File

@@ -34,6 +34,16 @@ const props = defineProps({
type: Boolean,
default: true,
},
noblur: {
type: Boolean,
default: false,
},
onHide: {
type: Function,
default() {
return () => {}
},
},
})
const shareModal = ref(null)
@@ -115,7 +125,7 @@ defineExpose({
</script>
<template>
<Modal ref="shareModal" :header="header">
<Modal ref="shareModal" :header="header" :noblur="noblur" :on-hide="onHide">
<div class="share-body">
<div v-if="link" class="qr-wrapper">
<div ref="qrCode">

15
pnpm-lock.yaml generated
View File

@@ -62,6 +62,9 @@ importers:
'@tauri-apps/plugin-shell':
specifier: ^2.0.0-rc.0
version: 2.0.0-rc.0
'@tauri-apps/plugin-updater':
specifier: ^2.0.0-rc.0
version: 2.0.0-rc.1
'@tauri-apps/plugin-window-state':
specifier: ^2.0.0-rc.0
version: 2.0.0-rc.0
@@ -1948,6 +1951,9 @@ packages:
'@tauri-apps/api@2.0.0-rc.3':
resolution: {integrity: sha512-k1erUfnoOFJwL5VNFZz0BQZ2agNstG7CNOjwpdWMl1vOaVuSn4DhJtXB0Deh9lZaaDlfrykKOyZs9c3XXpMi5Q==}
'@tauri-apps/api@2.0.0-rc.4':
resolution: {integrity: sha512-UNiIhhKG08j4ooss2oEEVexffmWkgkYlC2M3GcX3VPtNsqFgVNL8Mcw/4Y7rO9M9S+ffAMnLOF5ypzyuyb8tyg==}
'@tauri-apps/cli-darwin-arm64@2.0.0-rc.4':
resolution: {integrity: sha512-CSkGon9QAGymYhDng3GTiBuyrWAkqdGimL+3hzfAA4c0yMV87Y68IZ7mwzzbmRjweImvrhuNgw4ME94d1wnGPQ==}
engines: {node: '>= 10'}
@@ -2087,6 +2093,9 @@ packages:
'@tauri-apps/plugin-shell@2.0.0-rc.0':
resolution: {integrity: sha512-bhUcQcrqZoK8H1DFXapr5r1Z75oh6Kd5Tltz97XpZFLREEqp+KhN2Fvyh8r/fKAyenYsTYUIsDsyGdjdueuF9g==}
'@tauri-apps/plugin-updater@2.0.0-rc.1':
resolution: {integrity: sha512-SwQhDad/jnGIUe4eFfVBZ+0wRZthPSLwrFAl/K0Fyuu7dD/uGHS/jbATxZzwFDGfcpWOGLOsS8+LqSmY5zufHw==}
'@tauri-apps/plugin-window-state@2.0.0-rc.0':
resolution: {integrity: sha512-lR8reD+D1yIHT+53v56WltLS0+Y2zIkKqTuwrvz1yNbY5Hk4Z6foFV2Byo4kJAAvi5vbeGtvxYAjSiczZK5euw==}
@@ -7783,6 +7792,8 @@ snapshots:
'@tauri-apps/api@2.0.0-rc.3': {}
'@tauri-apps/api@2.0.0-rc.4': {}
'@tauri-apps/cli-darwin-arm64@2.0.0-rc.4':
optional: true
@@ -7881,6 +7892,10 @@ snapshots:
dependencies:
'@tauri-apps/api': 2.0.0-rc.3
'@tauri-apps/plugin-updater@2.0.0-rc.1':
dependencies:
'@tauri-apps/api': 2.0.0-rc.4
'@tauri-apps/plugin-window-state@2.0.0-rc.0':
dependencies:
'@tauri-apps/api': 2.0.0-rc.3