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
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
83 changed files with 15614 additions and 2013 deletions

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

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 }}

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

2216
Cargo.lock generated

File diff suppressed because it is too large Load Diff

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",

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">

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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 = () => {

View File

@ -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'

View File

@ -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">

View File

@ -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 = () => {

View File

@ -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)
}
}

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')
} }

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 })

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 })
} }

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'
/* /*

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:

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

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')
} }

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

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]

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

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
/* /*

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

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() {

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')

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')

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)
}
},
},
}

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'

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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

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,

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"] }

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"]

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");
} }

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"
]
}

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

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

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:*"
} }
} }

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?)
} }

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))

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,

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)| {
( (

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?;

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()
}
}
}

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;

View File

@ -1,47 +1,43 @@
/// 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,
) {
unsafe {
window.setTitleVisibility_(
NSWindowTitleVisibility::NSWindowTitleHidden,
);
if transparent {
window.setTitlebarAppearsTransparent_(cocoa::base::YES);
} else {
window.setTitlebarAppearsTransparent_(cocoa::base::NO);
}
}
}
fn position_traffic_lights(&self, x: f64, y: f64) {
use cocoa::appkit::{NSView, NSWindow, NSWindowButton}; use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
use cocoa::foundation::NSRect; use cocoa::foundation::NSRect;
use objc::{msg_send, sel, sel_impl}; let ns_window = ns_window_handle.0 as cocoa::base::id;
let window = self.ns_window().unwrap() as cocoa::base::id;
unsafe { unsafe {
let close = window let close = ns_window
.standardWindowButton_(NSWindowButton::NSWindowCloseButton); .standardWindowButton_(NSWindowButton::NSWindowCloseButton);
let miniaturize = window.standardWindowButton_( let miniaturize = ns_window
NSWindowButton::NSWindowMiniaturizeButton, .standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
); let zoom =
let zoom = window ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
let title_bar_container_view = close.superview().superview(); let title_bar_container_view = close.superview().superview();
@ -52,13 +48,13 @@ impl<R: Runtime> WindowExt for Window<R> {
let mut title_bar_rect = NSView::frame(title_bar_container_view); let mut title_bar_rect = NSView::frame(title_bar_container_view);
title_bar_rect.size.height = title_bar_frame_height; title_bar_rect.size.height = title_bar_frame_height;
title_bar_rect.origin.y = title_bar_rect.origin.y =
NSView::frame(window).size.height - title_bar_frame_height; NSView::frame(ns_window).size.height - title_bar_frame_height;
let _: () = let _: () =
msg_send![title_bar_container_view, setFrame: title_bar_rect]; msg_send![title_bar_container_view, setFrame: title_bar_rect];
let window_buttons = vec![close, miniaturize, zoom]; let window_buttons = vec![close, miniaturize, zoom];
let space_between = NSView::frame(miniaturize).origin.x let space_between =
- NSView::frame(close).origin.x; NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
for (i, button) in window_buttons.into_iter().enumerate() { for (i, button) in window_buttons.into_iter().enumerate() {
let mut rect: NSRect = NSView::frame(button); let mut rect: NSRect = NSView::frame(button);
@ -66,5 +62,351 @@ impl<R: Runtime> WindowExt for Window<R> {
button.setFrameOrigin(rect.origin); 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)
}))
} }
} }

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 {
tracing::info!("Handling file open {filename}");
let mut payload = mtx_copy.lock().await;
if payload.is_none() {
*payload = Some(filename.clone());
}
let _ = api::utils::handle_command(filename).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 { tauri::async_runtime::spawn(async move {
tracing::info!("Handling deep link {request}"); 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(request.clone()); *payload = Some(request.clone());
} }
let _ = api::utils::handle_command(request).await; 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(
request, payload,
)); ));
}, 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,18 +181,55 @@ 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")]
{
builder = builder.plugin(macos::window_ext::init());
}
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")] #[cfg(target_os = "windows")]
{ {
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution // tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
if format!("{:?}", e) if format!("{:?}", e).contains(
.contains("Runtime(CreateWebview(WebView2Error(WindowsError") "Runtime(CreateWebview(WebView2Error(WindowsError",
{ ) {
MessageDialog::new() MessageDialog::new()
.set_type(MessageType::Error) .set_type(MessageType::Error)
.set_title("Initialization error") .set_title("Initialization error")
@ -260,4 +253,5 @@ fn main() {
panic!("{1}: {:?}", e, "error while running tauri application") panic!("{1}: {:?}", e, "error while running tauri application")
} }
}
} }

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"
} }
} }
} }

View File

@ -2,57 +2,29 @@
"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": {
"productName": "Modrinth App",
"version": "0.8.3-1"
},
"tauri": {
"allowlist": {
"dialog": {
"confirm": true,
"open": true
},
"protocol": {
"asset": true,
"assetScope": []
},
"shell": {
"open": true
},
"window": {
"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,
"bundle": { "bundle": {
"active": true, "active": true,
"category": "Game", "category": "Game",
"copyright": "", "copyright": "",
"deb": { "targets": "all",
"depends": []
},
"externalBin": [], "externalBin": [],
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], "icon": [
"identifier": "ModrinthApp", "icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
],
"windows": {
"certificateThumbprint": null,
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com",
"wix": {
"template": "./msi/main.wxs"
}
},
"longDescription": "", "longDescription": "",
"macOS": { "macOS": {
"entitlements": "App.entitlements", "entitlements": "App.entitlements",
@ -63,22 +35,26 @@
}, },
"resources": [], "resources": [],
"shortDescription": "", "shortDescription": "",
"targets": "all", "linux": {
"windows": { "deb": {
"certificateThumbprint": null, "depends": []
"digestAlgorithm": "sha256",
"timestampUrl": "http://timestamp.digicert.com",
"wix": {
"template": "./msi/main.wxs"
} }
} }
}, },
"security": { "productName": "Modrinth App",
"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'" "version": "0.8.3-1",
"identifier": "ModrinthApp",
"plugins": {
"deep-link": {
"desktop": {
"schemes": ["modrinth"]
}, },
"updater": { "mobile": []
"active": false }
}, },
"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'"
}
} }
} }

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
} }
] ]

View File

@ -0,0 +1,2 @@
BASE_URL=https://api.modrinth.com/v2/
BROWSER_BASE_URL=https://api.modrinth.com/v2/

View File

@ -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": {

View File

@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n DELETE FROM processes WHERE pid = $1\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 1
},
"nullable": []
},
"hash": "1769b7033985bfdd04ee8912d9f28e0d15a8b893db47aca3aec054c7134f1f3f"
}

View File

@ -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": {

View File

@ -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",

View File

@ -26,7 +26,7 @@
{ {
"name": "expires", "name": "expires",
"ordinal": 4, "ordinal": 4,
"type_info": "Int64" "type_info": "Integer"
} }
], ],
"parameters": { "parameters": {

View File

@ -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"
}

View File

@ -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",

View File

@ -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",

View File

@ -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"
}

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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": {

View File

@ -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"
}

View File

@ -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"
}

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"

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(),

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,

View File

@ -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", "")
); );

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,

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,

928
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

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": {