feat(theseus): Update to Tauri v2 (#2178)

* feat(theseus): Initial migration to Tauri v2

* feat(theseus): Added a way to zoom / scale UI

* chore(theseus): Started cleaning up some plugins

* fix(theseus): Github Actions

* refactor(theseus): Reduced boilerplate & more work

* feat(theseus): Allow multiple app instances to be open at once (#995)

* fix(theseus): Lint & more

* fix(theseus): App Release github action

* fix(theseus): Open links in browser & macos builds

* fix(theseus): Rebase fixes

* fix(theseus): Updater & app release action

* fix(theseus): Fixed definitions in `build.rs`

* Fix MacOS deep linking, window decorations

* fix(theseus): Closing & maximizing app

* Fix macos build

* add back release conf

* acc fix build

* make updater for release builds only

* focus window on startup

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
Co-authored-by: Geometrically <18202329+Geometrically@users.noreply.github.com>
This commit is contained in:
Norbiros
2024-08-27 21:49:36 +02:00
committed by GitHub
parent 396f737612
commit d6a72fbfc4
83 changed files with 15614 additions and 2013 deletions
+3
View File
@@ -13,3 +13,6 @@ max_line_length = 100
[*.md] [*.md]
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false
[*.rs]
indent_size = 4
+11 -9
View File
@@ -20,12 +20,12 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
platform: [macos-latest, windows-latest, ubuntu-20.04] platform: [macos-latest, windows-latest, ubuntu-22.04]
runs-on: ${{ matrix.platform }} runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Rust setup (mac) - name: Rust setup (mac)
if: startsWith(matrix.platform, 'macos') if: startsWith(matrix.platform, 'macos')
@@ -49,7 +49,7 @@ jobs:
${{ runner.os }}-rust-target- ${{ runner.os }}-rust-target-
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v4
with: with:
node-version: 20 node-version: 20
@@ -66,7 +66,7 @@ jobs:
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
- name: Setup pnpm cache - name: Setup pnpm cache
uses: actions/cache@v3 uses: actions/cache@v4
with: with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
@@ -77,7 +77,7 @@ jobs:
if: startsWith(matrix.platform, 'ubuntu') if: startsWith(matrix.platform, 'ubuntu')
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libselinux1 sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Install frontend dependencies - name: Install frontend dependencies
run: pnpm install run: pnpm install
@@ -95,11 +95,12 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with: with:
args: "--target universal-apple-darwin --config ./apps/app/tauri-release.conf.json" args: "--target universal-apple-darwin --config ./apps/app/tauri-release.conf.json"
working-directory: ./apps/app working-directory: ./apps/app
tauriScript: pnpm --filter=@modrinth/app run tauri
- name: build app - name: build app
uses: tauri-apps/tauri-action@v0 uses: tauri-apps/tauri-action@v0
@@ -107,21 +108,22 @@ jobs:
if: "!startsWith(matrix.platform, 'macos')" if: "!startsWith(matrix.platform, 'macos')"
env: env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }} TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }} TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
with: with:
tauriScript: pnpm --filter=@modrinth/app run tauri
args: "--config ./apps/app/tauri-release.conf.json" args: "--config ./apps/app/tauri-release.conf.json"
working-directory: ./apps/app working-directory: ./apps/app
- name: upload ${{ matrix.platform }} - name: upload ${{ matrix.platform }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: startsWith(matrix.platform, 'macos') if: startsWith(matrix.platform, 'macos')
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
path: "${{ join(fromJSON(steps.build_os_mac.outputs.artifactPaths), '\n') }}" path: "${{ join(fromJSON(steps.build_os_mac.outputs.artifactPaths), '\n') }}"
- name: upload ${{ matrix.platform }} - name: upload ${{ matrix.platform }}
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v4
if: "!startsWith(matrix.platform, 'macos')" if: "!startsWith(matrix.platform, 'macos')"
with: with:
name: ${{ matrix.platform }} name: ${{ matrix.platform }}
+2 -2
View File
@@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
name: Build, Test, and Lint name: Build, Test, and Lint
runs-on: ubuntu-20.04 runs-on: ubuntu-22.04
steps: steps:
- name: Check out code - name: Check out code
@@ -30,7 +30,7 @@ jobs:
- name: Install build dependencies - name: Install build dependencies
run: | run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libselinux1 sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Setup Node.JS environment - name: Setup Node.JS environment
uses: actions/setup-node@v4 uses: actions/setup-node@v4
Generated
+1310 -906
View File
File diff suppressed because it is too large Load Diff
+6 -3
View File
@@ -13,14 +13,17 @@
"@modrinth/assets": "workspace:*", "@modrinth/assets": "workspace:*",
"@modrinth/ui": "workspace:*", "@modrinth/ui": "workspace:*",
"@modrinth/utils": "workspace:*", "@modrinth/utils": "workspace:*",
"@tauri-apps/api": "^1.6.0", "@tauri-apps/api": "^2.0.0-rc.3",
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
"@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",
"@vintl/vintl": "^4.4.1", "@vintl/vintl": "^4.4.1",
"dayjs": "^1.11.10", "dayjs": "^1.11.10",
"floating-vue": "^5.2.2", "floating-vue": "^5.2.2",
"mixpanel-browser": "^2.49.0", "mixpanel-browser": "^2.49.0",
"ofetch": "^1.3.4", "ofetch": "^1.3.4",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
"vite-svg-loader": "^5.1.0", "vite-svg-loader": "^5.1.0",
"vue": "^3.4.21", "vue": "^3.4.21",
"vue-multiselect": "3.0.0", "vue-multiselect": "3.0.0",
@@ -28,7 +31,7 @@
"vue-virtual-scroller": "v2.0.0-beta.8" "vue-virtual-scroller": "v2.0.0-beta.8"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.6.0", "@tauri-apps/cli": "^2.0.0-rc",
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^8.57.0", "eslint": "^8.57.0",
+21 -22
View File
@@ -15,8 +15,7 @@ import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
import { handleError, useNotifications } from '@/store/notifications.js' import { handleError, useNotifications } from '@/store/notifications.js'
import { command_listener, warning_listener } from '@/helpers/events.js' import { command_listener, warning_listener } from '@/helpers/events.js'
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons' import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
import { type } from '@tauri-apps/api/os' import { type } from '@tauri-apps/plugin-os'
import { appWindow } from '@tauri-apps/api/window'
import { isDev, getOS } from '@/helpers/utils.js' import { isDev, getOS } from '@/helpers/utils.js'
import { import {
mixpanel_track, mixpanel_track,
@@ -24,18 +23,20 @@ import {
mixpanel_opt_out_tracking, mixpanel_opt_out_tracking,
mixpanel_is_loaded, mixpanel_is_loaded,
} from '@/helpers/mixpanel' } from '@/helpers/mixpanel'
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api' import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import { window as TauriWindow } from '@tauri-apps/api'
import { TauriEvent } from '@tauri-apps/api/event' import { TauriEvent } from '@tauri-apps/api/event'
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import { install_from_file } from './helpers/pack' import { install_from_file } from './helpers/pack'
import { useError } from '@/store/error.js' import { useError } from '@/store/error.js'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue' import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue' import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue' import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
import { useInstall } from '@/store/install.js' import { useInstall } from '@/store/install.js'
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-shell'
import { get_opening_command, initialize_state } from '@/helpers/state' import { get_opening_command, initialize_state } from '@/helpers/state'
const themeStore = useTheming() const themeStore = useTheming()
@@ -57,6 +58,10 @@ const os = ref('')
const stateInitialized = ref(false) const stateInitialized = ref(false)
onMounted(async () => {
await useCheckDisableMouseover()
})
async function setupApp() { async function setupApp() {
stateInitialized.value = true stateInitialized.value = true
const { const {
@@ -79,7 +84,7 @@ async function setupApp() {
showOnboarding.value = !onboarded showOnboarding.value = !onboarded
nativeDecorations.value = native_decorations nativeDecorations.value = native_decorations
if (os.value !== 'MacOS') await appWindow.setDecorations(native_decorations) if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations)
themeStore.setThemeState(theme) themeStore.setThemeState(theme)
themeStore.collapsedNavigation = collapsed_navigation themeStore.collapsedNavigation = collapsed_navigation
@@ -93,7 +98,8 @@ async function setupApp() {
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault()) if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
if ((await type()) === 'Darwin') { const osType = await type()
if (osType === 'macos') {
document.getElementsByTagName('html')[0].classList.add('mac') document.getElementsByTagName('html')[0].classList.add('mac')
} else { } else {
document.getElementsByTagName('html')[0].classList.add('windows') document.getElementsByTagName('html')[0].classList.add('windows')
@@ -126,14 +132,9 @@ initialize_state()
}) })
const handleClose = async () => { const handleClose = async () => {
await saveWindowState(StateFlags.ALL) await getCurrentWindow().close()
await TauriWindow.getCurrent().close()
} }
TauriWindow.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
await handleClose()
})
const router = useRouter() const router = useRouter()
router.afterEach((to, from, failure) => { router.afterEach((to, from, failure) => {
if (mixpanel_is_loaded()) { if (mixpanel_is_loaded()) {
@@ -180,13 +181,7 @@ document.querySelector('body').addEventListener('click', function (e) {
!target.href.startsWith('http://localhost') && !target.href.startsWith('http://localhost') &&
!target.href.startsWith('https://tauri.localhost') !target.href.startsWith('https://tauri.localhost')
) { ) {
window.__TAURI_INVOKE__('tauri', { open(target.href)
__tauriModule: 'Shell',
message: {
cmd: 'open',
path: target.href,
},
})
} }
e.preventDefault() e.preventDefault()
break break
@@ -288,10 +283,14 @@ async function handleCommand(e) {
</section> </section>
</div> </div>
<section v-if="!nativeDecorations" class="window-controls"> <section v-if="!nativeDecorations" class="window-controls">
<Button class="titlebar-button" icon-only @click="() => appWindow.minimize()"> <Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
<MinimizeIcon /> <MinimizeIcon />
</Button> </Button>
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()"> <Button
class="titlebar-button"
icon-only
@click="() => getCurrentWindow().toggleMaximize()"
>
<MaximizeIcon /> <MaximizeIcon />
</Button> </Button>
<Button class="titlebar-button close" icon-only @click="handleClose"> <Button class="titlebar-button close" icon-only @click="handleClose">
@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets' import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
import { Button, OverflowMenu } from '@modrinth/ui' import { Button, OverflowMenu } from '@modrinth/ui'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { add_project_from_path } from '@/helpers/profile.js' import { add_project_from_path } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
@@ -4,7 +4,7 @@ import { Button, Checkbox, Modal } from '@modrinth/ui'
import { PackageIcon, VersionIcon } from '@/assets/icons' import { PackageIcon, VersionIcon } from '@/assets/icons'
import { ref } from 'vue' import { ref } from 'vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js' import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import { useTheming } from '@/store/theme' import { useTheming } from '@/store/theme'
@@ -3,7 +3,7 @@ import { onUnmounted, ref, computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { StopCircleIcon, PlayIcon } from '@modrinth/assets' import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui' import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/core'
import { kill, run } from '@/helpers/profile' import { kill, run } from '@/helpers/profile'
import { get_by_profile_path } from '@/helpers/process' import { get_by_profile_path } from '@/helpers/process'
import { process_listener } from '@/helpers/events' import { process_listener } from '@/helpers/events'
@@ -211,8 +211,8 @@ import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
import { computed, onUnmounted, ref, shallowRef } from 'vue' import { computed, onUnmounted, ref, shallowRef } from 'vue'
import { get_loaders } from '@/helpers/tags' import { get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile' import { create } from '@/helpers/profile'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { tauri } from '@tauri-apps/api' import { convertFileSrc } from '@tauri-apps/api/core'
import { get_game_versions, get_loader_versions } from '@/helpers/metadata' import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
@@ -382,7 +382,7 @@ const upload_icon = async () => {
}) })
if (!icon.value) return if (!icon.value) return
display_icon.value = tauri.convertFileSrc(icon.value) display_icon.value = convertFileSrc(icon.value)
} }
const reset_icon = () => { const reset_icon = () => {
@@ -63,7 +63,7 @@ import {
import { Button } from '@modrinth/ui' import { Button } from '@modrinth/ui'
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js' import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
import { ref } from 'vue' import { ref } from 'vue'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue' import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import { mixpanel_track } from '@/helpers/mixpanel' import { mixpanel_track } from '@/helpers/mixpanel'
import { handleError } from '@/store/state.js' import { handleError } from '@/store/state.js'
@@ -1,10 +1,10 @@
<template> <template>
<div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }"> <div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }">
<div v-if="os !== 'MacOS'" class="app-buttons"> <div v-if="os !== 'MacOS'" class="app-buttons">
<button class="btn icon-only transparent" icon-only @click="() => appWindow.minimize()"> <button class="btn icon-only transparent" icon-only @click="() => getCurrent().minimize()">
<MinimizeIcon /> <MinimizeIcon />
</button> </button>
<button class="btn icon-only transparent" @click="() => appWindow.toggleMaximize()"> <button class="btn icon-only transparent" @click="() => getCurrent().toggleMaximize()">
<MaximizeIcon /> <MaximizeIcon />
</button> </button>
<button class="btn icon-only transparent" @click="handleClose"> <button class="btn icon-only transparent" @click="handleClose">
@@ -85,12 +85,11 @@
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import ProgressBar from '@/components/ui/ProgressBar.vue' import ProgressBar from '@/components/ui/ProgressBar.vue'
import { loading_listener } from '@/helpers/events.js' import { loading_listener } from '@/helpers/events.js'
import { appWindow } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { XIcon } from '@modrinth/assets' import { XIcon } from '@modrinth/assets'
import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js' import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js'
import { window as TauriWindow } from '@tauri-apps/api'
import { TauriEvent } from '@tauri-apps/api/event' import { TauriEvent } from '@tauri-apps/api/event'
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api' import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { getOS } from '@/helpers/utils.js' import { getOS } from '@/helpers/utils.js'
import { useLoading } from '@/store/loading.js' import { useLoading } from '@/store/loading.js'
@@ -138,13 +137,8 @@ loading_listener(async (e) => {
}) })
const handleClose = async () => { const handleClose = async () => {
await saveWindowState(StateFlags.ALL) await getCurrentWindow().close()
await TauriWindow.getCurrent().close()
} }
TauriWindow.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
await handleClose()
})
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -16,13 +16,13 @@ import {
list, list,
create, create,
} from '@/helpers/profile' } from '@/helpers/profile'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { installVersionDependencies } from '@/store/install.js' import { installVersionDependencies } from '@/store/install.js'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import { mixpanel_track } from '@/helpers/mixpanel' import { mixpanel_track } from '@/helpers/mixpanel'
import { useTheming } from '@/store/theme.js' import { useTheming } from '@/store/theme.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { tauri } from '@tauri-apps/api' import { convertFileSrc } from '@tauri-apps/api/core'
const themeStore = useTheming() const themeStore = useTheming()
const router = useRouter() const router = useRouter()
@@ -153,7 +153,7 @@ const upload_icon = async () => {
}) })
if (!icon.value) return if (!icon.value) return
display_icon.value = tauri.convertFileSrc(icon.value) display_icon.value = convertFileSrc(icon.value)
} }
const reset_icon = () => { const reset_icon = () => {
@@ -0,0 +1,20 @@
import { invoke } from '@tauri-apps/api/core'
import cssContent from '@/assets/stylesheets/macFix.css?inline'
export async function useCheckDisableMouseover() {
try {
// Fetch the CSS content from the Rust backend
let should_disable_mouseover = await invoke('plugin:utils|should_disable_mouseover')
if (should_disable_mouseover) {
// Create a style element and set its content
const styleElement = document.createElement('style')
styleElement.innerHTML = cssContent
// Append the style element to the document's head
document.head.appendChild(styleElement)
}
} catch (error) {
console.error('Error checking OS version from Rust backend', error)
}
}
+30 -19
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
// Example function: // Example function:
// User goes to auth_url to complete flow, and when completed, authenticate_await_completion() returns the credentials // User goes to auth_url to complete flow, and when completed, authenticate_await_completion() returns the credentials
@@ -13,35 +13,46 @@ import { invoke } from '@tauri-apps/api/tauri'
// await authenticate_await_completion() // await authenticate_await_completion()
// } // }
/// Authenticate a user with Hydra - part 1 /**
/// This begins the authentication flow quasi-synchronously * Authenticate a user with Hydra - part 1.
/// This returns a DeviceLoginSuccess object, with two relevant fields: * This begins the authentication flow quasi-synchronously.
/// - verification_uri: the URL to go to to complete the flow *
/// - user_code: the code to enter on the verification_uri page * @returns {Promise<DeviceLoginSuccess>} A DeviceLoginSuccess object with two relevant fields:
* @property {string} verification_uri - The URL to go to complete the flow.
* @property {string} user_code - The code to enter on the verification_uri page.
*/
export async function login() { export async function login() {
return await invoke('auth_login') return await invoke('plugin:auth|login')
} }
/// Retrieves the default user /**
/// user is UUID * Retrieves the default user
* @return {Promise<UUID | undefined>}
*/
export async function get_default_user() { export async function get_default_user() {
return await invoke('plugin:auth|auth_get_default_user') return await invoke('plugin:auth|get_default_user')
} }
/// Updates the default user /**
/// user is UUID * Updates the default user
* @param {UUID} user
*/
export async function set_default_user(user) { export async function set_default_user(user) {
return await invoke('plugin:auth|auth_set_default_user', { user }) return await invoke('plugin:auth|set_default_user', { user })
} }
/// Remove a user account from the database /**
/// user is UUID * Remove a user account from the database
* @param {UUID} user
*/
export async function remove_user(user) { export async function remove_user(user) {
return await invoke('plugin:auth|auth_remove_user', { user }) return await invoke('plugin:auth|remove_user', { user })
} }
/// Returns a list of users /**
/// Returns an Array of Credentials * Returns a list of users
* @returns {Promise<Credential[]>}
*/
export async function users() { export async function users() {
return await invoke('plugin:auth|auth_users') return await invoke('plugin:auth|get_users')
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
export async function get_project(id, cacheBehaviour) { export async function get_project(id, cacheBehaviour) {
return await invoke('plugin:cache|get_project', { id, cacheBehaviour }) return await invoke('plugin:cache|get_project', { id, cacheBehaviour })
+5 -5
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
import { create } from './profile' import { create } from './profile'
/* /*
@@ -27,7 +27,7 @@ import { create } from './profile'
/// eg: get_importable_instances("MultiMC", "C:/MultiMC") /// eg: get_importable_instances("MultiMC", "C:/MultiMC")
/// returns ["Instance 1", "Instance 2"] /// returns ["Instance 1", "Instance 2"]
export async function get_importable_instances(launcherType, basePath) { export async function get_importable_instances(launcherType, basePath) {
return await invoke('plugin:import|import_get_importable_instances', { launcherType, basePath }) return await invoke('plugin:import|get_importable_instances', { launcherType, basePath })
} }
/// Import an instance from a launcher type and base path /// Import an instance from a launcher type and base path
@@ -38,7 +38,7 @@ export async function import_instance(launcherType, basePath, instanceFolder) {
// fs watching will be enabled once the instance is imported // fs watching will be enabled once the instance is imported
const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null, true) const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null, true)
return await invoke('plugin:import|import_import_instance', { return await invoke('plugin:import|import_instance', {
profilePath, profilePath,
launcherType, launcherType,
basePath, basePath,
@@ -49,7 +49,7 @@ export async function import_instance(launcherType, basePath, instanceFolder) {
/// Checks if this instance is valid for importing, given a certain launcher type /// Checks if this instance is valid for importing, given a certain launcher type
/// eg: is_valid_importable_instance("C:/MultiMC/Instance 1", "MultiMC") /// eg: is_valid_importable_instance("C:/MultiMC/Instance 1", "MultiMC")
export async function is_valid_importable_instance(instanceFolder, launcherType) { export async function is_valid_importable_instance(instanceFolder, launcherType) {
return await invoke('plugin:import|import_is_valid_importable_instance', { return await invoke('plugin:import|is_valid_importable_instance', {
instanceFolder, instanceFolder,
launcherType, launcherType,
}) })
@@ -59,5 +59,5 @@ export async function is_valid_importable_instance(instanceFolder, launcherType)
/// null if it can't be found or doesn't exist /// null if it can't be found or doesn't exist
/// eg: get_default_launcher_path("MultiMC") /// eg: get_default_launcher_path("MultiMC")
export async function get_default_launcher_path(launcherType) { export async function get_default_launcher_path(launcherType) {
return await invoke('plugin:import|import_get_default_launcher_path', { launcherType }) return await invoke('plugin:import|get_default_launcher_path', { launcherType })
} }
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
/* /*
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
/* /*
A log is a struct containing the filename string, stdout, and stderr, as follows: A log is a struct containing the filename string, stdout, and stderr, as follows:
+1 -1
View File
@@ -1,4 +1,4 @@
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
/// Gets the game versions from daedalus /// Gets the game versions from daedalus
// Returns a VersionManifest // Returns a VersionManifest
+6 -6
View File
@@ -3,22 +3,22 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
export async function login(provider) { export async function login(provider) {
return await invoke('modrinth_auth_login', { provider }) return await invoke('modrinth_auth_login', { provider })
} }
export async function login_pass(username, password, challenge) { export async function login_pass(username, password, challenge) {
return await invoke('plugin:mr_auth|login_pass', { username, password, challenge }) return await invoke('plugin:mr-auth|login_pass', { username, password, challenge })
} }
export async function login_2fa(code, flow) { export async function login_2fa(code, flow) {
return await invoke('plugin:mr_auth|login_2fa', { code, flow }) return await invoke('plugin:mr-auth|login_2fa', { code, flow })
} }
export async function create_account(username, email, password, challenge, signUpNewsletter) { export async function create_account(username, email, password, challenge, signUpNewsletter) {
return await invoke('plugin:mr_auth|create_account', { return await invoke('plugin:mr-auth|create_account', {
username, username,
email, email,
password, password,
@@ -28,9 +28,9 @@ export async function create_account(username, email, password, challenge, signU
} }
export async function logout() { export async function logout() {
return await invoke('plugin:mr_auth|logout') return await invoke('plugin:mr-auth|logout')
} }
export async function get() { export async function get() {
return await invoke('plugin:mr_auth|get') return await invoke('plugin:mr-auth|get')
} }
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
import { create } from './profile' import { create } from './profile'
// Installs pack from a version ID // Installs pack from a version ID
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
/// Gets all running process IDs with a given profile path /// Gets all running process IDs with a given profile path
/// Returns [u32] /// Returns [u32]
+3 -3
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
/// Add instance /// Add instance
/* /*
@@ -19,7 +19,7 @@ import { invoke } from '@tauri-apps/api/tauri'
export async function create(name, gameVersion, modloader, loaderVersion, iconPath, skipInstall) { export async function create(name, gameVersion, modloader, loaderVersion, iconPath, skipInstall) {
//Trim string name to avoid "Unable to find directory" //Trim string name to avoid "Unable to find directory"
name = name.trim() name = name.trim()
return await invoke('plugin:profile_create|profile_create', { return await invoke('plugin:profile-create|profile_create', {
name, name,
gameVersion, gameVersion,
modloader, modloader,
@@ -31,7 +31,7 @@ export async function create(name, gameVersion, modloader, loaderVersion, iconPa
// duplicate a profile // duplicate a profile
export async function duplicate(path) { export async function duplicate(path) {
return await invoke('plugin:profile_create|profile_duplicate', { path }) return await invoke('plugin:profile-create|profile_duplicate', { path })
} }
// Remove a profile // Remove a profile
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
// Settings object // Settings object
/* /*
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
// Initialize the theseus API state // Initialize the theseus API state
// This should be called during the initializion/opening of the launcher // This should be called during the initializion/opening of the launcher
+1 -1
View File
@@ -3,7 +3,7 @@
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized, * So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
// Gets cached category tags // Gets cached category tags
export async function get_categories() { export async function get_categories() {
+1 -1
View File
@@ -1,5 +1,5 @@
import { get_full_path, get_mod_full_path } from '@/helpers/profile' import { get_full_path, get_mod_full_path } from '@/helpers/profile'
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/core'
export async function isDev() { export async function isDev() {
return await invoke('is_dev') return await invoke('is_dev')
-2
View File
@@ -4,7 +4,6 @@ import App from '@/App.vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import FloatingVue from 'floating-vue' import FloatingVue from 'floating-vue'
import 'floating-vue/dist/style.css' import 'floating-vue/dist/style.css'
import loadCssMixin from './mixins/macCssFix.js'
import { createPlugin } from '@vintl/vintl/plugin' import { createPlugin } from '@vintl/vintl/plugin'
const VIntlPlugin = createPlugin({ const VIntlPlugin = createPlugin({
@@ -30,7 +29,6 @@ let app = createApp(App)
app.use(router) app.use(router)
app.use(pinia) app.use(pinia)
app.use(FloatingVue) app.use(FloatingVue)
app.mixin(loadCssMixin)
app.use(VIntlPlugin) app.use(VIntlPlugin)
app.mount('#app') app.mount('#app')
-27
View File
@@ -1,27 +0,0 @@
import { invoke } from '@tauri-apps/api/tauri'
import cssContent from '@/assets/stylesheets/macFix.css?inline'
export default {
async mounted() {
await this.checkDisableMouseover()
},
methods: {
async checkDisableMouseover() {
try {
// Fetch the CSS content from the Rust backend
const should_disable_mouseover = await invoke('plugin:utils|should_disable_mouseover')
if (should_disable_mouseover) {
// Create a style element and set its content
const styleElement = document.createElement('style')
styleElement.innerHTML = cssContent
// Append the style element to the document's head
document.head.appendChild(styleElement)
}
} catch (error) {
console.error('Error checking OS version from Rust backend', error)
}
},
},
}
+1 -1
View File
@@ -19,7 +19,7 @@ import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import SearchCard from '@/components/ui/SearchCard.vue' import SearchCard from '@/components/ui/SearchCard.vue'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js' import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/core'
import { get_search_results } from '@/helpers/cache.js' import { get_search_results } from '@/helpers/cache.js'
import { debounce } from '@/helpers/utils.js' import { debounce } from '@/helpers/utils.js'
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue' import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
+1 -1
View File
@@ -9,7 +9,7 @@ import { get as getCreds, logout } from '@/helpers/mr_auth.js'
import JavaSelector from '@/components/ui/JavaSelector.vue' import JavaSelector from '@/components/ui/JavaSelector.vue'
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue' import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel' import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { getOS } from '@/helpers/utils.js' import { getOS } from '@/helpers/utils.js'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import { get_user, purge_cache_types } from '@/helpers/cache.js' import { get_user, purge_cache_types } from '@/helpers/cache.js'
@@ -132,7 +132,7 @@ import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
import { showProfileInFolder } from '@/helpers/utils.js' import { showProfileInFolder } from '@/helpers/utils.js'
import ContextMenu from '@/components/ui/ContextMenu.vue' import ContextMenu from '@/components/ui/ContextMenu.vue'
import { mixpanel_track } from '@/helpers/mixpanel' import { mixpanel_track } from '@/helpers/mixpanel'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/core'
import { useFetch } from '@/helpers/fetch' import { useFetch } from '@/helpers/fetch'
import { handleSevereError } from '@/store/error.js' import { handleSevereError } from '@/store/error.js'
import { get_project, get_version_many } from '@/helpers/cache.js' import { get_project, get_version_many } from '@/helpers/cache.js'
@@ -541,8 +541,8 @@ import { computed, readonly, ref, shallowRef, watch } from 'vue'
import { get_max_memory } from '@/helpers/jre.js' import { get_max_memory } from '@/helpers/jre.js'
import { get } from '@/helpers/settings.js' import { get } from '@/helpers/settings.js'
import JavaSelector from '@/components/ui/JavaSelector.vue' import JavaSelector from '@/components/ui/JavaSelector.vue'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/api/dialog' import { open } from '@tauri-apps/plugin-dialog'
import { get_loader_versions } from '@/helpers/metadata.js' import { get_loader_versions } from '@/helpers/metadata.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js' import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
@@ -257,7 +257,7 @@ import { useRoute } from 'vue-router'
import { ref, shallowRef, watch } from 'vue' import { ref, shallowRef, watch } from 'vue'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useBreadcrumbs } from '@/store/breadcrumbs'
import { handleError } from '@/store/notifications.js' import { handleError } from '@/store/notifications.js'
import { convertFileSrc } from '@tauri-apps/api/tauri' import { convertFileSrc } from '@tauri-apps/api/core'
import ContextMenu from '@/components/ui/ContextMenu.vue' import ContextMenu from '@/components/ui/ContextMenu.vue'
import { install as installVersion } from '@/store/install.js' import { install as installVersion } from '@/store/install.js'
import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js' import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js'
+1 -1
View File
@@ -139,7 +139,7 @@ export default new createRouter({
linkExactActiveClass: 'router-link-exact-active', linkExactActiveClass: 'router-link-exact-active',
scrollBehavior() { scrollBehavior() {
// Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux) // Sometimes Vue's scroll behavior is not working as expected, so we need to manually scroll to top (especially on Linux)
document.querySelector('.router-view').scrollTop = 0 document.querySelector('.router-view')?.scrollTo(0, 0)
return { return {
el: '.router-view', el: '.router-view',
top: 0, top: 0,
+1 -2
View File
@@ -10,14 +10,13 @@ theseus = { path = "../../packages/app-lib", features = ["cli"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.7.1", features = ["shell-open"] } tauri = "2.0.0-rc.4"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
thiserror = "1.0" thiserror = "1.0"
url = "2.2" url = "2.2"
webbrowser = "0.8.13" webbrowser = "0.8.13"
dunce = "1.0.3" dunce = "1.0.3"
tokio-stream = { version = "0.1", features = ["fs"] }
futures = "0.3" futures = "0.3"
uuid = { version = "1.1", features = ["serde", "v4"] } uuid = { version = "1.1", features = ["serde", "v4"] }
+13 -12
View File
@@ -1,17 +1,14 @@
[package] [package]
name = "theseus_gui" name = "theseus_gui"
version = "0.8.3-1" version = "0.8.3-1"
description = "A Tauri App" description = "The Modrinth App is a desktop application for managing your Minecraft mods"
authors = ["you"] license = "GPL-3.0-only"
license = "" repository = "https://github.com/modrinth/code/apps/app/"
repository = ""
edition = "2021" edition = "2021"
build = "build.rs" build = "build.rs"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[build-dependencies] [build-dependencies]
tauri-build = { version = "1.5.3", features = [] } tauri-build = { version = "2.0.0-rc", features = ["codegen"] }
[dependencies] [dependencies]
theseus = { path = "../../packages/app-lib", features = ["tauri"] } theseus = { path = "../../packages/app-lib", features = ["tauri"] }
@@ -19,14 +16,16 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
serde_json = "1.0" serde_json = "1.0"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
tauri = { version = "1.7.1", features = [ "app-all", "devtools", "dialog", "dialog-confirm", "dialog-open", "macos-private-api", "os-all", "protocol-asset", "shell-open", "window-close", "window-create", "window-hide", "window-maximize", "window-minimize", "window-set-decorations", "window-show", "window-start-dragging", "window-unmaximize", "window-unminimize"] } tauri = { version = "2.0.0-rc.6", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
tauri-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-window-state = "2.0.0-rc"
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" } tauri-plugin-deep-link = "2.0.0-rc"
tauri-plugin-deep-link = "0.1.2" 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 }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
thiserror = "1.0" thiserror = "1.0"
tokio-stream = { version = "0.1", features = ["fs"] }
futures = "0.3" futures = "0.3"
daedalus = "0.2.3" daedalus = "0.2.3"
chrono = "0.4.26" chrono = "0.4.26"
@@ -56,6 +55,7 @@ window-shadows = "0.2.1"
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
cocoa = "0.25.0" cocoa = "0.25.0"
objc = "0.2.7" objc = "0.2.7"
rand = "0.8.5"
[features] [features]
# by default Tauri runs in production mode # by default Tauri runs in production mode
@@ -64,3 +64,4 @@ default = ["custom-protocol"]
# this feature is used for production builds where `devPath` points to the filesystem # this feature is used for production builds where `devPath` points to the filesystem
# DO NOT remove this # DO NOT remove this
custom-protocol = ["tauri/custom-protocol"] custom-protocol = ["tauri/custom-protocol"]
updater = ["dep:tauri-plugin-updater"]
+221 -2
View File
@@ -1,4 +1,223 @@
use tauri_build::{DefaultPermissionRule, InlinedPlugin};
fn main() { fn main() {
// Build the Tauri app // Sadly, there is no better way to do it right now
tauri_build::build(); // You could try parsing source code here and detecting #[tauri::command]
// But I think it's not worth it
// https://github.com/tauri-apps/tauri/issues/10075
tauri_build::try_build(
tauri_build::Attributes::new()
.codegen(tauri_build::CodegenContext::new())
.plugin(
"auth",
InlinedPlugin::new()
.commands(&[
"login",
"remove_user",
"get_default_user",
"set_default_user",
"get_users",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"cache",
InlinedPlugin::new()
.commands(&[
"get_project",
"get_project_many",
"get_version",
"get_version_many",
"get_user",
"get_user_many",
"get_team",
"get_team_many",
"get_organization",
"get_organization_many",
"get_search_results",
"get_search_results_many",
"purge_cache_types",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"import",
InlinedPlugin::new()
.commands(&[
"get_importable_instances",
"import_instance",
"is_valid_importable_instance",
"get_default_launcher_path",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"jre",
InlinedPlugin::new()
.commands(&[
"get_java_versions",
"set_java_versions",
"jre_find_filtered_jres",
"jre_get_jre",
"jre_test_jre",
"jre_auto_install_java",
"jre_get_max_memory",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"logs",
InlinedPlugin::new()
.commands(&[
"logs_get_logs",
"logs_get_logs_by_filename",
"logs_get_output_by_filename",
"logs_delete_logs",
"logs_delete_logs_by_filename",
"logs_get_latest_log_cursor",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"metadata",
InlinedPlugin::new()
.commands(&[
"metadata_get_game_versions",
"metadata_get_loader_versions",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"mr-auth",
InlinedPlugin::new()
.commands(&[
"login_pass",
"login_2fa",
"create_account",
"logout",
"get",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"pack",
InlinedPlugin::new()
.commands(&["pack_install", "pack_get_profile_from_pack"])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"process",
InlinedPlugin::new()
.commands(&[
"process_get_all",
"process_get_by_profile_path",
"process_kill",
"process_wait_for",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"profile",
InlinedPlugin::new()
.commands(&[
"profile_remove",
"profile_get",
"profile_get_many",
"profile_get_projects",
"profile_get_optimal_jre_key",
"profile_get_full_path",
"profile_get_mod_full_path",
"profile_list",
"profile_check_installed",
"profile_install",
"profile_update_all",
"profile_update_project",
"profile_add_project_from_version",
"profile_add_project_from_path",
"profile_toggle_disable_project",
"profile_remove_project",
"profile_update_managed_modrinth_version",
"profile_repair_managed_modrinth",
"profile_run",
"profile_run_credentials",
"profile_kill",
"profile_edit",
"profile_edit_icon",
"profile_export_mrpack",
"profile_get_pack_export_candidates",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"profile-create",
InlinedPlugin::new()
.commands(&["profile_create", "profile_duplicate"])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"settings",
InlinedPlugin::new()
.commands(&[
"settings_get",
"settings_set",
"cancel_directory_change",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"tags",
InlinedPlugin::new()
.commands(&[
"tags_get_categories",
"tags_get_report_types",
"tags_get_loaders",
"tags_get_game_versions",
"tags_get_donation_platforms",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
)
.plugin(
"utils",
InlinedPlugin::new()
.commands(&[
"get_os",
"should_disable_mouseover",
"highlight_in_folder",
"open_path",
"show_launcher_logs_folder",
"progress_bars_list",
"get_opening_command",
])
.default_permission(
DefaultPermissionRule::AllowAllCommands,
),
),
)
.expect("Failed to run tauri-build");
} }
+30
View File
@@ -0,0 +1,30 @@
{
"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"
]
}
+40
View File
@@ -0,0 +1,40 @@
{
"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"
]
}
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
{"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"]}}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+4 -3
View File
@@ -2,16 +2,17 @@
"name": "@modrinth/app", "name": "@modrinth/app",
"scripts": { "scripts": {
"build": "tauri build", "build": "tauri build",
"tauri": "tauri",
"dev": "tauri dev", "dev": "tauri dev",
"test": "cargo test", "test": "cargo test",
"lint": "cargo fmt --check && cargo clippy -- -D warnings", "lint": "cargo fmt --check && cargo clippy -- -D warnings",
"fix": "cargo fmt && cargo clippy --fix" "fix": "cargo fmt && cargo clippy --fix"
}, },
"devDependencies": { "devDependencies": {
"@tauri-apps/cli": "^1.6.0" "@tauri-apps/cli": "2.0.0-rc.5"
}, },
"dependencies": { "dependencies": {
"@modrinth/app-lib": "workspace:*", "@modrinth/app-frontend": "workspace:*",
"@modrinth/app-frontend": "workspace:*" "@modrinth/app-lib": "workspace:*"
} }
} }
+20 -18
View File
@@ -1,16 +1,17 @@
use crate::api::Result; use crate::api::Result;
use chrono::{Duration, Utc}; use chrono::{Duration, Utc};
use tauri::plugin::TauriPlugin; use tauri::plugin::TauriPlugin;
use tauri::{Manager, UserAttentionType}; use tauri::{Manager, Runtime, UserAttentionType};
use theseus::prelude::*; use theseus::prelude::*;
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> { pub fn init<R: Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("auth") tauri::plugin::Builder::<R>::new("auth")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
auth_get_default_user, login,
auth_set_default_user, remove_user,
auth_remove_user, get_default_user,
auth_users, set_default_user,
get_users,
]) ])
.build() .build()
} }
@@ -18,19 +19,21 @@ pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
/// Authenticate a user with Hydra - part 1 /// Authenticate a user with Hydra - part 1
/// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at) /// This begins the authentication flow quasi-synchronously, returning a URL to visit (that the user will sign in at)
#[tauri::command] #[tauri::command]
pub async fn auth_login(app: tauri::AppHandle) -> Result<Option<Credentials>> { pub async fn login<R: Runtime>(
app: tauri::AppHandle<R>,
) -> Result<Option<Credentials>> {
let flow = minecraft_auth::begin_login().await?; let flow = minecraft_auth::begin_login().await?;
let start = Utc::now(); let start = Utc::now();
if let Some(window) = app.get_window("signin") { if let Some(window) = app.get_webview_window("signin") {
window.close()?; window.close()?;
} }
let window = tauri::WindowBuilder::new( let window = tauri::WebviewWindowBuilder::new(
&app, &app,
"signin", "signin",
tauri::WindowUrl::External(flow.redirect_uri.parse().map_err( tauri::WebviewUrl::External(flow.redirect_uri.parse().map_err(
|_| { |_| {
theseus::ErrorKind::OtherError( theseus::ErrorKind::OtherError(
"Error parsing auth redirect URL".to_string(), "Error parsing auth redirect URL".to_string(),
@@ -53,12 +56,12 @@ pub async fn auth_login(app: tauri::AppHandle) -> Result<Option<Credentials>> {
} }
if window if window
.url() .url()?
.as_str() .as_str()
.starts_with("https://login.live.com/oauth20_desktop.srf") .starts_with("https://login.live.com/oauth20_desktop.srf")
{ {
if let Some((_, code)) = if let Some((_, code)) =
window.url().query_pairs().find(|x| x.0 == "code") window.url()?.query_pairs().find(|x| x.0 == "code")
{ {
window.close()?; window.close()?;
let val = let val =
@@ -75,23 +78,22 @@ pub async fn auth_login(app: tauri::AppHandle) -> Result<Option<Credentials>> {
Ok(None) Ok(None)
} }
#[tauri::command] #[tauri::command]
pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> { pub async fn remove_user(user: uuid::Uuid) -> Result<()> {
Ok(minecraft_auth::remove_user(user).await?) Ok(minecraft_auth::remove_user(user).await?)
} }
#[tauri::command] #[tauri::command]
pub async fn auth_get_default_user() -> Result<Option<uuid::Uuid>> { pub async fn get_default_user() -> Result<Option<uuid::Uuid>> {
Ok(minecraft_auth::get_default_user().await?) Ok(minecraft_auth::get_default_user().await?)
} }
#[tauri::command] #[tauri::command]
pub async fn auth_set_default_user(user: uuid::Uuid) -> Result<()> { pub async fn set_default_user(user: uuid::Uuid) -> Result<()> {
Ok(minecraft_auth::set_default_user(user).await?) Ok(minecraft_auth::set_default_user(user).await?)
} }
/// Get a copy of the list of all user credentials /// Get a copy of the list of all user credentials
// invoke('plugin:auth|auth_users',user)
#[tauri::command] #[tauri::command]
pub async fn auth_users() -> Result<Vec<Credentials>> { pub async fn get_users() -> Result<Vec<Credentials>> {
Ok(minecraft_auth::users().await?) Ok(minecraft_auth::users().await?)
} }
+8 -8
View File
@@ -8,10 +8,10 @@ use theseus::pack::import;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> { pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("import") tauri::plugin::Builder::new("import")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
import_get_importable_instances, get_importable_instances,
import_import_instance, import_instance,
import_is_valid_importable_instance, is_valid_importable_instance,
import_get_default_launcher_path, get_default_launcher_path,
]) ])
.build() .build()
} }
@@ -20,7 +20,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
/// eg: get_importable_instances(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC")) /// eg: get_importable_instances(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"))
/// returns ["Instance 1", "Instance 2"] /// returns ["Instance 1", "Instance 2"]
#[tauri::command] #[tauri::command]
pub async fn import_get_importable_instances( pub async fn get_importable_instances(
launcher_type: ImportLauncherType, launcher_type: ImportLauncherType,
base_path: PathBuf, base_path: PathBuf,
) -> Result<Vec<String>> { ) -> Result<Vec<String>> {
@@ -31,7 +31,7 @@ pub async fn import_get_importable_instances(
/// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted /// profile_path should be a blank profile for this purpose- if the function fails, it will be deleted
/// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1") /// eg: import_instance(ImportLauncherType::MultiMC, PathBuf::from("C:/MultiMC"), "Instance 1")
#[tauri::command] #[tauri::command]
pub async fn import_import_instance( pub async fn import_instance(
profile_path: &str, profile_path: &str,
launcher_type: ImportLauncherType, launcher_type: ImportLauncherType,
base_path: PathBuf, base_path: PathBuf,
@@ -50,7 +50,7 @@ pub async fn import_import_instance(
/// Checks if this instance is valid for importing, given a certain launcher type /// Checks if this instance is valid for importing, given a certain launcher type
/// eg: is_valid_importable_instance(PathBuf::from("C:/MultiMC/Instance 1"), ImportLauncherType::MultiMC) /// eg: is_valid_importable_instance(PathBuf::from("C:/MultiMC/Instance 1"), ImportLauncherType::MultiMC)
#[tauri::command] #[tauri::command]
pub async fn import_is_valid_importable_instance( pub async fn is_valid_importable_instance(
instance_folder: PathBuf, instance_folder: PathBuf,
launcher_type: ImportLauncherType, launcher_type: ImportLauncherType,
) -> Result<bool> { ) -> Result<bool> {
@@ -63,7 +63,7 @@ pub async fn import_is_valid_importable_instance(
/// Returns the default path for the given launcher type /// Returns the default path for the given launcher type
/// None if it can't be found or doesn't exist /// None if it can't be found or doesn't exist
#[tauri::command] #[tauri::command]
pub async fn import_get_default_launcher_path( pub async fn get_default_launcher_path(
launcher_type: ImportLauncherType, launcher_type: ImportLauncherType,
) -> Result<Option<PathBuf>> { ) -> Result<Option<PathBuf>> {
Ok(import::get_default_launcher_path(launcher_type)) Ok(import::get_default_launcher_path(launcher_type))
-12
View File
@@ -39,10 +39,6 @@ pub enum TheseusSerializableError {
#[error("Tauri error: {0}")] #[error("Tauri error: {0}")]
Tauri(#[from] tauri::Error), Tauri(#[from] tauri::Error),
#[cfg(target_os = "macos")]
#[error("Callback error: {0}")]
Callback(String),
} }
// Generic implementation of From<T> for ErrorTypeA // Generic implementation of From<T> for ErrorTypeA
@@ -90,14 +86,6 @@ macro_rules! impl_serialize {
} }
// Use the macro to implement Serialize for TheseusSerializableError // Use the macro to implement Serialize for TheseusSerializableError
#[cfg(target_os = "macos")]
impl_serialize! {
IO,
Tauri,
Callback
}
#[cfg(not(target_os = "macos"))]
impl_serialize! { impl_serialize! {
IO, IO,
Tauri, Tauri,
+6 -6
View File
@@ -5,7 +5,7 @@ use tauri::{Manager, UserAttentionType};
use theseus::prelude::*; use theseus::prelude::*;
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> { pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
tauri::plugin::Builder::new("mr_auth") tauri::plugin::Builder::new("mr-auth")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
login_pass, login_pass,
login_2fa, login_2fa,
@@ -25,14 +25,14 @@ pub async fn modrinth_auth_login(
let start = Utc::now(); let start = Utc::now();
if let Some(window) = app.get_window("modrinth-signin") { if let Some(window) = app.get_webview_window("modrinth-signin") {
window.close()?; window.close()?;
} }
let window = tauri::WindowBuilder::new( let window = tauri::WebviewWindowBuilder::new(
&app, &app,
"modrinth-signin", "modrinth-signin",
tauri::WindowUrl::External(redirect_uri.parse().map_err(|_| { tauri::WebviewUrl::External(redirect_uri.parse().map_err(|_| {
theseus::ErrorKind::OtherError( theseus::ErrorKind::OtherError(
"Error parsing auth redirect URL".to_string(), "Error parsing auth redirect URL".to_string(),
) )
@@ -53,12 +53,12 @@ pub async fn modrinth_auth_login(
} }
if window if window
.url() .url()?
.as_str() .as_str()
.starts_with("https://launcher-files.modrinth.com/detect.txt") .starts_with("https://launcher-files.modrinth.com/detect.txt")
{ {
let query = window let query = window
.url() .url()?
.query_pairs() .query_pairs()
.map(|(key, val)| { .map(|(key, val)| {
( (
+3 -3
View File
@@ -2,7 +2,7 @@ use crate::api::Result;
use theseus::prelude::*; use theseus::prelude::*;
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> { pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
tauri::plugin::Builder::new("profile_create") tauri::plugin::Builder::new("profile-create")
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![
profile_create, profile_create,
profile_duplicate profile_duplicate
@@ -11,7 +11,7 @@ pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
} }
// Creates a profile at the given filepath and adds it to the in-memory state // Creates a profile at the given filepath and adds it to the in-memory state
// invoke('plugin:profile_create|profile_add',profile) // invoke('plugin:profile-create|profile_add',profile)
#[tauri::command] #[tauri::command]
pub async fn profile_create( pub async fn profile_create(
name: String, // the name of the profile, and relative path name: String, // the name of the profile, and relative path
@@ -35,7 +35,7 @@ pub async fn profile_create(
} }
// Creates a profile from a duplicate // Creates a profile from a duplicate
// invoke('plugin:profile_create|profile_duplicate',profile) // invoke('plugin:profile-create|profile_duplicate',profile)
#[tauri::command] #[tauri::command]
pub async fn profile_duplicate(path: &str) -> Result<String> { pub async fn profile_duplicate(path: &str) -> Result<String> {
let res = profile::create::profile_create_from_duplicate(path).await?; let res = profile::create::profile_create_from_duplicate(path).await?;
-98
View File
@@ -1,98 +0,0 @@
use cocoa::{
base::{id, nil},
foundation::NSAutoreleasePool,
};
use objc::{
class,
declare::ClassDecl,
msg_send,
runtime::{Class, Object, Sel},
sel, sel_impl,
};
use once_cell::sync::OnceCell;
use crate::api::TheseusSerializableError;
type Callback = OnceCell<Box<dyn Fn(String) + Send + Sync + 'static>>;
static CALLBACK: Callback = OnceCell::new();
pub struct AppDelegateClass(pub *const Class);
unsafe impl Send for AppDelegateClass {}
unsafe impl Sync for AppDelegateClass {}
// Obj C class for the app delegate
// This inherits from the TaoAppDelegate (used by tauri) so we do not accidentally override any functionality
// The application_open_file method is the only method we override, as it is currently unimplemented in tauri
lazy_static::lazy_static! {
pub static ref THESEUS_APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
let superclass = class!(TaoAppDelegate);
let mut decl = ClassDecl::new("TheseusAppDelegate", superclass).unwrap();
// Add the method to the class
decl.add_method(
sel!(application:openFile:),
application_open_file as extern "C" fn(&Object, Sel, id, id) -> bool,
);
// Other methods are inherited
AppDelegateClass(decl.register())
};
}
extern "C" fn application_open_file(
_: &Object,
_: Sel,
_: id,
file: id,
) -> bool {
let file = nsstring_to_string(file);
callback(file)
}
pub fn callback(file: String) -> bool {
if let Some(callback) = CALLBACK.get() {
callback(file);
true
} else {
false
}
}
pub fn register_open_file<T>(
callback: T,
) -> Result<(), TheseusSerializableError>
where
T: Fn(String) + Send + Sync + 'static,
{
unsafe {
// Modified from tao: https://github.com/tauri-apps/tao
// sets the current app delegate to be the inherited app delegate rather than the default tauri/tao one
let app: id = msg_send![class!(TaoApp), sharedApplication];
let delegate: id = msg_send![THESEUS_APP_DELEGATE_CLASS.0, new];
let pool = NSAutoreleasePool::new(nil);
let _: () = msg_send![app, setDelegate: delegate];
let _: () = msg_send![pool, drain];
}
CALLBACK.set(Box::new(callback)).map_err(|_| {
TheseusSerializableError::Callback("Callback already set".to_string())
})
}
/// Convert an NSString to a Rust `String`
/// From 'fruitbasket' https://github.com/mrmekon/fruitbasket/
#[allow(clippy::cmp_null)]
pub fn nsstring_to_string(nsstring: *mut Object) -> String {
unsafe {
let cstr: *const i8 = msg_send![nsstring, UTF8String];
if cstr != std::ptr::null() {
std::ffi::CStr::from_ptr(cstr)
.to_string_lossy()
.into_owned()
} else {
"".into()
}
}
}
-1
View File
@@ -1,3 +1,2 @@
pub mod deep_link; pub mod deep_link;
pub mod delegate;
pub mod window_ext; pub mod window_ext;
+400 -58
View File
@@ -1,70 +1,412 @@
/// from: https://github.com/tauri-apps/tauri/issues/4789, full credit to haasal // Stolen from https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9
#[cfg(target_os = "macos")] use objc::{msg_send, sel, sel_impl};
use tauri::{Runtime, Window}; use rand::{distributions::Alphanumeric, Rng};
use tauri::{
plugin::{Builder, TauriPlugin},
Emitter, Runtime, Window,
}; // 0.8
#[cfg(target_os = "macos")] const WINDOW_CONTROL_PAD_X: f64 = 9.0;
pub trait WindowExt { const WINDOW_CONTROL_PAD_Y: f64 = 16.0;
fn set_transparent_titlebar(&self, transparent: bool);
fn position_traffic_lights(&self, x: f64, y: f64); struct UnsafeWindowHandle(*mut std::ffi::c_void);
unsafe impl Send for UnsafeWindowHandle {}
unsafe impl Sync for UnsafeWindowHandle {}
pub fn init<R: Runtime>() -> TauriPlugin<R> {
Builder::new("traffic_light_positioner")
.on_window_ready(|window| {
#[cfg(target_os = "macos")]
setup_traffic_light_positioner(window);
})
.build()
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
impl<R: Runtime> WindowExt for Window<R> { fn position_traffic_lights(
fn set_transparent_titlebar(&self, transparent: bool) { ns_window_handle: UnsafeWindowHandle,
use cocoa::appkit::{NSWindow, NSWindowTitleVisibility}; x: f64,
let window = self.ns_window().unwrap() as cocoa::base::id; y: f64,
) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect;
let ns_window = ns_window_handle.0 as cocoa::base::id;
unsafe {
let close = ns_window
.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize = ns_window
.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
let zoom =
ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
unsafe { let title_bar_container_view = close.superview().superview();
window.setTitleVisibility_(
NSWindowTitleVisibility::NSWindowTitleHidden,
);
if transparent { let close_rect: NSRect = msg_send![close, frame];
window.setTitlebarAppearsTransparent_(cocoa::base::YES); let button_height = close_rect.size.height;
} else {
window.setTitlebarAppearsTransparent_(cocoa::base::NO);
}
}
}
fn position_traffic_lights(&self, x: f64, y: f64) { let title_bar_frame_height = button_height + y;
use cocoa::appkit::{NSView, NSWindow, NSWindowButton}; let mut title_bar_rect = NSView::frame(title_bar_container_view);
use cocoa::foundation::NSRect; title_bar_rect.size.height = title_bar_frame_height;
use objc::{msg_send, sel, sel_impl}; title_bar_rect.origin.y =
NSView::frame(ns_window).size.height - title_bar_frame_height;
let _: () =
msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window = self.ns_window().unwrap() as cocoa::base::id; let window_buttons = vec![close, miniaturize, zoom];
let space_between =
NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
unsafe { for (i, button) in window_buttons.into_iter().enumerate() {
let close = window let mut rect: NSRect = NSView::frame(button);
.standardWindowButton_(NSWindowButton::NSWindowCloseButton); rect.origin.x = x + (i as f64 * space_between);
let miniaturize = window.standardWindowButton_( button.setFrameOrigin(rect.origin);
NSWindowButton::NSWindowMiniaturizeButton,
);
let zoom = window
.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview();
let close_rect: NSRect = msg_send![close, frame];
let button_height = close_rect.size.height;
let title_bar_frame_height = button_height + y;
let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y =
NSView::frame(window).size.height - title_bar_frame_height;
let _: () =
msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x
- NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button);
rect.origin.x = x + (i as f64 * space_between);
button.setFrameOrigin(rect.origin);
}
} }
} }
} }
#[cfg(target_os = "macos")]
#[derive(Debug)]
struct WindowState<R: Runtime> {
window: Window<R>,
}
#[cfg(target_os = "macos")]
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
use cocoa::appkit::NSWindow;
use cocoa::base::{id, BOOL};
use cocoa::foundation::NSUInteger;
use objc::runtime::{Object, Sel};
use std::ffi::c_void;
// Do the initial positioning
position_traffic_lights(
UnsafeWindowHandle(
window.ns_window().expect("Failed to create window handle"),
),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
);
// Ensure they stay in place while resizing the window.
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
this: &Object,
func: F,
) {
let ptr = unsafe {
let x: *mut c_void = *this.get_ivar("app_box");
&mut *(x as *mut WindowState<R>)
};
func(ptr);
}
unsafe {
let ns_win = window
.ns_window()
.expect("NS Window should exist to mount traffic light delegate.")
as id;
let current_delegate: id = ns_win.delegate();
extern "C" fn on_window_should_close(
this: &Object,
_cmd: Sel,
sender: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, windowShouldClose: sender]
}
}
extern "C" fn on_window_will_close(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillClose: notification];
}
}
extern "C" fn on_window_did_resize<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(this, |state: &mut WindowState<R>| {
let id = state.window.ns_window().expect(
"NS window should exist on state to handle resize",
) as id;
#[cfg(target_os = "macos")]
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidResize: notification];
}
}
extern "C" fn on_window_did_move(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidMove: notification];
}
}
extern "C" fn on_window_did_change_backing_properties(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
}
}
extern "C" fn on_window_did_become_key(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () =
msg_send![super_del, windowDidBecomeKey: notification];
}
}
extern "C" fn on_window_did_resign_key(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () =
msg_send![super_del, windowDidResignKey: notification];
}
}
extern "C" fn on_dragging_entered(
this: &Object,
_cmd: Sel,
notification: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, draggingEntered: notification]
}
}
extern "C" fn on_prepare_for_drag_operation(
this: &Object,
_cmd: Sel,
notification: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, prepareForDragOperation: notification]
}
}
extern "C" fn on_perform_drag_operation(
this: &Object,
_cmd: Sel,
sender: id,
) -> BOOL {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, performDragOperation: sender]
}
}
extern "C" fn on_conclude_drag_operation(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () =
msg_send![super_del, concludeDragOperation: notification];
}
}
extern "C" fn on_dragging_exited(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, draggingExited: notification];
}
}
extern "C" fn on_window_will_use_full_screen_presentation_options(
this: &Object,
_cmd: Sel,
window: id,
proposed_options: NSUInteger,
) -> NSUInteger {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
}
}
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(this, |state: &mut WindowState<R>| {
state
.window
.emit("did-enter-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
}
}
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(this, |state: &mut WindowState<R>| {
state
.window
.emit("will-enter-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
}
}
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(this, |state: &mut WindowState<R>| {
state
.window
.emit("did-exit-fullscreen", ())
.expect("Failed to emit event");
let id =
state.window.ns_window().expect("Failed to emit event")
as id;
position_traffic_lights(
UnsafeWindowHandle(id as *mut std::ffi::c_void),
WINDOW_CONTROL_PAD_X,
WINDOW_CONTROL_PAD_Y,
);
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () =
msg_send![super_del, windowDidExitFullScreen: notification];
}
}
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
with_window_state(this, |state: &mut WindowState<R>| {
state
.window
.emit("will-exit-fullscreen", ())
.expect("Failed to emit event");
});
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
}
}
extern "C" fn on_window_did_fail_to_enter_full_screen(
this: &Object,
_cmd: Sel,
window: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
}
}
extern "C" fn on_effective_appearance_did_change(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
}
}
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
this: &Object,
_cmd: Sel,
notification: id,
) {
unsafe {
let super_del: id = *this.get_ivar("super_delegate");
let _: () = msg_send![
super_del,
effectiveAppearanceDidChangedOnMainThread: notification
];
}
}
// Are we deallocing this properly ? (I miss safe Rust :( )
let window_label = window.label().to_string();
let app_state = WindowState { window };
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
let random_str: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(20)
.map(char::from)
.collect();
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
// delegate with the same name.
let delegate_name =
format!("windowDelegate_{}_{}", window_label, random_str);
ns_win.setDelegate_(delegate!(&delegate_name, {
window: id = ns_win,
app_box: *mut c_void = app_box,
toolbar: id = cocoa::base::nil,
super_delegate: id = current_delegate,
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
}))
}
}
+108 -114
View File
@@ -4,8 +4,7 @@
)] )]
use native_dialog::{MessageDialog, MessageType}; use native_dialog::{MessageDialog, MessageType};
use tauri::{Manager, PhysicalSize}; use tauri::{Listener, Manager};
use tauri_plugin_window_state::{StateFlags, WindowExt};
use theseus::prelude::*; use theseus::prelude::*;
mod api; mod api;
@@ -14,6 +13,14 @@ mod error;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
mod macos; mod macos;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate cocoa;
#[cfg(target_os = "macos")]
#[macro_use]
extern crate objc;
// Should be called in launcher initialization // Should be called in launcher initialization
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
#[tauri::command] #[tauri::command]
@@ -32,7 +39,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
#[tracing::instrument(skip_all)] #[tracing::instrument(skip_all)]
#[tauri::command] #[tauri::command]
fn show_window(app: tauri::AppHandle) { fn show_window(app: tauri::AppHandle) {
let win = app.get_window("main").unwrap(); let win = app.get_webview_window("main").unwrap();
if let Err(e) = win.show() { if let Err(e) = win.show() {
MessageDialog::new() MessageDialog::new()
.set_type(MessageType::Error) .set_type(MessageType::Error)
@@ -45,16 +52,7 @@ fn show_window(app: tauri::AppHandle) {
.unwrap(); .unwrap();
panic!("cannot display application window") panic!("cannot display application window")
} else { } else {
let _ = win.restore_state(StateFlags::all());
let _ = win.set_focus(); let _ = win.set_focus();
// fix issue where window shows as extremely small
if let Ok(size) = win.inner_size() {
let width = if size.width < 1100 { 1280 } else { size.width };
let height = if size.height < 700 { 800 } else { size.height };
let _ = win.set_size(PhysicalSize::new(width, height));
}
} }
} }
@@ -75,17 +73,9 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
Ok(()) Ok(())
} }
#[derive(Clone, serde::Serialize)]
struct Payload {
args: Vec<String>,
cwd: String,
}
// if Tauri app is called with arguments, then those arguments will be treated as commands // if Tauri app is called with arguments, then those arguments will be treated as commands
// ie: deep links or filepaths for .mrpacks // ie: deep links or filepaths for .mrpacks
fn main() { fn main() {
tauri_plugin_deep_link::prepare("ModrinthApp");
/* /*
tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show tracing is set basd on the environment variable RUST_LOG=xxx, depending on the amount of logs to show
ERROR > WARN > INFO > DEBUG > TRACE ERROR > WARN > INFO > DEBUG > TRACE
@@ -105,15 +95,25 @@ fn main() {
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!"); tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
let mut builder = tauri::Builder::default(); let mut builder = tauri::Builder::default();
#[cfg(feature = "updater")]
{
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
}
builder = builder builder = builder
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| { .plugin(tauri_plugin_os::init())
app.emit_all("single-instance", Payload { args: argv, cwd }) .plugin(tauri_plugin_dialog::init())
.unwrap(); .plugin(tauri_plugin_deep_link::init())
})) .plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_window_state::Builder::default().build()) .plugin(
tauri_plugin_window_state::Builder::default()
.with_filename("app-window-state.json")
.build(),
)
.setup(|app| { .setup(|app| {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let res = { {
use macos::deep_link::InitialPayload; use macos::deep_link::InitialPayload;
let mtx = std::sync::Arc::new(tokio::sync::Mutex::new(None)); let mtx = std::sync::Arc::new(tokio::sync::Mutex::new(None));
@@ -122,56 +122,32 @@ fn main() {
}); });
let mtx_copy = mtx.clone(); let mtx_copy = mtx.clone();
macos::delegate::register_open_file(move |filename| { app.listen("deep-link://new-url", move |url| {
let mtx_copy = mtx_copy.clone(); let mtx_copy_copy = mtx_copy.clone();
let request = url.payload().to_owned();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
tracing::info!("Handling file open {filename}"); tracing::info!("Handling deep link {request}");
let mut payload = mtx_copy.lock().await; let mut payload = mtx_copy_copy.lock().await;
if payload.is_none() { if payload.is_none() {
*payload = Some(filename.clone()); *payload = Some(request.clone());
} }
let _ = api::utils::handle_command(filename).await; let _ = api::utils::handle_command(request).await;
}); });
}) });
.unwrap();
let mtx_copy = mtx.clone();
tauri_plugin_deep_link::register(
"modrinth",
move |request: String| {
let mtx_copy = mtx_copy.clone();
tauri::async_runtime::spawn(async move {
tracing::info!("Handling deep link {request}");
let mut payload = mtx_copy.lock().await;
if payload.is_none() {
*payload = Some(request.clone());
}
let _ = api::utils::handle_command(request).await;
});
},
)
}; };
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
let res = tauri_plugin_deep_link::register( app.listen("deep-link://new-url", |url| {
"modrinth", let payload = url.payload().to_owned();
|request: String| { tracing::info!("Handling deep link {payload}");
tracing::info!("Handling deep link {request}"); tauri::async_runtime::spawn(api::utils::handle_command(
tauri::async_runtime::spawn(api::utils::handle_command( payload,
request, ));
)); dbg!(url);
}, });
);
if let Err(e) = res {
tracing::error!("Error registering deep link handler: {}", e);
}
if let Some(window) = app.get_window("main") { if let Some(window) = app.get_window("main") {
// Hide window to prevent white flash on startup // Hide window to prevent white flash on startup
@@ -179,34 +155,14 @@ fn main() {
#[cfg(not(target_os = "linux"))] #[cfg(not(target_os = "linux"))]
{ {
use window_shadows::set_shadow; window.set_shadow(true).unwrap();
set_shadow(&window, true).unwrap();
}
#[cfg(target_os = "macos")]
{
use macos::window_ext::WindowExt;
window.set_transparent_titlebar(true);
window.position_traffic_lights(9.0, 16.0);
} }
} }
Ok(()) Ok(())
}); });
#[cfg(target_os = "macos")] builder = builder
{
use tauri::WindowEvent;
builder = builder.on_window_event(|e| {
use macos::window_ext::WindowExt;
if let WindowEvent::Resized(..) = e.event() {
let win = e.window();
win.position_traffic_lights(9.0, 16.0);
}
})
}
let builder = builder
.plugin(api::auth::init()) .plugin(api::auth::init())
.plugin(api::mr_auth::init()) .plugin(api::mr_auth::init())
.plugin(api::import::init()) .plugin(api::import::init())
@@ -225,39 +181,77 @@ fn main() {
initialize_state, initialize_state,
is_dev, is_dev,
toggle_decorations, toggle_decorations,
api::auth::auth_login,
api::mr_auth::modrinth_auth_login, api::mr_auth::modrinth_auth_login,
show_window, show_window,
]); ]);
if let Err(e) = builder.run(tauri::generate_context!()) { #[cfg(target_os = "macos")]
#[cfg(target_os = "windows")] {
{ builder = builder.plugin(macos::window_ext::init());
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution }
if format!("{:?}", e)
.contains("Runtime(CreateWebview(WebView2Error(WindowsError")
{
MessageDialog::new()
.set_type(MessageType::Error)
.set_title("Initialization error")
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
.show_alert()
.unwrap();
panic!("webview2 initialization failed") let app = builder.build(tauri::generate_context!());
}
match app {
Ok(app) => {
#[allow(unused_variables)]
app.run(|app, event| {
#[cfg(target_os = "macos")]
if let tauri::RunEvent::Opened { urls } = event {
tracing::info!("Handling webview open {urls:?}");
let file = urls
.into_iter()
.filter_map(|url| url.to_file_path().ok())
.next();
if let Some(file) = file {
use macos::deep_link::InitialPayload;
let initial_payload = app.state::<InitialPayload>();
let request = file.to_string_lossy().to_string();
let mtx_copy = initial_payload.payload.clone();
tauri::async_runtime::spawn(async move {
let mut payload = mtx_copy.lock().await;
if payload.is_none() {
*payload = Some(request.clone());
}
let _ = api::utils::handle_command(request).await;
});
}
}
});
} }
Err(e) => {
#[cfg(target_os = "windows")]
{
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
if format!("{:?}", e).contains(
"Runtime(CreateWebview(WebView2Error(WindowsError",
) {
MessageDialog::new()
.set_type(MessageType::Error)
.set_title("Initialization error")
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
.show_alert()
.unwrap();
MessageDialog::new() panic!("webview2 initialization failed")
.set_type(MessageType::Error) }
.set_title("Initialization error") }
.set_text(&format!(
"Cannot initialize application due to an error:\n{:?}",
e
))
.show_alert()
.unwrap();
panic!("{1}: {:?}", e, "error while running tauri application") MessageDialog::new()
.set_type(MessageType::Error)
.set_title("Initialization error")
.set_text(&format!(
"Cannot initialize application due to an error:\n{:?}",
e
))
.show_alert()
.unwrap();
panic!("{1}: {:?}", e, "error while running tauri application")
}
} }
} }
+9 -5
View File
@@ -1,10 +1,14 @@
{ {
"tauri": { "bundle": {
"createUpdaterArtifacts": "v1Compatible"
},
"build": {
"features": ["updater"]
},
"plugins": {
"updater": { "updater": {
"active": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
"endpoints": ["https://launcher-files.modrinth.com/updates.json"], "endpoints": ["https://launcher-files.modrinth.com/updates.json"]
"dialog": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK"
} }
} }
} }
+60 -72
View File
@@ -2,83 +2,59 @@
"build": { "build": {
"beforeDevCommand": "pnpm turbo run dev --filter=@modrinth/app-frontend", "beforeDevCommand": "pnpm turbo run dev --filter=@modrinth/app-frontend",
"beforeBuildCommand": "pnpm turbo run build --filter=@modrinth/app-frontend", "beforeBuildCommand": "pnpm turbo run build --filter=@modrinth/app-frontend",
"devPath": "http://localhost:1420", "frontendDist": "../app-frontend/dist",
"distDir": "../app-frontend/dist", "devUrl": "http://localhost:1420"
"withGlobalTauri": false
}, },
"package": { "bundle": {
"productName": "Modrinth App", "active": true,
"version": "0.8.3-1" "category": "Game",
}, "copyright": "",
"tauri": { "targets": "all",
"allowlist": { "externalBin": [],
"dialog": { "icon": [
"confirm": true, "icons/128x128.png",
"open": true "icons/128x128@2x.png",
}, "icons/icon.icns",
"protocol": { "icons/icon.ico"
"asset": true, ],
"assetScope": [] "windows": {
}, "certificateThumbprint": null,
"shell": { "digestAlgorithm": "sha256",
"open": true "timestampUrl": "http://timestamp.digicert.com",
}, "wix": {
"window": { "template": "./msi/main.wxs"
"create": true,
"close": true,
"hide": true,
"show": true,
"maximize": true,
"minimize": true,
"unmaximize": true,
"unminimize": true,
"startDragging": true,
"setDecorations": true
},
"os": {
"all": true
},
"app": {
"all": true
} }
}, },
"macOSPrivateApi": true, "longDescription": "",
"bundle": { "macOS": {
"active": true, "entitlements": "App.entitlements",
"category": "Game", "exceptionDomain": "",
"copyright": "", "frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"linux": {
"deb": { "deb": {
"depends": [] "depends": []
},
"externalBin": [],
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
"identifier": "ModrinthApp",
"longDescription": "",
"macOS": {
"entitlements": "App.entitlements",
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": null
},
"resources": [],
"shortDescription": "",
"targets": "all",
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com",
"wix": {
"template": "./msi/main.wxs"
}
} }
}, }
"security": { },
"csp": "default-src 'self'; connect-src https://modrinth.com https://*.modrinth.com https://mixpanel.com https://*.mixpanel.com https://*.cloudflare.com https://api.mclo.gs; font-src https://cdn-raw.modrinth.com/fonts/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost; script-src https://*.cloudflare.com 'self'; frame-src https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'; style-src 'unsafe-inline' 'self'" "productName": "Modrinth App",
}, "version": "0.8.3-1",
"updater": { "identifier": "ModrinthApp",
"active": false "plugins": {
}, "deep-link": {
"desktop": {
"schemes": ["modrinth"]
},
"mobile": []
}
},
"app": {
"withGlobalTauri": false,
"macOSPrivateApi": true,
"windows": [ "windows": [
{ {
"titleBarStyle": "Overlay", "titleBarStyle": "Overlay",
@@ -91,8 +67,20 @@
"minHeight": 700, "minHeight": 700,
"minWidth": 1100, "minWidth": 1100,
"visible": false, "visible": false,
"zoomHotkeysEnabled": true,
"decorations": false "decorations": false
} }
] ],
"security": {
"assetProtocol": {
"scope": [
"$APPDATA/caches/icons/*",
"$APPCONFIG/caches/icons/*",
"$CONFIG/caches/icons/*"
],
"enable": true
},
"csp": "default-src 'self'; connect-src ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://mixpanel.com https://*.mixpanel.com https://*.cloudflare.com https://api.mclo.gs; font-src https://cdn-raw.modrinth.com/fonts/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost; script-src https://*.cloudflare.com 'self'; frame-src https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'; style-src unsafe-inline 'self'"
}
} }
} }
+2 -1
View File
@@ -1,5 +1,5 @@
{ {
"tauri": { "app": {
"windows": [ "windows": [
{ {
"titleBarStyle": "Overlay", "titleBarStyle": "Overlay",
@@ -12,6 +12,7 @@
"minHeight": 700, "minHeight": 700,
"minWidth": 1100, "minWidth": 1100,
"visible": false, "visible": false,
"zoomHotkeysEnabled": true,
"decorations": true "decorations": true
} }
] ]
+2
View File
@@ -0,0 +1,2 @@
BASE_URL=https://api.modrinth.com/v2/
BROWSER_BASE_URL=https://api.modrinth.com/v2/
@@ -6,12 +6,12 @@
{ {
"name": "max_concurrent_writes", "name": "max_concurrent_writes",
"ordinal": 0, "ordinal": 0,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "max_concurrent_downloads", "name": "max_concurrent_downloads",
"ordinal": 1, "ordinal": 1,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "theme", "name": "theme",
@@ -26,37 +26,37 @@
{ {
"name": "collapsed_navigation", "name": "collapsed_navigation",
"ordinal": 4, "ordinal": 4,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "advanced_rendering", "name": "advanced_rendering",
"ordinal": 5, "ordinal": 5,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "native_decorations", "name": "native_decorations",
"ordinal": 6, "ordinal": 6,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "discord_rpc", "name": "discord_rpc",
"ordinal": 7, "ordinal": 7,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "developer_mode", "name": "developer_mode",
"ordinal": 8, "ordinal": 8,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "telemetry", "name": "telemetry",
"ordinal": 9, "ordinal": 9,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "onboarded", "name": "onboarded",
"ordinal": 10, "ordinal": 10,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "extra_launch_args", "name": "extra_launch_args",
@@ -71,27 +71,27 @@
{ {
"name": "mc_memory_max", "name": "mc_memory_max",
"ordinal": 13, "ordinal": 13,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "mc_force_fullscreen", "name": "mc_force_fullscreen",
"ordinal": 14, "ordinal": 14,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "mc_game_resolution_x", "name": "mc_game_resolution_x",
"ordinal": 15, "ordinal": 15,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "mc_game_resolution_y", "name": "mc_game_resolution_y",
"ordinal": 16, "ordinal": 16,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "hide_on_process_start", "name": "hide_on_process_start",
"ordinal": 17, "ordinal": 17,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "hook_pre_launch", "name": "hook_pre_launch",
@@ -121,7 +121,7 @@
{ {
"name": "migrated", "name": "migrated",
"ordinal": 23, "ordinal": 23,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n DELETE FROM processes WHERE pid = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "1769b7033985bfdd04ee8912d9f28e0d15a8b893db47aca3aec054c7134f1f3f"
}
@@ -11,7 +11,7 @@
{ {
"name": "active", "name": "active",
"ordinal": 1, "ordinal": 1,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "session_id", "name": "session_id",
@@ -21,7 +21,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -6,7 +6,7 @@
{ {
"name": "major_version", "name": "major_version",
"ordinal": 0, "ordinal": 0,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "full_version", "name": "full_version",
@@ -26,7 +26,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 4, "ordinal": 4,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -1,50 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n pid, start_time, name, executable, profile_path, post_exit_command\n FROM processes\n WHERE 1=$1",
"describe": {
"columns": [
{
"name": "pid",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "start_time",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executable",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "profile_path",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "post_exit_command",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "3cac786ad15ef1167bc50ca846d98facb3dee35c9e421209c1161ee7380b7a74"
}
@@ -56,32 +56,32 @@
{ {
"name": "locked", "name": "locked",
"ordinal": 10, "ordinal": 10,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "created", "name": "created",
"ordinal": 11, "ordinal": 11,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "modified", "name": "modified",
"ordinal": 12, "ordinal": 12,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "last_played", "name": "last_played",
"ordinal": 13, "ordinal": 13,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "submitted_time_played", "name": "submitted_time_played",
"ordinal": 14, "ordinal": 14,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "recent_time_played", "name": "recent_time_played",
"ordinal": 15, "ordinal": 15,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_java_path", "name": "override_java_path",
@@ -101,22 +101,22 @@
{ {
"name": "override_mc_memory_max", "name": "override_mc_memory_max",
"ordinal": 19, "ordinal": 19,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_force_fullscreen", "name": "override_mc_force_fullscreen",
"ordinal": 20, "ordinal": 20,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_game_resolution_x", "name": "override_mc_game_resolution_x",
"ordinal": 21, "ordinal": 21,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_game_resolution_y", "name": "override_mc_game_resolution_y",
"ordinal": 22, "ordinal": 22,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_hook_pre_launch", "name": "override_hook_pre_launch",
@@ -56,32 +56,32 @@
{ {
"name": "locked", "name": "locked",
"ordinal": 10, "ordinal": 10,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "created", "name": "created",
"ordinal": 11, "ordinal": 11,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "modified", "name": "modified",
"ordinal": 12, "ordinal": 12,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "last_played", "name": "last_played",
"ordinal": 13, "ordinal": 13,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "submitted_time_played", "name": "submitted_time_played",
"ordinal": 14, "ordinal": 14,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "recent_time_played", "name": "recent_time_played",
"ordinal": 15, "ordinal": 15,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_java_path", "name": "override_java_path",
@@ -101,22 +101,22 @@
{ {
"name": "override_mc_memory_max", "name": "override_mc_memory_max",
"ordinal": 19, "ordinal": 19,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_force_fullscreen", "name": "override_mc_force_fullscreen",
"ordinal": 20, "ordinal": 20,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_game_resolution_x", "name": "override_mc_game_resolution_x",
"ordinal": 21, "ordinal": 21,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_mc_game_resolution_y", "name": "override_mc_game_resolution_y",
"ordinal": 22, "ordinal": 22,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "override_hook_pre_launch", "name": "override_hook_pre_launch",
@@ -1,50 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n pid, start_time, name, executable, profile_path, post_exit_command\n FROM processes\n WHERE profile_path = $1",
"describe": {
"columns": [
{
"name": "pid",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "start_time",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executable",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "profile_path",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "post_exit_command",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "5f07a8b45063167074db8b3da51e220a7a0f5879fb8978d4033e259102ae3790"
}
@@ -11,7 +11,7 @@
{ {
"name": "active", "name": "active",
"ordinal": 1, "ordinal": 1,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "session_id", "name": "session_id",
@@ -21,7 +21,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 3, "ordinal": 3,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -26,12 +26,12 @@
{ {
"name": "issue_instant", "name": "issue_instant",
"ordinal": 4, "ordinal": 4,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "not_after", "name": "not_after",
"ordinal": 5, "ordinal": 5,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "token", "name": "token",
@@ -41,7 +41,7 @@
{ {
"name": "display_claims!: serde_json::Value", "name": "display_claims!: serde_json::Value",
"ordinal": 7, "ordinal": 7,
"type_info": "Null" "type_info": "Text"
} }
], ],
"parameters": { "parameters": {
@@ -11,7 +11,7 @@
{ {
"name": "active", "name": "active",
"ordinal": 1, "ordinal": 1,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "username", "name": "username",
@@ -31,7 +31,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 5, "ordinal": 5,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -11,7 +11,7 @@
{ {
"name": "active", "name": "active",
"ordinal": 1, "ordinal": 1,
"type_info": "Int64" "type_info": "Integer"
}, },
{ {
"name": "username", "name": "username",
@@ -31,7 +31,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 5, "ordinal": 5,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {
@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO processes (pid, start_time, name, executable, profile_path, post_exit_command)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (pid) DO UPDATE SET\n start_time = $2,\n name = $3,\n executable = $4,\n profile_path = $5,\n post_exit_command = $6\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 6
},
"nullable": []
},
"hash": "d1b8f27c8150f9ae514a7c9ddc68f4a59f08b7df1c65758539220d7211ade682"
}
@@ -1,50 +0,0 @@
{
"db_name": "SQLite",
"query": "\n SELECT\n pid, start_time, name, executable, profile_path, post_exit_command\n FROM processes\n WHERE pid = $1",
"describe": {
"columns": [
{
"name": "pid",
"ordinal": 0,
"type_info": "Int64"
},
{
"name": "start_time",
"ordinal": 1,
"type_info": "Int64"
},
{
"name": "name",
"ordinal": 2,
"type_info": "Text"
},
{
"name": "executable",
"ordinal": 3,
"type_info": "Text"
},
{
"name": "profile_path",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "post_exit_command",
"ordinal": 5,
"type_info": "Text"
}
],
"parameters": {
"Right": 1
},
"nullable": [
false,
false,
false,
false,
false,
true
]
},
"hash": "e18e960d33a140e522ca20b91d63560b921b922701b69d868dc231f6b0f4cf1c"
}
+3 -9
View File
@@ -2,9 +2,7 @@
name = "theseus" name = "theseus"
version = "0.8.3-1" version = "0.8.3-1"
authors = ["Jai A <jaiagr+gpg@pm.me>"] authors = ["Jai A <jaiagr+gpg@pm.me>"]
edition = "2018" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
bytes = "1" bytes = "1"
@@ -38,14 +36,13 @@ tracing-error = "0.2.0"
paste = { version = "1.0" } paste = { version = "1.0" }
tauri = { version = "1.7.1", optional = true } tauri = { version = "2.0.0-rc.4", optional = true }
indicatif = { version = "0.17.3", optional = true } indicatif = { version = "0.17.3", optional = true }
async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] } async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
futures = "0.3" futures = "0.3"
reqwest = { version = "0.12.3", features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls", "charset", "http2", "macos-system-configuration"], default-features = false } reqwest = { version = "0.12.3", features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls", "charset", "http2", "macos-system-configuration"], default-features = false }
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
tokio-stream = { version = "0.1", features = ["fs"] }
async-recursion = "1.0.4" async-recursion = "1.0.4"
notify = { version = "6.1.1", default-features = false } notify = { version = "6.1.1", default-features = false }
@@ -63,10 +60,7 @@ rand = "0.8"
byteorder = "1.5.0" byteorder = "1.5.0"
base64 = "0.22.0" base64 = "0.22.0"
# TODO: Remove when new SQLX version is released sqlx = { version = "0.8.0", features = [ "runtime-tokio", "sqlite", "macros" ] }
# We force-upgrade SQLite so JSONB support is added (theseus)
# https://github.com/launchbadge/sqlx/commit/352b02de6af70f1ff1bfbd15329120589a0f7337
sqlx = { git = "https://github.com/launchbadge/sqlx.git", rev = "352b02de6af70f1ff1bfbd15329120589a0f7337", features = [ "runtime-tokio", "sqlite", "macros"] }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winreg = "0.52.0" winreg = "0.52.0"
+7 -8
View File
@@ -3,14 +3,13 @@ use crate::event::{
CommandPayload, EventError, LoadingBar, LoadingBarType, ProcessPayloadType, CommandPayload, EventError, LoadingBar, LoadingBarType, ProcessPayloadType,
ProfilePayloadType, ProfilePayloadType,
}; };
use futures::prelude::*;
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
use crate::event::{ use crate::event::{
LoadingPayload, ProcessPayload, ProfilePayload, WarningPayload, LoadingPayload, ProcessPayload, ProfilePayload, WarningPayload,
}; };
use futures::prelude::*;
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
use tauri::Manager; use tauri::Emitter;
use uuid::Uuid; use uuid::Uuid;
#[cfg(feature = "cli")] #[cfg(feature = "cli")]
@@ -187,7 +186,7 @@ pub async fn emit_loading(
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
event_state event_state
.app .app
.emit_all( .emit(
"loading", "loading",
LoadingPayload { LoadingPayload {
fraction: opt_display_frac, fraction: opt_display_frac,
@@ -215,7 +214,7 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
event_state event_state
.app .app
.emit_all( .emit(
"warning", "warning",
WarningPayload { WarningPayload {
message: message.to_string(), message: message.to_string(),
@@ -239,7 +238,7 @@ pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
event_state event_state
.app .app
.emit_all("command", command) .emit("command", command)
.map_err(EventError::from)?; .map_err(EventError::from)?;
} }
Ok(()) Ok(())
@@ -258,7 +257,7 @@ pub async fn emit_process(
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
event_state event_state
.app .app
.emit_all( .emit(
"process", "process",
ProcessPayload { ProcessPayload {
profile_path_id: profile_path.to_string(), profile_path_id: profile_path.to_string(),
@@ -283,7 +282,7 @@ pub async fn emit_profile(
let event_state = crate::EventState::get().await?; let event_state = crate::EventState::get().await?;
event_state event_state
.app .app
.emit_all( .emit(
"profile", "profile",
ProfilePayload { ProfilePayload {
profile_path_id: profile_path_id.to_string(), profile_path_id: profile_path_id.to_string(),
+6 -4
View File
@@ -2,6 +2,8 @@
use dashmap::DashMap; use dashmap::DashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{path::PathBuf, sync::Arc}; use std::{path::PathBuf, sync::Arc};
#[cfg(feature = "tauri")]
use tauri::Emitter;
use tokio::sync::OnceCell; use tokio::sync::OnceCell;
use uuid::Uuid; use uuid::Uuid;
@@ -62,10 +64,11 @@ impl EventState {
} }
#[cfg(feature = "tauri")] #[cfg(feature = "tauri")]
pub async fn get_main_window() -> crate::Result<Option<tauri::Window>> { pub async fn get_main_window() -> crate::Result<Option<tauri::WebviewWindow>>
{
use tauri::Manager; use tauri::Manager;
let value = Self::get().await?; let value = Self::get().await?;
Ok(value.app.get_window("main")) Ok(value.app.get_webview_window("main"))
} }
} }
@@ -103,8 +106,7 @@ impl Drop for LoadingBarId {
let event = bar.bar_type.clone(); let event = bar.bar_type.clone();
let fraction = bar.current / bar.total; let fraction = bar.current / bar.total;
use tauri::Manager; let _ = event_state.app.emit(
let _ = event_state.app.emit_all(
"loading", "loading",
LoadingPayload { LoadingPayload {
fraction: None, fraction: None,
@@ -217,7 +217,7 @@ where
let file_name = format!( let file_name = format!(
"{}/{}", "{}/{}",
profile.path, profile.path,
path.replace("\\", "/") path.replace('\\', "/")
.replace(".disabled", "") .replace(".disabled", "")
); );
+4 -2
View File
@@ -68,11 +68,13 @@ impl Settings {
onboarded: res.onboarded == 1, onboarded: res.onboarded == 1,
extra_launch_args: res extra_launch_args: res
.extra_launch_args .extra_launch_args
.and_then(|x| serde_json::from_str(&x).ok()) .as_ref()
.and_then(|x| serde_json::from_str(x).ok())
.unwrap_or_default(), .unwrap_or_default(),
custom_env_vars: res custom_env_vars: res
.custom_env_vars .custom_env_vars
.and_then(|x| serde_json::from_str(&x).ok()) .as_ref()
.and_then(|x| serde_json::from_str(x).ok())
.unwrap_or_default(), .unwrap_or_default(),
memory: MemorySettings { memory: MemorySettings {
maximum: res.mc_memory_max as u32, maximum: res.mc_memory_max as u32,
+13 -11
View File
@@ -3,18 +3,20 @@ import { DropdownIcon } from '@modrinth/assets'
import { reactive } from 'vue' import { reactive } from 'vue'
import Button from './Button.vue' import Button from './Button.vue'
const props = withDefaults( const props = defineProps({
defineProps<{ collapsible: {
collapsible: boolean type: Boolean,
defaultCollapsed: boolean default: false,
noAutoBody: boolean
}>(),
{
collapsible: false,
defaultCollapsed: false,
noAutoBody: false,
}, },
) defaultCollapsed: {
type: Boolean,
default: false,
},
noAutoBody: {
type: Boolean,
default: false,
},
})
const state = reactive({ const state = reactive({
collapsed: props.defaultCollapsed, collapsed: props.defaultCollapsed,
+630 -298
View File
File diff suppressed because it is too large Load Diff
+2 -1
View File
@@ -22,7 +22,8 @@
"dev": { "dev": {
"cache": false, "cache": false,
"persistent": true, "persistent": true,
"inputs": ["$TURBO_DEFAULT$", ".env*"] "inputs": ["$TURBO_DEFAULT$", ".env*"],
"env": ["DISPLAY", "WEBKIT_DISABLE_DMABUF_RENDERER"]
}, },
"test": {}, "test": {},
"fix": { "fix": {