You've already forked AstralRinth
forked from xxxOFFxxx/AstralRinth
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:
21
.github/workflows/app-release.yml
vendored
21
.github/workflows/app-release.yml
vendored
@@ -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
90
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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...'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
49
apps/app-frontend/src/components/ui/modal/ModalWrapper.vue
Normal file
49
apps/app-frontend/src/components/ui/modal/ModalWrapper.vue
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
|
||||
@@ -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':
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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({})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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'))
|
||||
}
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -38,4 +38,4 @@
|
||||
"utils:default",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/app/capabilities/updater.json
Normal file
11
apps/app/capabilities/updater.json
Normal 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
@@ -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"]}}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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(())
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus"
|
||||
version = "0.8.4"
|
||||
version = "0.8.5"
|
||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||
edition = "2021"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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?;
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>(())
|
||||
};
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -886,7 +886,7 @@ a,
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: var(--color-link);
|
||||
color: var(--color-blue);
|
||||
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
|
||||
@@ -797,7 +797,7 @@ function openVideoModal() {
|
||||
|
||||
.markdown-resource-link {
|
||||
cursor: pointer;
|
||||
color: var(--color-link);
|
||||
color: var(--color-blue);
|
||||
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
|
||||
@@ -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'])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
15
pnpm-lock.yaml
generated
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user