Compare commits
1 Commits
issue-temp
...
plus-theme
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7d096e768 |
@@ -13,6 +13,3 @@ max_line_length = 100
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
||||
4
.github/ISSUE_TEMPLATE/2-web-bug.yml
vendored
4
.github/ISSUE_TEMPLATE/2-web-bug.yml
vendored
@@ -1,6 +1,6 @@
|
||||
name: 🌐 Website bug (modrinth.com)
|
||||
description: Report an issue on the Modrinth website.
|
||||
labels: [bug, web]
|
||||
labels: [bug, frontend]
|
||||
body:
|
||||
- type: checkboxes
|
||||
attributes:
|
||||
@@ -49,4 +49,4 @@ body:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
4
.github/ISSUE_TEMPLATE/4-feature-request.yml
vendored
4
.github/ISSUE_TEMPLATE/4-feature-request.yml
vendored
@@ -9,8 +9,6 @@ body:
|
||||
options:
|
||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
|
||||
required: true
|
||||
- label: I checked the [existing discussions](https://github.com/orgs/modrinth/discussions) for duplicate feature requests
|
||||
required: true
|
||||
- label: I have checked that this feature request is not on our [roadmap](https://roadmap.modrinth.com)
|
||||
required: true
|
||||
- type: dropdown
|
||||
@@ -45,4 +43,4 @@ body:
|
||||
label: Additional context
|
||||
description: Add any other context or screenshots about the suggested enhancement here.
|
||||
validations:
|
||||
required: false
|
||||
required: false
|
||||
57
.github/workflows/app-release.yml
vendored
57
.github/workflows/app-release.yml
vendored
@@ -6,7 +6,6 @@ on:
|
||||
tags:
|
||||
- 'v*'
|
||||
paths:
|
||||
- .github/workflows/app-release.yml
|
||||
- 'apps/app/**'
|
||||
- 'apps/app-frontend/**'
|
||||
- 'packages/app-lib/**'
|
||||
@@ -21,12 +20,12 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
platform: [macos-latest, windows-latest, ubuntu-22.04]
|
||||
platform: [macos-latest, windows-latest, ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.platform }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Rust setup (mac)
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
@@ -50,7 +49,7 @@ jobs:
|
||||
${{ runner.os }}-rust-target-
|
||||
|
||||
- name: Use Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
@@ -67,7 +66,7 @@ jobs:
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Setup pnpm cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||
@@ -78,13 +77,14 @@ jobs:
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev sqlite3
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libselinux1
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
|
||||
- name: build app (macos)
|
||||
run: pnpm --filter=@modrinth/app run tauri build --target universal-apple-darwin --config "tauri-release.conf.json"
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os_mac
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -95,37 +95,34 @@ jobs:
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
args: "--target universal-apple-darwin --config ./apps/app/tauri-release.conf.json"
|
||||
working-directory: ./apps/app
|
||||
|
||||
- name: build app
|
||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
||||
uses: tauri-apps/tauri-action@v0
|
||||
id: build_os
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
TAURI_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
with:
|
||||
args: "--config ./apps/app/tauri-release.conf.json"
|
||||
working-directory: ./apps/app
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v3
|
||||
if: startsWith(matrix.platform, 'macos')
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: |
|
||||
target/*/release/bundle/*/*.dmg
|
||||
target/*/release/bundle/*/*.app.tar.gz
|
||||
target/*/release/bundle/*/*.app.tar.gz.sig
|
||||
target/release/bundle/*/*.dmg
|
||||
target/release/bundle/*/*.app.tar.gz
|
||||
target/release/bundle/*/*.app.tar.gz.sig
|
||||
|
||||
target/release/bundle/*/*.AppImage
|
||||
target/release/bundle/*/*.AppImage.tar.gz
|
||||
target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
target/release/bundle/*/*.deb
|
||||
target/release/bundle/*/*.rpm
|
||||
|
||||
target/release/bundle/*/*.msi
|
||||
target/release/bundle/*/*.msi.zip
|
||||
target/release/bundle/*/*.msi.zip.sig
|
||||
path: "${{ join(fromJSON(steps.build_os_mac.outputs.artifactPaths), '\n') }}"
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v3
|
||||
if: "!startsWith(matrix.platform, 'macos')"
|
||||
with:
|
||||
name: ${{ matrix.platform }}
|
||||
path: "${{ join(fromJSON(steps.build_os.outputs.artifactPaths), '\n') }}"
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -11,7 +11,7 @@ on:
|
||||
jobs:
|
||||
build:
|
||||
name: Build, Test, and Lint
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Check out code
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
- name: Install build dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev librsvg2-dev patchelf libselinux1
|
||||
|
||||
- name: Setup Node.JS environment
|
||||
uses: actions/setup-node@v4
|
||||
|
||||
2202
Cargo.lock
generated
2202
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -13,26 +13,22 @@
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@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",
|
||||
"@tauri-apps/api": "^1.6.0",
|
||||
"@vintl/vintl": "^4.4.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"floating-vue": "^5.2.2",
|
||||
"mixpanel-browser": "^2.49.0",
|
||||
"ofetch": "^1.3.4",
|
||||
"pinia": "^2.1.7",
|
||||
"tauri-plugin-window-state-api": "github:tauri-apps/tauri-plugin-window-state#v1",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-multiselect": "3.0.0",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8",
|
||||
"posthog-js": "^1.158.2",
|
||||
"@sentry/vue": "^8.27.0"
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0-rc",
|
||||
"@tauri-apps/cli": "^1.6.0",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
|
||||
@@ -15,21 +15,27 @@ import ModrinthLoadingIndicator from '@/components/modrinth-loading-indicator'
|
||||
import { handleError, useNotifications } from '@/store/notifications.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { type } from '@tauri-apps/api/os'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { isDev, getOS } from '@/helpers/utils.js'
|
||||
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import {
|
||||
mixpanel_track,
|
||||
mixpanel_init,
|
||||
mixpanel_opt_out_tracking,
|
||||
mixpanel_is_loaded,
|
||||
} from '@/helpers/mixpanel'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { window as TauriWindow } from '@tauri-apps/api'
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
|
||||
import { install_from_file } from './helpers/pack'
|
||||
import { useError } from '@/store/error.js'
|
||||
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
|
||||
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
|
||||
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
|
||||
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
|
||||
import { useInstall } from '@/store/install.js'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-shell'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
|
||||
const themeStore = useTheming()
|
||||
@@ -51,10 +57,6 @@ const os = ref('')
|
||||
|
||||
const stateInitialized = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
await useCheckDisableMouseover()
|
||||
})
|
||||
|
||||
async function setupApp() {
|
||||
stateInitialized.value = true
|
||||
const {
|
||||
@@ -77,23 +79,21 @@ async function setupApp() {
|
||||
showOnboarding.value = !onboarded
|
||||
|
||||
nativeDecorations.value = native_decorations
|
||||
if (os.value !== 'MacOS') await getCurrentWindow().setDecorations(native_decorations)
|
||||
if (os.value !== 'MacOS') await appWindow.setDecorations(native_decorations)
|
||||
|
||||
themeStore.setThemeState(theme)
|
||||
themeStore.collapsedNavigation = collapsed_navigation
|
||||
themeStore.advancedRendering = advanced_rendering
|
||||
|
||||
initAnalytics()
|
||||
mixpanel_init('014c7d6a336d0efaefe3aca91063748d', { debug: dev, persistence: 'localStorage' })
|
||||
if (!telemetry) {
|
||||
optOutAnalytics()
|
||||
mixpanel_opt_out_tracking()
|
||||
}
|
||||
if (dev) debugAnalytics()
|
||||
trackEvent('Launched', { version, dev, onboarded })
|
||||
mixpanel_track('Launched', { version, dev, onboarded })
|
||||
|
||||
if (!dev) document.addEventListener('contextmenu', (event) => event.preventDefault())
|
||||
|
||||
const osType = await type()
|
||||
if (osType === 'macos') {
|
||||
if ((await type()) === 'Darwin') {
|
||||
document.getElementsByTagName('html')[0].classList.add('mac')
|
||||
} else {
|
||||
document.getElementsByTagName('html')[0].classList.add('windows')
|
||||
@@ -126,12 +126,19 @@ initialize_state()
|
||||
})
|
||||
|
||||
const handleClose = async () => {
|
||||
await getCurrentWindow().close()
|
||||
await saveWindowState(StateFlags.ALL)
|
||||
await TauriWindow.getCurrent().close()
|
||||
}
|
||||
|
||||
TauriWindow.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
|
||||
await handleClose()
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
router.afterEach((to, from, failure) => {
|
||||
trackEvent('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
if (mixpanel_is_loaded()) {
|
||||
mixpanel_track('PageView', { path: to.path, fromPath: from.path, failed: failure })
|
||||
}
|
||||
})
|
||||
const route = useRoute()
|
||||
const isOnBrowse = computed(() => route.path.startsWith('/browse'))
|
||||
@@ -173,7 +180,13 @@ document.querySelector('body').addEventListener('click', function (e) {
|
||||
!target.href.startsWith('http://localhost') &&
|
||||
!target.href.startsWith('https://tauri.localhost')
|
||||
) {
|
||||
open(target.href)
|
||||
window.__TAURI_INVOKE__('tauri', {
|
||||
__tauriModule: 'Shell',
|
||||
message: {
|
||||
cmd: 'open',
|
||||
path: target.href,
|
||||
},
|
||||
})
|
||||
}
|
||||
e.preventDefault()
|
||||
break
|
||||
@@ -206,7 +219,7 @@ async function handleCommand(e) {
|
||||
// RunMRPack should directly install a local mrpack given a path
|
||||
if (e.path.endsWith('.mrpack')) {
|
||||
await install_from_file(e.path).catch(handleError)
|
||||
trackEvent('InstanceCreate', {
|
||||
mixpanel_track('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
@@ -275,14 +288,10 @@ async function handleCommand(e) {
|
||||
</section>
|
||||
</div>
|
||||
<section v-if="!nativeDecorations" class="window-controls">
|
||||
<Button class="titlebar-button" icon-only @click="() => getCurrentWindow().minimize()">
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.minimize()">
|
||||
<MinimizeIcon />
|
||||
</Button>
|
||||
<Button
|
||||
class="titlebar-button"
|
||||
icon-only
|
||||
@click="() => getCurrentWindow().toggleMaximize()"
|
||||
>
|
||||
<Button class="titlebar-button" icon-only @click="() => appWindow.toggleMaximize()">
|
||||
<MaximizeIcon />
|
||||
</Button>
|
||||
<Button class="titlebar-button close" icon-only @click="handleClose">
|
||||
|
||||
@@ -23,7 +23,7 @@ import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
|
||||
@@ -125,14 +125,14 @@ const handleOptionsClick = async (args) => {
|
||||
await run(args.item.path).catch((err) =>
|
||||
handleSevereError(err, { profilePath: args.item.path }),
|
||||
)
|
||||
trackEvent('InstanceStart', {
|
||||
mixpanel_track('InstanceStart', {
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
break
|
||||
case 'stop':
|
||||
await kill(args.item.path).catch(handleError)
|
||||
trackEvent('InstanceStop', {
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: args.item.loader,
|
||||
game_version: args.item.game_version,
|
||||
})
|
||||
|
||||
@@ -70,7 +70,7 @@ import {
|
||||
get_default_user,
|
||||
} from '@/helpers/auth'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
|
||||
@@ -118,7 +118,7 @@ async function login() {
|
||||
await refreshValues()
|
||||
}
|
||||
|
||||
trackEvent('AccountLogIn')
|
||||
mixpanel_track('AccountLogIn')
|
||||
}
|
||||
|
||||
const logout = async (id) => {
|
||||
@@ -130,7 +130,7 @@ const logout = async (id) => {
|
||||
} else {
|
||||
emit('change')
|
||||
}
|
||||
trackEvent('AccountLogOut')
|
||||
mixpanel_track('AccountLogOut')
|
||||
}
|
||||
|
||||
let showCard = ref(false)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownIcon, FolderOpenIcon, SearchIcon } from '@modrinth/assets'
|
||||
import { Button, OverflowMenu } from '@modrinth/ui'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { add_project_from_path } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
@@ -5,10 +5,10 @@ import { ChatIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.js'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const errorModal = ref()
|
||||
const error = ref()
|
||||
@@ -85,7 +85,7 @@ async function loginMinecraft() {
|
||||
await set_default_user(loggedIn.id).catch(handleError)
|
||||
}
|
||||
|
||||
await trackEvent('AccountLogIn', { source: 'ErrorModal' })
|
||||
await mixpanel.track('AccountLogIn')
|
||||
loadingMinecraft.value = false
|
||||
errorModal.value.hide()
|
||||
} catch (err) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Button, Checkbox, Modal } from '@modrinth/ui'
|
||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/theme'
|
||||
|
||||
|
||||
@@ -3,14 +3,14 @@ import { onUnmounted, ref, computed } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { StopCircleIcon, PlayIcon } from '@modrinth/assets'
|
||||
import { Card, Avatar, AnimatedLogo } from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { kill, run } from '@/helpers/profile'
|
||||
import { get_by_profile_path } from '@/helpers/process'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -45,7 +45,7 @@ const play = async (e, context) => {
|
||||
)
|
||||
modLoading.value = false
|
||||
|
||||
trackEvent('InstancePlay', {
|
||||
mixpanel_track('InstancePlay', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
@@ -58,7 +58,7 @@ const stop = async (e, context) => {
|
||||
|
||||
await kill(props.instance.path).catch(handleError)
|
||||
|
||||
trackEvent('InstanceStop', {
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
source: context,
|
||||
|
||||
@@ -211,12 +211,12 @@ import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
@@ -264,13 +264,13 @@ defineExpose({
|
||||
hide()
|
||||
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
|
||||
await install_from_file(event.payload[0]).catch(handleError)
|
||||
trackEvent('InstanceCreate', {
|
||||
mixpanel_track('InstanceCreate', {
|
||||
source: 'CreationModalFileDrop',
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
trackEvent('InstanceCreateStart', { source: 'CreationModal' })
|
||||
mixpanel_track('InstanceCreateStart', { source: 'CreationModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -360,7 +360,7 @@ const create_instance = async () => {
|
||||
icon.value,
|
||||
).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
mixpanel_track('InstanceCreate', {
|
||||
profile_name: profile_name.value,
|
||||
game_version: game_version.value,
|
||||
loader: loader.value,
|
||||
@@ -382,7 +382,7 @@ const upload_icon = async () => {
|
||||
})
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
@@ -419,7 +419,7 @@ const openFile = async () => {
|
||||
hide()
|
||||
await install_from_file(newProject).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
mixpanel_track('InstanceCreate', {
|
||||
source: 'CreationModalFileOpen',
|
||||
})
|
||||
}
|
||||
|
||||
@@ -40,8 +40,8 @@ import { Modal, Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -67,7 +67,7 @@ const emit = defineEmits(['submit'])
|
||||
function setJavaInstall(javaInstall) {
|
||||
emit('submit', javaInstall)
|
||||
detectJavaModal.value.hide()
|
||||
trackEvent('JavaAutoDetect', {
|
||||
mixpanel_track('JavaAutoDetect', {
|
||||
path: javaInstall.path,
|
||||
version: javaInstall.version,
|
||||
})
|
||||
|
||||
@@ -63,10 +63,10 @@ import {
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
|
||||
import { ref } from 'vue'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const props = defineProps({
|
||||
version: {
|
||||
@@ -113,7 +113,7 @@ async function testJava() {
|
||||
)
|
||||
testingJava.value = false
|
||||
|
||||
trackEvent('JavaTest', {
|
||||
mixpanel_track('JavaTest', {
|
||||
path: props.modelValue ? props.modelValue.path : '',
|
||||
success: testingJavaSuccess.value,
|
||||
})
|
||||
@@ -136,7 +136,7 @@ async function handleJavaFileInput() {
|
||||
}
|
||||
}
|
||||
|
||||
trackEvent('JavaManualSelect', {
|
||||
mixpanel_track('JavaManualSelect', {
|
||||
path: filePath,
|
||||
version: props.version,
|
||||
})
|
||||
@@ -170,7 +170,7 @@ async function reinstallJava() {
|
||||
}
|
||||
}
|
||||
|
||||
trackEvent('JavaReInstall', {
|
||||
mixpanel_track('JavaReInstall', {
|
||||
path: path,
|
||||
version: props.version,
|
||||
})
|
||||
|
||||
@@ -117,9 +117,9 @@ import { useRouter } from 'vue-router'
|
||||
import { progress_bars_list } from '@/helpers/state.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { get_many } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const router = useRouter()
|
||||
const card = ref(null)
|
||||
@@ -164,7 +164,7 @@ const stop = async (process) => {
|
||||
try {
|
||||
await killProcess(process.uuid).catch(handleError)
|
||||
|
||||
trackEvent('InstanceStop', {
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: process.profile.loader,
|
||||
game_version: process.profile.game_version,
|
||||
source: 'AppBar',
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div v-if="!hidden" class="splash-screen dark" :class="{ 'fade-out': doneLoading }">
|
||||
<div v-if="os !== 'MacOS'" class="app-buttons">
|
||||
<button class="btn icon-only transparent" icon-only @click="() => getCurrent().minimize()">
|
||||
<button class="btn icon-only transparent" icon-only @click="() => appWindow.minimize()">
|
||||
<MinimizeIcon />
|
||||
</button>
|
||||
<button class="btn icon-only transparent" @click="() => getCurrent().toggleMaximize()">
|
||||
<button class="btn icon-only transparent" @click="() => appWindow.toggleMaximize()">
|
||||
<MaximizeIcon />
|
||||
</button>
|
||||
<button class="btn icon-only transparent" @click="handleClose">
|
||||
@@ -85,11 +85,12 @@
|
||||
import { ref, watch } from 'vue'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
import { loading_listener } from '@/helpers/events.js'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { appWindow } from '@tauri-apps/api/window'
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js'
|
||||
import { window as TauriWindow } from '@tauri-apps/api'
|
||||
import { TauriEvent } from '@tauri-apps/api/event'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { saveWindowState, StateFlags } from 'tauri-plugin-window-state-api'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { useLoading } from '@/store/loading.js'
|
||||
|
||||
@@ -137,8 +138,13 @@ loading_listener(async (e) => {
|
||||
})
|
||||
|
||||
const handleClose = async () => {
|
||||
await getCurrentWindow().close()
|
||||
await saveWindowState(StateFlags.ALL)
|
||||
await TauriWindow.getCurrent().close()
|
||||
}
|
||||
|
||||
TauriWindow.getCurrent().listen(TauriEvent.WINDOW_CLOSE_REQUESTED, async () => {
|
||||
await handleClose()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -61,7 +61,7 @@ import { formatCategory } from '@modrinth/utils'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
import { ref } from 'vue'
|
||||
import { handleError, useTheming } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -87,7 +87,7 @@ defineExpose({
|
||||
|
||||
incompatibleModal.value.show()
|
||||
|
||||
trackEvent('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectIncompatibilityWarningModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -98,7 +98,7 @@ const install = async () => {
|
||||
onInstall.value(selectedVersion.value.id)
|
||||
incompatibleModal.value.hide()
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
id: project.value,
|
||||
|
||||
@@ -3,7 +3,7 @@ import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button, Modal } from '@modrinth/ui'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { ref } from 'vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
|
||||
@@ -25,7 +25,7 @@ defineExpose({
|
||||
|
||||
onInstall.value = callback
|
||||
|
||||
trackEvent('PackInstallStart')
|
||||
mixpanel_track('PackInstallStart')
|
||||
},
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ async function install() {
|
||||
project.value.title,
|
||||
project.value.icon_url,
|
||||
).catch(handleError)
|
||||
trackEvent('PackInstall', {
|
||||
mixpanel_track('PackInstall', {
|
||||
id: project.value.id,
|
||||
version_id: versionId.value,
|
||||
title: project.value.title,
|
||||
|
||||
@@ -16,13 +16,13 @@ import {
|
||||
list,
|
||||
create,
|
||||
} from '@/helpers/profile'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { installVersionDependencies } from '@/store/install.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { tauri } from '@tauri-apps/api'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
@@ -88,7 +88,7 @@ defineExpose({
|
||||
|
||||
installModal.value.show()
|
||||
|
||||
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
mixpanel_track('ProjectInstallStart', { source: 'ProjectInstallModal' })
|
||||
},
|
||||
})
|
||||
|
||||
@@ -115,7 +115,7 @@ async function install(instance) {
|
||||
instance.installedMod = true
|
||||
instance.installing = false
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.value.id,
|
||||
@@ -137,7 +137,7 @@ const toggleCreation = () => {
|
||||
loader.value = null
|
||||
|
||||
if (showCreation.value) {
|
||||
trackEvent('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
mixpanel_track('InstanceCreateStart', { source: 'ProjectInstallModal' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,7 +153,7 @@ const upload_icon = async () => {
|
||||
})
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
display_icon.value = tauri.convertFileSrc(icon.value)
|
||||
}
|
||||
|
||||
const reset_icon = () => {
|
||||
@@ -186,7 +186,7 @@ const createInstance = async () => {
|
||||
const instance = await get(id, true)
|
||||
await installVersionDependencies(instance, versions.value[0])
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
mixpanel_track('InstanceCreate', {
|
||||
profile_name: name.value,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
loader: loader,
|
||||
@@ -195,7 +195,7 @@ const createInstance = async () => {
|
||||
source: 'ProjectInstallModal',
|
||||
})
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: loader,
|
||||
game_version: versions.value[0].game_versions[0],
|
||||
id: project.value,
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
import { posthog } from 'posthog-js'
|
||||
|
||||
export const initAnalytics = () => {
|
||||
posthog.init('phc_hm2ihMpTAoE86xIm7XzsCB8RPiTRKivViK5biiHedm', {
|
||||
persistence: 'localStorage',
|
||||
})
|
||||
}
|
||||
|
||||
export const debugAnalytics = () => {
|
||||
posthog.debug()
|
||||
}
|
||||
|
||||
export const optOutAnalytics = () => {
|
||||
posthog.opt_out_capturing()
|
||||
}
|
||||
|
||||
export const optInAnalytics = () => {
|
||||
posthog.opt_in_capturing()
|
||||
}
|
||||
|
||||
export const trackEvent = (eventName, properties) => {
|
||||
posthog.capture(eventName, properties)
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Example function:
|
||||
// User goes to auth_url to complete flow, and when completed, authenticate_await_completion() returns the credentials
|
||||
@@ -13,46 +13,35 @@ import { invoke } from '@tauri-apps/api/core'
|
||||
// await authenticate_await_completion()
|
||||
// }
|
||||
|
||||
/**
|
||||
* Authenticate a user with Hydra - part 1.
|
||||
* This begins the authentication flow quasi-synchronously.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
/// Authenticate a user with Hydra - part 1
|
||||
/// This begins the authentication flow quasi-synchronously
|
||||
/// This returns a DeviceLoginSuccess object, with two relevant fields:
|
||||
/// - verification_uri: the URL to go to to complete the flow
|
||||
/// - user_code: the code to enter on the verification_uri page
|
||||
export async function login() {
|
||||
return await invoke('plugin:auth|login')
|
||||
return await invoke('auth_login')
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the default user
|
||||
* @return {Promise<UUID | undefined>}
|
||||
*/
|
||||
/// Retrieves the default user
|
||||
/// user is UUID
|
||||
export async function get_default_user() {
|
||||
return await invoke('plugin:auth|get_default_user')
|
||||
return await invoke('plugin:auth|auth_get_default_user')
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the default user
|
||||
* @param {UUID} user
|
||||
*/
|
||||
/// Updates the default user
|
||||
/// user is UUID
|
||||
export async function set_default_user(user) {
|
||||
return await invoke('plugin:auth|set_default_user', { user })
|
||||
return await invoke('plugin:auth|auth_set_default_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a user account from the database
|
||||
* @param {UUID} user
|
||||
*/
|
||||
/// Remove a user account from the database
|
||||
/// user is UUID
|
||||
export async function remove_user(user) {
|
||||
return await invoke('plugin:auth|remove_user', { user })
|
||||
return await invoke('plugin:auth|auth_remove_user', { user })
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of users
|
||||
* @returns {Promise<Credential[]>}
|
||||
*/
|
||||
/// Returns a list of users
|
||||
/// Returns an Array of Credentials
|
||||
export async function users() {
|
||||
return await invoke('plugin:auth|get_users')
|
||||
return await invoke('plugin:auth|auth_users')
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function get_project(id, cacheBehaviour) {
|
||||
return await invoke('plugin:cache|get_project', { id, cacheBehaviour })
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { create } from './profile'
|
||||
|
||||
/*
|
||||
@@ -27,7 +27,7 @@ import { create } from './profile'
|
||||
/// eg: get_importable_instances("MultiMC", "C:/MultiMC")
|
||||
/// returns ["Instance 1", "Instance 2"]
|
||||
export async function get_importable_instances(launcherType, basePath) {
|
||||
return await invoke('plugin:import|get_importable_instances', { launcherType, basePath })
|
||||
return await invoke('plugin:import|import_get_importable_instances', { launcherType, basePath })
|
||||
}
|
||||
|
||||
/// 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
|
||||
const profilePath = await create(instanceFolder, '1.19.4', 'vanilla', 'latest', null, true)
|
||||
|
||||
return await invoke('plugin:import|import_instance', {
|
||||
return await invoke('plugin:import|import_import_instance', {
|
||||
profilePath,
|
||||
launcherType,
|
||||
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
|
||||
/// eg: is_valid_importable_instance("C:/MultiMC/Instance 1", "MultiMC")
|
||||
export async function is_valid_importable_instance(instanceFolder, launcherType) {
|
||||
return await invoke('plugin:import|is_valid_importable_instance', {
|
||||
return await invoke('plugin:import|import_is_valid_importable_instance', {
|
||||
instanceFolder,
|
||||
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
|
||||
/// eg: get_default_launcher_path("MultiMC")
|
||||
export async function get_default_launcher_path(launcherType) {
|
||||
return await invoke('plugin:import|get_default_launcher_path', { launcherType })
|
||||
return await invoke('plugin:import|import_get_default_launcher_path', { launcherType })
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/*
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/*
|
||||
A log is a struct containing the filename string, stdout, and stderr, as follows:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/// Gets the game versions from daedalus
|
||||
// Returns a VersionManifest
|
||||
|
||||
57
apps/app-frontend/src/helpers/mixpanel.js
Normal file
57
apps/app-frontend/src/helpers/mixpanel.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import mixpanel from 'mixpanel-browser'
|
||||
|
||||
// mixpanel_track
|
||||
function trackWrapper(originalTrack) {
|
||||
return function (event_name, properties = {}) {
|
||||
try {
|
||||
originalTrack(event_name, properties)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
export const mixpanel_track = trackWrapper(mixpanel.track.bind(mixpanel))
|
||||
|
||||
// mixpanel_opt_out_tracking()
|
||||
function optOutTrackingWrapper(originalOptOutTracking) {
|
||||
return function () {
|
||||
try {
|
||||
originalOptOutTracking()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
export const mixpanel_opt_out_tracking = optOutTrackingWrapper(
|
||||
mixpanel.opt_out_tracking.bind(mixpanel),
|
||||
)
|
||||
|
||||
// mixpanel_opt_in_tracking()
|
||||
function optInTrackingWrapper(originalOptInTracking) {
|
||||
return function () {
|
||||
try {
|
||||
originalOptInTracking()
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
export const mixpanel_opt_in_tracking = optInTrackingWrapper(
|
||||
mixpanel.opt_in_tracking.bind(mixpanel),
|
||||
)
|
||||
|
||||
// mixpanel_init
|
||||
function initWrapper(originalInit) {
|
||||
return function (token, config = {}) {
|
||||
try {
|
||||
originalInit(token, config)
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
export const mixpanel_init = initWrapper(mixpanel.init.bind(mixpanel))
|
||||
|
||||
export const mixpanel_is_loaded = () => {
|
||||
return mixpanel.__loaded
|
||||
}
|
||||
@@ -3,22 +3,22 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function login(provider) {
|
||||
return await invoke('modrinth_auth_login', { provider })
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
return await invoke('plugin:mr-auth|create_account', {
|
||||
return await invoke('plugin:mr_auth|create_account', {
|
||||
username,
|
||||
email,
|
||||
password,
|
||||
@@ -28,9 +28,9 @@ export async function create_account(username, email, password, challenge, signU
|
||||
}
|
||||
|
||||
export async function logout() {
|
||||
return await invoke('plugin:mr-auth|logout')
|
||||
return await invoke('plugin:mr_auth|logout')
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
return await invoke('plugin:mr-auth|get')
|
||||
return await invoke('plugin:mr_auth|get')
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
import { create } from './profile'
|
||||
|
||||
// Installs pack from a version ID
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/// Gets all running process IDs with a given profile path
|
||||
/// Returns [u32]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
/// Add instance
|
||||
/*
|
||||
@@ -19,7 +19,7 @@ import { invoke } from '@tauri-apps/api/core'
|
||||
export async function create(name, gameVersion, modloader, loaderVersion, iconPath, skipInstall) {
|
||||
//Trim string name to avoid "Unable to find directory"
|
||||
name = name.trim()
|
||||
return await invoke('plugin:profile-create|profile_create', {
|
||||
return await invoke('plugin:profile_create|profile_create', {
|
||||
name,
|
||||
gameVersion,
|
||||
modloader,
|
||||
@@ -31,7 +31,7 @@ export async function create(name, gameVersion, modloader, loaderVersion, iconPa
|
||||
|
||||
// duplicate a profile
|
||||
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
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Settings object
|
||||
/*
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Initialize the theseus API state
|
||||
// This should be called during the initializion/opening of the launcher
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* So, for example, addDefaultInstance creates a blank Profile object, where the Rust struct is serialized,
|
||||
* and deserialized into a usable JS object.
|
||||
*/
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
// Gets cached category tags
|
||||
export async function get_categories() {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { get_full_path, get_mod_full_path } from '@/helpers/profile'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { invoke } from '@tauri-apps/api/tauri'
|
||||
|
||||
export async function isDev() {
|
||||
return await invoke('is_dev')
|
||||
|
||||
@@ -4,8 +4,8 @@ import App from '@/App.vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import FloatingVue from 'floating-vue'
|
||||
import 'floating-vue/dist/style.css'
|
||||
import loadCssMixin from './mixins/macCssFix.js'
|
||||
import { createPlugin } from '@vintl/vintl/plugin'
|
||||
import * as Sentry from '@sentry/vue'
|
||||
|
||||
const VIntlPlugin = createPlugin({
|
||||
controllerOpts: {
|
||||
@@ -27,17 +27,10 @@ const VIntlPlugin = createPlugin({
|
||||
const pinia = createPinia()
|
||||
|
||||
let app = createApp(App)
|
||||
|
||||
Sentry.init({
|
||||
app,
|
||||
dsn: 'https://9508775ee5034536bc70433f5f531dd4@o485889.ingest.us.sentry.io/4504579615227904',
|
||||
integrations: [Sentry.browserTracingIntegration({ router })],
|
||||
tracesSampleRate: 0.1,
|
||||
})
|
||||
|
||||
app.use(router)
|
||||
app.use(pinia)
|
||||
app.use(FloatingVue)
|
||||
app.mixin(loadCssMixin)
|
||||
app.use(VIntlPlugin)
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
27
apps/app-frontend/src/mixins/macCssFix.js
Normal file
27
apps/app-frontend/src/mixins/macCssFix.js
Normal file
@@ -0,0 +1,27 @@
|
||||
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)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -19,7 +19,7 @@ import { get_categories, get_loaders, get_game_versions } from '@/helpers/tags'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { get_search_results } from '@/helpers/cache.js'
|
||||
import { debounce } from '@/helpers/utils.js'
|
||||
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||
|
||||
@@ -8,8 +8,8 @@ import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/j
|
||||
import { get as getCreds, logout } from '@/helpers/mr_auth.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import ModrinthLoginScreen from '@/components/ui/tutorial/ModrinthLoginScreen.vue'
|
||||
import { optOutAnalytics, optInAnalytics } from '@/helpers/analytics'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { mixpanel_opt_out_tracking, mixpanel_opt_in_tracking } from '@/helpers/mixpanel'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { get_user, purge_cache_types } from '@/helpers/cache.js'
|
||||
@@ -45,9 +45,9 @@ watch(
|
||||
const setSettings = JSON.parse(JSON.stringify(newSettings))
|
||||
|
||||
if (setSettings.telemetry) {
|
||||
optInAnalytics()
|
||||
mixpanel_opt_out_tracking()
|
||||
} else {
|
||||
optOutAnalytics()
|
||||
mixpanel_opt_in_tracking()
|
||||
}
|
||||
|
||||
setSettings.extra_launch_args = setSettings.launchArgs.trim().split(/\s+/).filter(Boolean)
|
||||
|
||||
@@ -131,8 +131,9 @@ import { ref, onUnmounted } from 'vue'
|
||||
import { handleError, useBreadcrumbs, useLoading } from '@/store/state'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { useFetch } from '@/helpers/fetch'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { get_project, get_version_many } from '@/helpers/cache.js'
|
||||
import dayjs from 'dayjs'
|
||||
@@ -182,7 +183,7 @@ const startInstance = async (context) => {
|
||||
}
|
||||
loading.value = false
|
||||
|
||||
trackEvent('InstanceStart', {
|
||||
mixpanel_track('InstanceStart', {
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
source: context,
|
||||
@@ -219,7 +220,7 @@ const stopInstance = async (context) => {
|
||||
playing.value = false
|
||||
await kill(route.params.id).catch(handleError)
|
||||
|
||||
trackEvent('InstanceStop', {
|
||||
mixpanel_track('InstanceStop', {
|
||||
loader: instance.value.loader,
|
||||
game_version: instance.value.game_version,
|
||||
source: context,
|
||||
|
||||
@@ -379,7 +379,7 @@ import {
|
||||
update_project,
|
||||
} from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||
@@ -682,7 +682,7 @@ const updateAll = async () => {
|
||||
projects.value[project].updating = false
|
||||
}
|
||||
|
||||
trackEvent('InstanceUpdateAll', {
|
||||
mixpanel_track('InstanceUpdateAll', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
count: setProjects.length,
|
||||
@@ -708,7 +708,7 @@ const updateProject = async (mod) => {
|
||||
mod.version = mod.updateVersion.version_number
|
||||
mod.updateVersion = null
|
||||
|
||||
trackEvent('InstanceProjectUpdate', {
|
||||
mixpanel_track('InstanceProjectUpdate', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
@@ -735,7 +735,7 @@ const toggleDisableMod = async (mod) => {
|
||||
.then((newPath) => {
|
||||
mod.path = newPath
|
||||
mod.disabled = !mod.disabled
|
||||
trackEvent('InstanceProjectDisable', {
|
||||
mixpanel_track('InstanceProjectDisable', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
@@ -756,7 +756,7 @@ const removeMod = async (mod) => {
|
||||
await remove_project(props.instance.path, mod.path).catch(handleError)
|
||||
projects.value = projects.value.filter((x) => mod.path !== x.path)
|
||||
|
||||
trackEvent('InstanceProjectRemove', {
|
||||
mixpanel_track('InstanceProjectRemove', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
id: mod.id,
|
||||
|
||||
@@ -541,15 +541,15 @@ import { computed, readonly, ref, shallowRef, watch } from 'vue'
|
||||
import { get_max_memory } from '@/helpers/jre.js'
|
||||
import { get } from '@/helpers/settings.js'
|
||||
import JavaSelector from '@/components/ui/JavaSelector.vue'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import { open } from '@tauri-apps/api/dialog'
|
||||
import { get_loader_versions } from '@/helpers/metadata.js'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -590,7 +590,7 @@ const availableGroups = ref([
|
||||
async function resetIcon() {
|
||||
icon.value = null
|
||||
await edit_icon(props.instance.path, null).catch(handleError)
|
||||
trackEvent('InstanceRemoveIcon')
|
||||
mixpanel_track('InstanceRemoveIcon')
|
||||
}
|
||||
|
||||
async function setIcon() {
|
||||
@@ -609,7 +609,7 @@ async function setIcon() {
|
||||
icon.value = value
|
||||
await edit_icon(props.instance.path, icon.value).catch(handleError)
|
||||
|
||||
trackEvent('InstanceSetIcon')
|
||||
mixpanel_track('InstanceSetIcon')
|
||||
}
|
||||
|
||||
const globalSettings = await get().catch(handleError)
|
||||
@@ -754,7 +754,7 @@ const repairing = ref(false)
|
||||
|
||||
async function duplicateProfile() {
|
||||
await duplicate(props.instance.path).catch(handleError)
|
||||
trackEvent('InstanceDuplicate', {
|
||||
mixpanel_track('InstanceDuplicate', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
@@ -765,7 +765,7 @@ async function repairProfile(force) {
|
||||
await install(props.instance.path, force).catch(handleError)
|
||||
repairing.value = false
|
||||
|
||||
trackEvent('InstanceRepair', {
|
||||
mixpanel_track('InstanceRepair', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
@@ -796,7 +796,7 @@ async function repairModpack() {
|
||||
await update_repair_modrinth(props.instance.path).catch(handleError)
|
||||
inProgress.value = false
|
||||
|
||||
trackEvent('InstanceRepair', {
|
||||
mixpanel_track('InstanceRepair', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
@@ -808,7 +808,7 @@ async function removeProfile() {
|
||||
await remove(props.instance.path).catch(handleError)
|
||||
removing.value = false
|
||||
|
||||
trackEvent('InstanceRemove', {
|
||||
mixpanel_track('InstanceRemove', {
|
||||
loader: props.instance.loader,
|
||||
game_version: props.instance.game_version,
|
||||
})
|
||||
|
||||
@@ -93,7 +93,7 @@ import {
|
||||
} from '@modrinth/assets'
|
||||
import { Button, Card } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel'
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
@@ -112,7 +112,7 @@ const nextImage = () => {
|
||||
expandedGalleryIndex.value = 0
|
||||
}
|
||||
expandedGalleryItem.value = props.project.gallery[expandedGalleryIndex.value]
|
||||
trackEvent('GalleryImageNext', {
|
||||
mixpanel_track('GalleryImageNext', {
|
||||
project_id: props.project.id,
|
||||
url: expandedGalleryItem.value.url,
|
||||
})
|
||||
@@ -124,7 +124,7 @@ const previousImage = () => {
|
||||
expandedGalleryIndex.value = props.project.gallery.length - 1
|
||||
}
|
||||
expandedGalleryItem.value = props.project.gallery[expandedGalleryIndex.value]
|
||||
trackEvent('GalleryImagePrevious', {
|
||||
mixpanel_track('GalleryImagePrevious', {
|
||||
project_id: props.project.id,
|
||||
url: expandedGalleryItem.value,
|
||||
})
|
||||
@@ -135,7 +135,7 @@ const expandImage = (item, index) => {
|
||||
expandedGalleryIndex.value = index
|
||||
zoomedIn.value = false
|
||||
|
||||
trackEvent('GalleryImageExpand', {
|
||||
mixpanel_track('GalleryImageExpand', {
|
||||
project_id: props.project.id,
|
||||
url: item.url,
|
||||
})
|
||||
|
||||
@@ -257,7 +257,7 @@ import { useRoute } from 'vue-router'
|
||||
import { ref, shallowRef, watch } from 'vue'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { convertFileSrc } from '@tauri-apps/api/tauri'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js'
|
||||
|
||||
@@ -139,7 +139,7 @@ export default new createRouter({
|
||||
linkExactActiveClass: 'router-link-exact-active',
|
||||
scrollBehavior() {
|
||||
// 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')?.scrollTo(0, 0)
|
||||
document.querySelector('.router-view').scrollTop = 0
|
||||
return {
|
||||
el: '.router-view',
|
||||
top: 0,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_project, get_version_many } from '@/helpers/cache.js'
|
||||
import { install as packInstall } from '@/helpers/pack.js'
|
||||
import { trackEvent } from '@/helpers/analytics.js'
|
||||
import { mixpanel_track } from '@/helpers/mixpanel.js'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
export const useInstall = defineStore('installStore', {
|
||||
@@ -51,7 +51,7 @@ export const install = async (projectId, versionId, instancePath, source, callba
|
||||
if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) {
|
||||
await packInstall(project.id, version, project.title, project.icon_url).catch(handleError)
|
||||
|
||||
trackEvent('PackInstall', {
|
||||
mixpanel_track('PackInstall', {
|
||||
id: project.id,
|
||||
version_id: version,
|
||||
title: project.title,
|
||||
@@ -107,7 +107,7 @@ export const install = async (projectId, versionId, instancePath, source, callba
|
||||
await add_project_from_version(instance.path, version.id).catch(handleError)
|
||||
await installVersionDependencies(instance, version)
|
||||
|
||||
trackEvent('ProjectInstall', {
|
||||
mixpanel_track('ProjectInstall', {
|
||||
loader: instance.loader,
|
||||
game_version: instance.game_version,
|
||||
id: project.id,
|
||||
|
||||
@@ -23,3 +23,7 @@ export const handleError = (err) => {
|
||||
})
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
export const handleMixpanelError = (err) => {
|
||||
console.error(err)
|
||||
}
|
||||
|
||||
@@ -10,13 +10,14 @@ theseus = { path = "../../packages/app-lib", features = ["cli"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = "2.0.0-rc.4"
|
||||
tauri = { version = "1.7.1", features = ["shell-open"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
webbrowser = "0.8.13"
|
||||
dunce = "1.0.3"
|
||||
|
||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
futures = "0.3"
|
||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
||||
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
[package]
|
||||
name = "theseus_gui"
|
||||
version = "0.8.3-1"
|
||||
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/modrinth/code/apps/app/"
|
||||
description = "A Tauri App"
|
||||
authors = ["you"]
|
||||
license = ""
|
||||
repository = ""
|
||||
edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc", features = ["codegen"] }
|
||||
tauri-build = { version = "1.5.3", features = [] }
|
||||
|
||||
[dependencies]
|
||||
theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
@@ -16,16 +19,14 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
tauri = { version = "2.0.0-rc.6", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
||||
tauri-plugin-window-state = "2.0.0-rc"
|
||||
tauri-plugin-deep-link = "2.0.0-rc"
|
||||
tauri-plugin-os = "2.0.0-rc"
|
||||
tauri-plugin-shell = "2.0.0-rc"
|
||||
tauri-plugin-dialog = "2.0.0-rc"
|
||||
tauri-plugin-updater = { version = "2.0.0-rc.1", optional = true }
|
||||
tauri = { 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-plugin-single-instance = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-window-state = { git = "https://github.com/tauri-apps/plugins-workspace", branch = "v1" }
|
||||
tauri-plugin-deep-link = "0.1.2"
|
||||
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
tokio-stream = { version = "0.1", features = ["fs"] }
|
||||
futures = "0.3"
|
||||
daedalus = "0.2.3"
|
||||
chrono = "0.4.26"
|
||||
@@ -55,7 +56,6 @@ window-shadows = "0.2.1"
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
cocoa = "0.25.0"
|
||||
objc = "0.2.7"
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
@@ -64,4 +64,3 @@ default = ["custom-protocol"]
|
||||
# this feature is used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
updater = ["dep:tauri-plugin-updater"]
|
||||
|
||||
@@ -6,17 +6,63 @@
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleURLName</key>
|
||||
<!-- Obviously needs to be replaced with your app's bundle identifier -->
|
||||
<string>ModrinthApp</string>
|
||||
<key>CFBundleURLSchemes</key>
|
||||
<array>
|
||||
<!-- register the myapp:// and myscheme:// schemes -->
|
||||
<string>modrinth</string>
|
||||
<string>modrinthscheme</string>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your camera.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your microphone.</string>
|
||||
<!-- Declare file types your app can open -->
|
||||
<key>CFBundleDocumentTypes</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>Modrinth type</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
<string>Owner</string>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array>
|
||||
<string>com.modrinth.theseus-type</string>
|
||||
</array>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>NSDocument</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>UTImportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array>
|
||||
<string>public.data</string>
|
||||
</array>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>Modrinth File</string>
|
||||
<key>UTTypeIcons</key>
|
||||
<dict/>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>com.modrinth.theseus-type</string>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array>
|
||||
<string>mrpack</string>
|
||||
</array>
|
||||
<key>public.mime-type</key>
|
||||
<array>
|
||||
<string>application/x-mrpack</string>
|
||||
</array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your camera.</string>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>A Minecraft mod wants to access your microphone.</string>
|
||||
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,223 +1,4 @@
|
||||
use tauri_build::{DefaultPermissionRule, InlinedPlugin};
|
||||
|
||||
fn main() {
|
||||
// Sadly, there is no better way to do it right now
|
||||
// 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");
|
||||
// Build the Tauri app
|
||||
tauri_build::build();
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
]
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"identifier": "plugins",
|
||||
"description": "",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"dialog:allow-open",
|
||||
"dialog:allow-confirm",
|
||||
"shell:allow-open",
|
||||
"os:allow-platform",
|
||||
"os:allow-version",
|
||||
"os:allow-os-type",
|
||||
"os:allow-family",
|
||||
"os:allow-arch",
|
||||
"os:allow-exe-extension",
|
||||
"os:allow-locale",
|
||||
"os:allow-hostname",
|
||||
"deep-link:default",
|
||||
"window-state:default",
|
||||
"window-state:allow-restore-state",
|
||||
"window-state:allow-save-window-state",
|
||||
|
||||
"auth:default",
|
||||
"import:default",
|
||||
"jre:default",
|
||||
"logs:default",
|
||||
"metadata:default",
|
||||
"mr-auth:default",
|
||||
"profile-create:default",
|
||||
"pack:default",
|
||||
"process:default",
|
||||
"profile:default",
|
||||
"cache:default",
|
||||
"settings:default",
|
||||
"tags:default",
|
||||
"utils:default"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"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
@@ -2,17 +2,16 @@
|
||||
"name": "@modrinth/app",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"dev": "tauri dev",
|
||||
"test": "cargo test",
|
||||
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-rc.5"
|
||||
"@tauri-apps/cli": "^1.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/app-frontend": "workspace:*",
|
||||
"@modrinth/app-lib": "workspace:*"
|
||||
"@modrinth/app-lib": "workspace:*",
|
||||
"@modrinth/app-frontend": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
use crate::api::Result;
|
||||
use chrono::{Duration, Utc};
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Manager, Runtime, UserAttentionType};
|
||||
use tauri::{Manager, UserAttentionType};
|
||||
use theseus::prelude::*;
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("auth")
|
||||
pub fn init<R: tauri::Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("auth")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
login,
|
||||
remove_user,
|
||||
get_default_user,
|
||||
set_default_user,
|
||||
get_users,
|
||||
auth_get_default_user,
|
||||
auth_set_default_user,
|
||||
auth_remove_user,
|
||||
auth_users,
|
||||
])
|
||||
.build()
|
||||
}
|
||||
@@ -19,21 +18,19 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
/// 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)
|
||||
#[tauri::command]
|
||||
pub async fn login<R: Runtime>(
|
||||
app: tauri::AppHandle<R>,
|
||||
) -> Result<Option<Credentials>> {
|
||||
pub async fn auth_login(app: tauri::AppHandle) -> Result<Option<Credentials>> {
|
||||
let flow = minecraft_auth::begin_login().await?;
|
||||
|
||||
let start = Utc::now();
|
||||
|
||||
if let Some(window) = app.get_webview_window("signin") {
|
||||
if let Some(window) = app.get_window("signin") {
|
||||
window.close()?;
|
||||
}
|
||||
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
let window = tauri::WindowBuilder::new(
|
||||
&app,
|
||||
"signin",
|
||||
tauri::WebviewUrl::External(flow.redirect_uri.parse().map_err(
|
||||
tauri::WindowUrl::External(flow.redirect_uri.parse().map_err(
|
||||
|_| {
|
||||
theseus::ErrorKind::OtherError(
|
||||
"Error parsing auth redirect URL".to_string(),
|
||||
@@ -56,12 +53,12 @@ pub async fn login<R: Runtime>(
|
||||
}
|
||||
|
||||
if window
|
||||
.url()?
|
||||
.url()
|
||||
.as_str()
|
||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||
{
|
||||
if let Some((_, code)) =
|
||||
window.url()?.query_pairs().find(|x| x.0 == "code")
|
||||
window.url().query_pairs().find(|x| x.0 == "code")
|
||||
{
|
||||
window.close()?;
|
||||
let val =
|
||||
@@ -78,22 +75,23 @@ pub async fn login<R: Runtime>(
|
||||
Ok(None)
|
||||
}
|
||||
#[tauri::command]
|
||||
pub async fn remove_user(user: uuid::Uuid) -> Result<()> {
|
||||
pub async fn auth_remove_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::remove_user(user).await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_default_user() -> Result<Option<uuid::Uuid>> {
|
||||
pub async fn auth_get_default_user() -> Result<Option<uuid::Uuid>> {
|
||||
Ok(minecraft_auth::get_default_user().await?)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn set_default_user(user: uuid::Uuid) -> Result<()> {
|
||||
pub async fn auth_set_default_user(user: uuid::Uuid) -> Result<()> {
|
||||
Ok(minecraft_auth::set_default_user(user).await?)
|
||||
}
|
||||
|
||||
/// Get a copy of the list of all user credentials
|
||||
// invoke('plugin:auth|auth_users',user)
|
||||
#[tauri::command]
|
||||
pub async fn get_users() -> Result<Vec<Credentials>> {
|
||||
pub async fn auth_users() -> Result<Vec<Credentials>> {
|
||||
Ok(minecraft_auth::users().await?)
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ use theseus::pack::import;
|
||||
pub fn init<R: tauri::Runtime>() -> tauri::plugin::TauriPlugin<R> {
|
||||
tauri::plugin::Builder::new("import")
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_importable_instances,
|
||||
import_instance,
|
||||
is_valid_importable_instance,
|
||||
get_default_launcher_path,
|
||||
import_get_importable_instances,
|
||||
import_import_instance,
|
||||
import_is_valid_importable_instance,
|
||||
import_get_default_launcher_path,
|
||||
])
|
||||
.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"))
|
||||
/// returns ["Instance 1", "Instance 2"]
|
||||
#[tauri::command]
|
||||
pub async fn get_importable_instances(
|
||||
pub async fn import_get_importable_instances(
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
) -> Result<Vec<String>> {
|
||||
@@ -31,7 +31,7 @@ pub async fn get_importable_instances(
|
||||
/// 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")
|
||||
#[tauri::command]
|
||||
pub async fn import_instance(
|
||||
pub async fn import_import_instance(
|
||||
profile_path: &str,
|
||||
launcher_type: ImportLauncherType,
|
||||
base_path: PathBuf,
|
||||
@@ -50,7 +50,7 @@ pub async fn import_instance(
|
||||
/// 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)
|
||||
#[tauri::command]
|
||||
pub async fn is_valid_importable_instance(
|
||||
pub async fn import_is_valid_importable_instance(
|
||||
instance_folder: PathBuf,
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<bool> {
|
||||
@@ -63,7 +63,7 @@ pub async fn is_valid_importable_instance(
|
||||
/// Returns the default path for the given launcher type
|
||||
/// None if it can't be found or doesn't exist
|
||||
#[tauri::command]
|
||||
pub async fn get_default_launcher_path(
|
||||
pub async fn import_get_default_launcher_path(
|
||||
launcher_type: ImportLauncherType,
|
||||
) -> Result<Option<PathBuf>> {
|
||||
Ok(import::get_default_launcher_path(launcher_type))
|
||||
|
||||
@@ -39,6 +39,10 @@ pub enum TheseusSerializableError {
|
||||
|
||||
#[error("Tauri error: {0}")]
|
||||
Tauri(#[from] tauri::Error),
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[error("Callback error: {0}")]
|
||||
Callback(String),
|
||||
}
|
||||
|
||||
// Generic implementation of From<T> for ErrorTypeA
|
||||
@@ -86,6 +90,14 @@ macro_rules! impl_serialize {
|
||||
}
|
||||
|
||||
// Use the macro to implement Serialize for TheseusSerializableError
|
||||
#[cfg(target_os = "macos")]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
Callback
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
|
||||
@@ -5,7 +5,7 @@ use tauri::{Manager, UserAttentionType};
|
||||
use theseus::prelude::*;
|
||||
|
||||
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![
|
||||
login_pass,
|
||||
login_2fa,
|
||||
@@ -25,14 +25,14 @@ pub async fn modrinth_auth_login(
|
||||
|
||||
let start = Utc::now();
|
||||
|
||||
if let Some(window) = app.get_webview_window("modrinth-signin") {
|
||||
if let Some(window) = app.get_window("modrinth-signin") {
|
||||
window.close()?;
|
||||
}
|
||||
|
||||
let window = tauri::WebviewWindowBuilder::new(
|
||||
let window = tauri::WindowBuilder::new(
|
||||
&app,
|
||||
"modrinth-signin",
|
||||
tauri::WebviewUrl::External(redirect_uri.parse().map_err(|_| {
|
||||
tauri::WindowUrl::External(redirect_uri.parse().map_err(|_| {
|
||||
theseus::ErrorKind::OtherError(
|
||||
"Error parsing auth redirect URL".to_string(),
|
||||
)
|
||||
@@ -53,12 +53,12 @@ pub async fn modrinth_auth_login(
|
||||
}
|
||||
|
||||
if window
|
||||
.url()?
|
||||
.url()
|
||||
.as_str()
|
||||
.starts_with("https://launcher-files.modrinth.com/detect.txt")
|
||||
{
|
||||
let query = window
|
||||
.url()?
|
||||
.url()
|
||||
.query_pairs()
|
||||
.map(|(key, val)| {
|
||||
(
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::api::Result;
|
||||
use theseus::prelude::*;
|
||||
|
||||
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![
|
||||
profile_create,
|
||||
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
|
||||
// invoke('plugin:profile-create|profile_add',profile)
|
||||
// invoke('plugin:profile_create|profile_add',profile)
|
||||
#[tauri::command]
|
||||
pub async fn profile_create(
|
||||
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
|
||||
// invoke('plugin:profile-create|profile_duplicate',profile)
|
||||
// invoke('plugin:profile_create|profile_duplicate',profile)
|
||||
#[tauri::command]
|
||||
pub async fn profile_duplicate(path: &str) -> Result<String> {
|
||||
let res = profile::create::profile_create_from_duplicate(path).await?;
|
||||
|
||||
@@ -137,6 +137,5 @@ pub async fn get_opening_command() -> Result<Option<CommandPayload>> {
|
||||
// helper function called when redirected by a weblink (ie: modrith://do-something) or when redirected by a .mrpack file (in which case its a filepath)
|
||||
// We hijack the deep link library (which also contains functionality for instance-checking)
|
||||
pub async fn handle_command(command: String) -> Result<()> {
|
||||
tracing::info!("handle command: {command}");
|
||||
Ok(theseus::handler::parse_and_emit_command(&command).await?)
|
||||
}
|
||||
|
||||
@@ -1,28 +1,6 @@
|
||||
use std::sync::Arc;
|
||||
use tauri::{Manager, Runtime};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InitialPayload {
|
||||
pub payload: Arc<Mutex<Option<String>>>,
|
||||
}
|
||||
|
||||
pub fn get_or_init_payload<R: Runtime, M: Manager<R>>(
|
||||
manager: &M,
|
||||
) -> InitialPayload {
|
||||
let initial_payload = manager.try_state::<InitialPayload>();
|
||||
let mtx = if let Some(initial_payload) = initial_payload {
|
||||
initial_payload.inner().clone()
|
||||
} else {
|
||||
tracing::info!("No initial payload found, creating new");
|
||||
let payload = InitialPayload {
|
||||
payload: Arc::new(Mutex::new(None)),
|
||||
};
|
||||
|
||||
manager.manage(payload.clone());
|
||||
|
||||
payload
|
||||
};
|
||||
|
||||
mtx
|
||||
}
|
||||
|
||||
98
apps/app/src/macos/delegate.rs
Normal file
98
apps/app/src/macos/delegate.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use cocoa::{
|
||||
base::{id, nil},
|
||||
foundation::NSAutoreleasePool,
|
||||
};
|
||||
use objc::{
|
||||
class,
|
||||
declare::ClassDecl,
|
||||
msg_send,
|
||||
runtime::{Class, Object, Sel},
|
||||
sel, sel_impl,
|
||||
};
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
use crate::api::TheseusSerializableError;
|
||||
|
||||
type Callback = OnceCell<Box<dyn Fn(String) + Send + Sync + 'static>>;
|
||||
|
||||
static CALLBACK: Callback = OnceCell::new();
|
||||
|
||||
pub struct AppDelegateClass(pub *const Class);
|
||||
unsafe impl Send for AppDelegateClass {}
|
||||
unsafe impl Sync for AppDelegateClass {}
|
||||
|
||||
// Obj C class for the app delegate
|
||||
// This inherits from the TaoAppDelegate (used by tauri) so we do not accidentally override any functionality
|
||||
// The application_open_file method is the only method we override, as it is currently unimplemented in tauri
|
||||
lazy_static::lazy_static! {
|
||||
pub static ref THESEUS_APP_DELEGATE_CLASS: AppDelegateClass = unsafe {
|
||||
let superclass = class!(TaoAppDelegate);
|
||||
let mut decl = ClassDecl::new("TheseusAppDelegate", superclass).unwrap();
|
||||
|
||||
// Add the method to the class
|
||||
decl.add_method(
|
||||
sel!(application:openFile:),
|
||||
application_open_file as extern "C" fn(&Object, Sel, id, id) -> bool,
|
||||
);
|
||||
|
||||
// Other methods are inherited
|
||||
|
||||
AppDelegateClass(decl.register())
|
||||
};
|
||||
}
|
||||
|
||||
extern "C" fn application_open_file(
|
||||
_: &Object,
|
||||
_: Sel,
|
||||
_: id,
|
||||
file: id,
|
||||
) -> bool {
|
||||
let file = nsstring_to_string(file);
|
||||
callback(file)
|
||||
}
|
||||
|
||||
pub fn callback(file: String) -> bool {
|
||||
if let Some(callback) = CALLBACK.get() {
|
||||
callback(file);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_open_file<T>(
|
||||
callback: T,
|
||||
) -> Result<(), TheseusSerializableError>
|
||||
where
|
||||
T: Fn(String) + Send + Sync + 'static,
|
||||
{
|
||||
unsafe {
|
||||
// Modified from tao: https://github.com/tauri-apps/tao
|
||||
// sets the current app delegate to be the inherited app delegate rather than the default tauri/tao one
|
||||
let app: id = msg_send![class!(TaoApp), sharedApplication];
|
||||
|
||||
let delegate: id = msg_send![THESEUS_APP_DELEGATE_CLASS.0, new];
|
||||
let pool = NSAutoreleasePool::new(nil);
|
||||
let _: () = msg_send![app, setDelegate: delegate];
|
||||
let _: () = msg_send![pool, drain];
|
||||
}
|
||||
CALLBACK.set(Box::new(callback)).map_err(|_| {
|
||||
TheseusSerializableError::Callback("Callback already set".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// Convert an NSString to a Rust `String`
|
||||
/// From 'fruitbasket' https://github.com/mrmekon/fruitbasket/
|
||||
#[allow(clippy::cmp_null)]
|
||||
pub fn nsstring_to_string(nsstring: *mut Object) -> String {
|
||||
unsafe {
|
||||
let cstr: *const i8 = msg_send![nsstring, UTF8String];
|
||||
if cstr != std::ptr::null() {
|
||||
std::ffi::CStr::from_ptr(cstr)
|
||||
.to_string_lossy()
|
||||
.into_owned()
|
||||
} else {
|
||||
"".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod deep_link;
|
||||
pub mod delegate;
|
||||
pub mod window_ext;
|
||||
|
||||
@@ -1,412 +1,70 @@
|
||||
// Stolen from https://gist.github.com/charrondev/43150e940bd2771b1ea88256d491c7a9
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use tauri::{
|
||||
plugin::{Builder, TauriPlugin},
|
||||
Emitter, Runtime, Window,
|
||||
}; // 0.8
|
||||
/// from: https://github.com/tauri-apps/tauri/issues/4789, full credit to haasal
|
||||
#[cfg(target_os = "macos")]
|
||||
use tauri::{Runtime, Window};
|
||||
|
||||
const WINDOW_CONTROL_PAD_X: f64 = 9.0;
|
||||
const WINDOW_CONTROL_PAD_Y: f64 = 16.0;
|
||||
|
||||
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")]
|
||||
pub trait WindowExt {
|
||||
fn set_transparent_titlebar(&self, transparent: bool);
|
||||
fn position_traffic_lights(&self, x: f64, y: f64);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn position_traffic_lights(
|
||||
ns_window_handle: UnsafeWindowHandle,
|
||||
x: f64,
|
||||
y: f64,
|
||||
) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
let ns_window = ns_window_handle.0 as cocoa::base::id;
|
||||
unsafe {
|
||||
let close = ns_window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize = ns_window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowMiniaturizeButton);
|
||||
let zoom =
|
||||
ns_window.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
impl<R: Runtime> WindowExt for Window<R> {
|
||||
fn set_transparent_titlebar(&self, transparent: bool) {
|
||||
use cocoa::appkit::{NSWindow, NSWindowTitleVisibility};
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
unsafe {
|
||||
window.setTitleVisibility_(
|
||||
NSWindowTitleVisibility::NSWindowTitleHidden,
|
||||
);
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
if transparent {
|
||||
window.setTitlebarAppearsTransparent_(cocoa::base::YES);
|
||||
} else {
|
||||
window.setTitlebarAppearsTransparent_(cocoa::base::NO);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y =
|
||||
NSView::frame(ns_window).size.height - title_bar_frame_height;
|
||||
let _: () =
|
||||
msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
fn position_traffic_lights(&self, x: f64, y: f64) {
|
||||
use cocoa::appkit::{NSView, NSWindow, NSWindowButton};
|
||||
use cocoa::foundation::NSRect;
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between =
|
||||
NSView::frame(miniaturize).origin.x - NSView::frame(close).origin.x;
|
||||
let window = self.ns_window().unwrap() as cocoa::base::id;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
unsafe {
|
||||
let close = window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowCloseButton);
|
||||
let miniaturize = window.standardWindowButton_(
|
||||
NSWindowButton::NSWindowMiniaturizeButton,
|
||||
);
|
||||
let zoom = window
|
||||
.standardWindowButton_(NSWindowButton::NSWindowZoomButton);
|
||||
|
||||
let title_bar_container_view = close.superview().superview();
|
||||
|
||||
let close_rect: NSRect = msg_send![close, frame];
|
||||
let button_height = close_rect.size.height;
|
||||
|
||||
let title_bar_frame_height = button_height + y;
|
||||
let mut title_bar_rect = NSView::frame(title_bar_container_view);
|
||||
title_bar_rect.size.height = title_bar_frame_height;
|
||||
title_bar_rect.origin.y =
|
||||
NSView::frame(window).size.height - title_bar_frame_height;
|
||||
let _: () =
|
||||
msg_send![title_bar_container_view, setFrame: title_bar_rect];
|
||||
|
||||
let window_buttons = vec![close, miniaturize, zoom];
|
||||
let space_between = NSView::frame(miniaturize).origin.x
|
||||
- NSView::frame(close).origin.x;
|
||||
|
||||
for (i, button) in window_buttons.into_iter().enumerate() {
|
||||
let mut rect: NSRect = NSView::frame(button);
|
||||
rect.origin.x = x + (i as f64 * space_between);
|
||||
button.setFrameOrigin(rect.origin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[derive(Debug)]
|
||||
struct WindowState<R: Runtime> {
|
||||
window: Window<R>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn setup_traffic_light_positioner<R: Runtime>(window: Window<R>) {
|
||||
use cocoa::appkit::NSWindow;
|
||||
use cocoa::base::{id, BOOL};
|
||||
use cocoa::foundation::NSUInteger;
|
||||
use objc::runtime::{Object, Sel};
|
||||
use std::ffi::c_void;
|
||||
|
||||
// Do the initial positioning
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(
|
||||
window.ns_window().expect("Failed to create window handle"),
|
||||
),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
|
||||
// Ensure they stay in place while resizing the window.
|
||||
fn with_window_state<R: Runtime, F: FnOnce(&mut WindowState<R>) -> T, T>(
|
||||
this: &Object,
|
||||
func: F,
|
||||
) {
|
||||
let ptr = unsafe {
|
||||
let x: *mut c_void = *this.get_ivar("app_box");
|
||||
&mut *(x as *mut WindowState<R>)
|
||||
};
|
||||
func(ptr);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
let ns_win = window
|
||||
.ns_window()
|
||||
.expect("NS Window should exist to mount traffic light delegate.")
|
||||
as id;
|
||||
|
||||
let current_delegate: id = ns_win.delegate();
|
||||
|
||||
extern "C" fn on_window_should_close(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
sender: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, windowShouldClose: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_close(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillClose: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resize<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
let id = state.window.ns_window().expect(
|
||||
"NS window should exist on state to handle resize",
|
||||
) as id;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidResize: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_move(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidMove: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_change_backing_properties(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidChangeBackingProperties: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_become_key(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidBecomeKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_resign_key(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidResignKey: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_entered(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, draggingEntered: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_prepare_for_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, prepareForDragOperation: notification]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_perform_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
sender: id,
|
||||
) -> BOOL {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, performDragOperation: sender]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_conclude_drag_operation(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, concludeDragOperation: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_dragging_exited(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, draggingExited: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_use_full_screen_presentation_options(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
proposed_options: NSUInteger,
|
||||
) -> NSUInteger {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
msg_send![super_del, window: window willUseFullScreenPresentationOptions: proposed_options]
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_enter_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-enter-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillEnterFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("did-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
|
||||
let id =
|
||||
state.window.ns_window().expect("Failed to emit event")
|
||||
as id;
|
||||
position_traffic_lights(
|
||||
UnsafeWindowHandle(id as *mut std::ffi::c_void),
|
||||
WINDOW_CONTROL_PAD_X,
|
||||
WINDOW_CONTROL_PAD_Y,
|
||||
);
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () =
|
||||
msg_send![super_del, windowDidExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_will_exit_full_screen<R: Runtime>(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
with_window_state(this, |state: &mut WindowState<R>| {
|
||||
state
|
||||
.window
|
||||
.emit("will-exit-fullscreen", ())
|
||||
.expect("Failed to emit event");
|
||||
});
|
||||
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowWillExitFullScreen: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_window_did_fail_to_enter_full_screen(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
window: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, windowDidFailToEnterFullScreen: window];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_change(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![super_del, effectiveAppearanceDidChange: notification];
|
||||
}
|
||||
}
|
||||
extern "C" fn on_effective_appearance_did_changed_on_main_thread(
|
||||
this: &Object,
|
||||
_cmd: Sel,
|
||||
notification: id,
|
||||
) {
|
||||
unsafe {
|
||||
let super_del: id = *this.get_ivar("super_delegate");
|
||||
let _: () = msg_send![
|
||||
super_del,
|
||||
effectiveAppearanceDidChangedOnMainThread: notification
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Are we deallocing this properly ? (I miss safe Rust :( )
|
||||
let window_label = window.label().to_string();
|
||||
|
||||
let app_state = WindowState { window };
|
||||
let app_box = Box::into_raw(Box::new(app_state)) as *mut c_void;
|
||||
let random_str: String = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(20)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
// We need to ensure we have a unique delegate name, otherwise we will panic while trying to create a duplicate
|
||||
// delegate with the same name.
|
||||
let delegate_name =
|
||||
format!("windowDelegate_{}_{}", window_label, random_str);
|
||||
|
||||
ns_win.setDelegate_(delegate!(&delegate_name, {
|
||||
window: id = ns_win,
|
||||
app_box: *mut c_void = app_box,
|
||||
toolbar: id = cocoa::base::nil,
|
||||
super_delegate: id = current_delegate,
|
||||
(windowShouldClose:) => on_window_should_close as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(windowWillClose:) => on_window_will_close as extern fn(&Object, Sel, id),
|
||||
(windowDidResize:) => on_window_did_resize::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidMove:) => on_window_did_move as extern fn(&Object, Sel, id),
|
||||
(windowDidChangeBackingProperties:) => on_window_did_change_backing_properties as extern fn(&Object, Sel, id),
|
||||
(windowDidBecomeKey:) => on_window_did_become_key as extern fn(&Object, Sel, id),
|
||||
(windowDidResignKey:) => on_window_did_resign_key as extern fn(&Object, Sel, id),
|
||||
(draggingEntered:) => on_dragging_entered as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(prepareForDragOperation:) => on_prepare_for_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(performDragOperation:) => on_perform_drag_operation as extern fn(&Object, Sel, id) -> BOOL,
|
||||
(concludeDragOperation:) => on_conclude_drag_operation as extern fn(&Object, Sel, id),
|
||||
(draggingExited:) => on_dragging_exited as extern fn(&Object, Sel, id),
|
||||
(window:willUseFullScreenPresentationOptions:) => on_window_will_use_full_screen_presentation_options as extern fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||
(windowDidEnterFullScreen:) => on_window_did_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillEnterFullScreen:) => on_window_will_enter_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidExitFullScreen:) => on_window_did_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowWillExitFullScreen:) => on_window_will_exit_full_screen::<R> as extern fn(&Object, Sel, id),
|
||||
(windowDidFailToEnterFullScreen:) => on_window_did_fail_to_enter_full_screen as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChange:) => on_effective_appearance_did_change as extern fn(&Object, Sel, id),
|
||||
(effectiveAppearanceDidChangedOnMainThread:) => on_effective_appearance_did_changed_on_main_thread as extern fn(&Object, Sel, id)
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
)]
|
||||
|
||||
use native_dialog::{MessageDialog, MessageType};
|
||||
use tauri::{Listener, Manager};
|
||||
use tauri::{Manager, PhysicalSize};
|
||||
use tauri_plugin_window_state::{StateFlags, WindowExt};
|
||||
use theseus::prelude::*;
|
||||
|
||||
mod api;
|
||||
@@ -13,14 +14,6 @@ mod error;
|
||||
#[cfg(target_os = "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
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
@@ -39,7 +32,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
fn show_window(app: tauri::AppHandle) {
|
||||
let win = app.get_webview_window("main").unwrap();
|
||||
let win = app.get_window("main").unwrap();
|
||||
if let Err(e) = win.show() {
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
@@ -52,7 +45,16 @@ fn show_window(app: tauri::AppHandle) {
|
||||
.unwrap();
|
||||
panic!("cannot display application window")
|
||||
} else {
|
||||
let _ = win.restore_state(StateFlags::all());
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,9 +75,17 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
|
||||
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
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
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
|
||||
ERROR > WARN > INFO > DEBUG > TRACE
|
||||
@@ -95,61 +105,73 @@ fn main() {
|
||||
tracing::info!("Initialized tracing subscriber. Loading Modrinth App!");
|
||||
|
||||
let mut builder = tauri::Builder::default();
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
{
|
||||
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
||||
}
|
||||
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(
|
||||
tauri_plugin_window_state::Builder::default()
|
||||
.with_filename("app-window-state.json")
|
||||
.build(),
|
||||
)
|
||||
.plugin(tauri_plugin_single_instance::init(|app, argv, cwd| {
|
||||
app.emit_all("single-instance", Payload { args: argv, cwd })
|
||||
.unwrap();
|
||||
}))
|
||||
.plugin(tauri_plugin_window_state::Builder::default().build())
|
||||
.setup(|app| {
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
let payload = macos::deep_link::get_or_init_payload(app);
|
||||
let res = {
|
||||
use macos::deep_link::InitialPayload;
|
||||
let mtx = std::sync::Arc::new(tokio::sync::Mutex::new(None));
|
||||
|
||||
let mtx_copy = payload.payload.clone();
|
||||
app.listen("deep-link://new-url", move |url| {
|
||||
let mtx_copy_copy = mtx_copy.clone();
|
||||
let request = url.payload().to_owned();
|
||||
app.manage(InitialPayload {
|
||||
payload: mtx.clone(),
|
||||
});
|
||||
|
||||
let actual_request =
|
||||
serde_json::from_str::<Vec<String>>(&request)
|
||||
.ok()
|
||||
.map(|mut x| x.remove(0))
|
||||
.unwrap_or(request);
|
||||
let mtx_copy = mtx.clone();
|
||||
macos::delegate::register_open_file(move |filename| {
|
||||
let mtx_copy = mtx_copy.clone();
|
||||
|
||||
tauri::async_runtime::spawn(async move {
|
||||
tracing::info!("Handling deep link {actual_request}");
|
||||
tracing::info!("Handling file open {filename}");
|
||||
|
||||
let mut payload = mtx_copy_copy.lock().await;
|
||||
let mut payload = mtx_copy.lock().await;
|
||||
if payload.is_none() {
|
||||
*payload = Some(actual_request.clone());
|
||||
*payload = Some(filename.clone());
|
||||
}
|
||||
|
||||
let _ =
|
||||
api::utils::handle_command(actual_request).await;
|
||||
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 {
|
||||
tracing::info!("Handling deep link {request}");
|
||||
|
||||
let mut payload = mtx_copy.lock().await;
|
||||
if payload.is_none() {
|
||||
*payload = Some(request.clone());
|
||||
}
|
||||
|
||||
let _ = api::utils::handle_command(request).await;
|
||||
});
|
||||
},
|
||||
)
|
||||
};
|
||||
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
app.listen("deep-link://new-url", |url| {
|
||||
let payload = url.payload().to_owned();
|
||||
tracing::info!("Handling deep link {payload}");
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
payload,
|
||||
));
|
||||
dbg!(url);
|
||||
});
|
||||
let res = tauri_plugin_deep_link::register(
|
||||
"modrinth",
|
||||
|request: String| {
|
||||
tracing::info!("Handling deep link {request}");
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
request,
|
||||
));
|
||||
},
|
||||
);
|
||||
|
||||
if let Err(e) = res {
|
||||
tracing::error!("Error registering deep link handler: {}", e);
|
||||
}
|
||||
|
||||
if let Some(window) = app.get_window("main") {
|
||||
// Hide window to prevent white flash on startup
|
||||
@@ -157,14 +179,34 @@ fn main() {
|
||||
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
{
|
||||
window.set_shadow(true).unwrap();
|
||||
use window_shadows::set_shadow;
|
||||
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(())
|
||||
});
|
||||
|
||||
builder = builder
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
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::mr_auth::init())
|
||||
.plugin(api::import::init())
|
||||
@@ -183,77 +225,39 @@ fn main() {
|
||||
initialize_state,
|
||||
is_dev,
|
||||
toggle_decorations,
|
||||
api::auth::auth_login,
|
||||
api::mr_auth::modrinth_auth_login,
|
||||
show_window,
|
||||
]);
|
||||
|
||||
#[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 {
|
||||
let payload =
|
||||
macos::deep_link::get_or_init_payload(app);
|
||||
|
||||
let mtx_copy = payload.payload.clone();
|
||||
let request = file.to_string_lossy().to_string();
|
||||
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")]
|
||||
if let Err(e) = builder.run(tauri::generate_context!()) {
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
|
||||
if format!("{:?}", e)
|
||||
.contains("Runtime(CreateWebview(WebView2Error(WindowsError")
|
||||
{
|
||||
// tauri doesn't expose runtime errors, so matching a string representation seems like the only solution
|
||||
if format!("{:?}", e).contains(
|
||||
"Runtime(CreateWebview(WebView2Error(WindowsError",
|
||||
) {
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text("Your Microsoft Edge WebView2 installation is corrupt.\n\nMicrosoft Edge WebView2 is required to run Modrinth App.\n\nLearn how to repair it at https://docs.modrinth.com/faq/app/webview2")
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
|
||||
panic!("webview2 initialization failed")
|
||||
}
|
||||
panic!("webview2 initialization failed")
|
||||
}
|
||||
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text(&format!(
|
||||
"Cannot initialize application due to an error:\n{:?}",
|
||||
e
|
||||
))
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
|
||||
panic!("{1}: {:?}", e, "error while running tauri application")
|
||||
}
|
||||
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
.set_title("Initialization error")
|
||||
.set_text(&format!(
|
||||
"Cannot initialize application due to an error:\n{:?}",
|
||||
e
|
||||
))
|
||||
.show_alert()
|
||||
.unwrap();
|
||||
|
||||
panic!("{1}: {:?}", e, "error while running tauri application")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
{
|
||||
"bundle": {
|
||||
"createUpdaterArtifacts": "v1Compatible"
|
||||
},
|
||||
"build": {
|
||||
"features": ["updater"]
|
||||
},
|
||||
"plugins": {
|
||||
"tauri": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
|
||||
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
|
||||
"active": true,
|
||||
"endpoints": ["https://launcher-files.modrinth.com/updates.json"],
|
||||
"dialog": true,
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,65 +2,83 @@
|
||||
"build": {
|
||||
"beforeDevCommand": "pnpm turbo run dev --filter=@modrinth/app-frontend",
|
||||
"beforeBuildCommand": "pnpm turbo run build --filter=@modrinth/app-frontend",
|
||||
"frontendDist": "../app-frontend/dist",
|
||||
"devUrl": "http://localhost:1420"
|
||||
"devPath": "http://localhost:1420",
|
||||
"distDir": "../app-frontend/dist",
|
||||
"withGlobalTauri": false
|
||||
},
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "Game",
|
||||
"copyright": "",
|
||||
"targets": "all",
|
||||
"externalBin": [],
|
||||
"icon": [
|
||||
"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"
|
||||
"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
|
||||
}
|
||||
},
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": "App.entitlements",
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"linux": {
|
||||
"macOSPrivateApi": true,
|
||||
"bundle": {
|
||||
"active": true,
|
||||
"category": "Game",
|
||||
"copyright": "",
|
||||
"deb": {
|
||||
"depends": []
|
||||
},
|
||||
"externalBin": [],
|
||||
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||
"identifier": "ModrinthApp",
|
||||
"longDescription": "",
|
||||
"macOS": {
|
||||
"entitlements": "App.entitlements",
|
||||
"exceptionDomain": "",
|
||||
"frameworks": [],
|
||||
"providerShortName": null,
|
||||
"signingIdentity": null
|
||||
},
|
||||
"resources": [],
|
||||
"shortDescription": "",
|
||||
"targets": "all",
|
||||
"windows": {
|
||||
"certificateThumbprint": null,
|
||||
"digestAlgorithm": "sha256",
|
||||
"timestampUrl": "http://timestamp.digicert.com",
|
||||
"wix": {
|
||||
"template": "./msi/main.wxs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileAssociations": [
|
||||
{
|
||||
"ext": ["mrpack"],
|
||||
"mimeType": "application/zip+mrpack"
|
||||
}
|
||||
]
|
||||
},
|
||||
"productName": "Modrinth App",
|
||||
"version": "0.8.3-1",
|
||||
"identifier": "ModrinthApp",
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
"desktop": {
|
||||
"schemes": ["modrinth"]
|
||||
},
|
||||
"mobile": []
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"withGlobalTauri": false,
|
||||
"macOSPrivateApi": true,
|
||||
"security": {
|
||||
"csp": "default-src 'self'; connect-src https://modrinth.com https://*.modrinth.com https://mixpanel.com https://*.mixpanel.com https://*.cloudflare.com https://api.mclo.gs; font-src https://cdn-raw.modrinth.com/fonts/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost; script-src https://*.cloudflare.com 'self'; frame-src https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'; style-src 'unsafe-inline' 'self'"
|
||||
},
|
||||
"updater": {
|
||||
"active": false
|
||||
},
|
||||
"windows": [
|
||||
{
|
||||
"titleBarStyle": "Overlay",
|
||||
@@ -73,20 +91,8 @@
|
||||
"minHeight": 700,
|
||||
"minWidth": 1100,
|
||||
"visible": false,
|
||||
"zoomHotkeysEnabled": true,
|
||||
"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://*.posthog.com https://*.sentry.io 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'"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": {
|
||||
"tauri": {
|
||||
"windows": [
|
||||
{
|
||||
"titleBarStyle": "Overlay",
|
||||
@@ -12,7 +12,6 @@
|
||||
"minHeight": 700,
|
||||
"minWidth": 1100,
|
||||
"visible": false,
|
||||
"zoomHotkeysEnabled": true,
|
||||
"decorations": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
BASE_URL=https://api.modrinth.com/v2/
|
||||
BROWSER_BASE_URL=https://api.modrinth.com/v2/
|
||||
@@ -625,17 +625,17 @@ tr.button-transparent {
|
||||
|
||||
.danger-button {
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
--text-color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.moderation-button {
|
||||
--background-color: var(--color-orange);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
--text-color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.brand-button {
|
||||
--background-color: var(--color-brand);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
--text-color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.alt-brand-button {
|
||||
@@ -715,7 +715,7 @@ tr.button-transparent {
|
||||
|
||||
&:after {
|
||||
transform: translatex(20px);
|
||||
background: var(--color-brand-inverted);
|
||||
background: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,9 +51,6 @@ html {
|
||||
--color-text-inverted: initial !important;
|
||||
--color-bg-inverted: initial !important;
|
||||
|
||||
--color-brand: var(--color-green) !important;
|
||||
--color-brand-inverted: initial !important;
|
||||
|
||||
--tab-underline-hovered: initial !important;
|
||||
|
||||
--color-button-text: initial !important;
|
||||
@@ -184,6 +181,8 @@ html {
|
||||
--color-green-bg: rgba(0, 175, 92, 0.1);
|
||||
--color-blue-bg: rgba(31, 104, 192, 0.1);
|
||||
--color-purple-bg: rgba(142, 50, 243, 0.1);
|
||||
--color-purple-highlight: rgba(142, 50, 243, 0.25);
|
||||
--color-purple-shadow: rgba(142, 50, 243, 0.7);
|
||||
|
||||
--color-warning-bg: hsl(355, 70%, 88%);
|
||||
--color-warning-text: hsl(342, 70%, 35%);
|
||||
@@ -280,6 +279,8 @@ html {
|
||||
--color-green-bg: rgba(27, 217, 106, 0.2);
|
||||
--color-blue-bg: rgba(79, 156, 255, 0.2);
|
||||
--color-purple-bg: rgba(199, 138, 255, 0.2);
|
||||
--color-purple-highlight: rgba(177, 101, 255, 0.25);
|
||||
--color-purple-shadow: rgba(191, 114, 255, 0.7);
|
||||
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: rgba(27, 217, 106, 0.25);
|
||||
@@ -391,6 +392,11 @@ html {
|
||||
}
|
||||
|
||||
.oled-mode {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
--color-button-bg: #222329;
|
||||
}
|
||||
|
||||
--color-bg: #000000;
|
||||
--color-raised-bg: #101013;
|
||||
|
||||
@@ -405,6 +411,11 @@ html {
|
||||
}
|
||||
|
||||
.retro-mode {
|
||||
.experimental-styles-within,
|
||||
&.experimental-styles-within {
|
||||
--color-button-bg: #3a3b38;
|
||||
}
|
||||
|
||||
--color-bg: #191917;
|
||||
--color-raised-bg: #1d1e1b;
|
||||
--color-button-bg: #3a3b38;
|
||||
@@ -426,12 +437,25 @@ html {
|
||||
--color-purple: rgb(139, 129, 230);
|
||||
--color-gray: #718096;
|
||||
|
||||
--color-red-highlight: rgba(232, 32, 13, 0.25);
|
||||
--color-orange-highlight: rgba(232, 141, 13, 0.25);
|
||||
--color-green-highlight: rgba(60, 219, 54, 0.25);
|
||||
--color-blue-highlight: rgba(9, 159, 239, 0.25);
|
||||
--color-red-bg: rgba(232, 32, 13, 0.1);
|
||||
--color-orange-bg: rgba(232, 141, 13, 0.1);
|
||||
--color-green-bg: rgba(60, 219, 54, 0.1);
|
||||
--color-blue-bg: rgba(9, 159, 239, 0.1);
|
||||
--color-purple-bg: rgba(139, 129, 230, 0.1);
|
||||
--color-purple-highlight: rgba(139, 129, 230, 0.25);
|
||||
--color-gray-highlight: rgba(113, 128, 150, 0.25);
|
||||
--color-purple-shadow: rgba(139, 129, 230, 0.7);
|
||||
}
|
||||
|
||||
.green-mode {
|
||||
--color-brand: var(--color-green);
|
||||
--color-brand-highlight: var(--color-green-highlight);
|
||||
--color-brand-shadow: var(--color-green-shadow);
|
||||
}
|
||||
|
||||
.purple-mode {
|
||||
--color-brand: var(--color-purple);
|
||||
--color-brand-highlight: var(--color-purple-highlight);
|
||||
--color-brand-shadow: var(--color-purple-shadow);
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -74,20 +74,20 @@
|
||||
.normal-page {
|
||||
margin: 0 auto;
|
||||
max-width: 80rem;
|
||||
column-gap: 0.75rem;
|
||||
column-gap: 1rem;
|
||||
|
||||
grid-template:
|
||||
"sidebar content" auto
|
||||
"info content" auto
|
||||
"dummy content" 1fr
|
||||
/ 18.75rem 1fr;
|
||||
/ 300px 1fr;
|
||||
|
||||
&.alt-layout {
|
||||
grid-template:
|
||||
"content sidebar" auto
|
||||
"content info" auto
|
||||
"content dummy" 1fr
|
||||
/ 1fr 18.75rem;
|
||||
/ 1fr 300px;
|
||||
}
|
||||
|
||||
&.no-sidebar {
|
||||
@@ -106,12 +106,12 @@
|
||||
}
|
||||
|
||||
.normal-page__sidebar {
|
||||
min-width: 18.75rem;
|
||||
width: 18.75rem;
|
||||
min-width: 300px;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.normal-page__content {
|
||||
max-width: calc(80rem - 18.75rem - 0.75rem);
|
||||
max-width: calc(80rem - 300px - 1rem);
|
||||
//overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
@@ -120,8 +120,8 @@
|
||||
display: grid;
|
||||
margin: 0 auto;
|
||||
max-width: 80rem;
|
||||
column-gap: 0.75rem;
|
||||
padding: 0 0.75rem;
|
||||
column-gap: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
grid-template:
|
||||
"header"
|
||||
@@ -135,14 +135,14 @@
|
||||
"header header" auto
|
||||
"content sidebar" auto
|
||||
"content dummy" 1fr
|
||||
/ 1fr 18.75rem;
|
||||
/ 1fr 300px;
|
||||
|
||||
&.alt-layout {
|
||||
grid-template:
|
||||
"header header" auto
|
||||
"sidebar content" auto
|
||||
"dummy content" 1fr
|
||||
/ 18.75rem 1fr;
|
||||
/ 300px 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
|
||||
.normal-page__content {
|
||||
grid-area: content;
|
||||
max-width: calc(80rem - 18.75rem - 0.75rem);
|
||||
max-width: calc(80rem - 300px - 1rem);
|
||||
//overflow-x: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,7 +119,7 @@ export default {
|
||||
}
|
||||
|
||||
svg {
|
||||
color: var(--color-brand-inverted);
|
||||
color: var(--color-accent-contrast);
|
||||
stroke-width: 0.2rem;
|
||||
height: 0.8rem;
|
||||
width: 0.8rem;
|
||||
|
||||
@@ -99,8 +99,8 @@ export default {
|
||||
border-radius: 0.25rem;
|
||||
|
||||
.nav-content {
|
||||
color: var(--color-button-text-active);
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-contrast);
|
||||
background-color: var(--color-brand-highlight);
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ function stopTimer(notif) {
|
||||
.vue-notification {
|
||||
background: var(--color-blue) !important;
|
||||
border-left: 5px solid var(--color-blue) !important;
|
||||
color: var(--color-brand-inverted) !important;
|
||||
color: var(--color-accent-contrast) !important;
|
||||
|
||||
box-sizing: border-box;
|
||||
text-align: left;
|
||||
|
||||
@@ -135,7 +135,7 @@ a {
|
||||
|
||||
&.page-number.current {
|
||||
background: var(--color-brand);
|
||||
color: var(--color-brand-inverted);
|
||||
color: var(--color-accent-contrast);
|
||||
cursor: default;
|
||||
outline: 2px solid transparent;
|
||||
}
|
||||
|
||||
@@ -496,7 +496,7 @@ const submitForReview = async () => {
|
||||
|
||||
&.done {
|
||||
--background-color: var(--color-green);
|
||||
--content-color: var(--color-brand-inverted);
|
||||
--content-color: var(--color-accent-contrast);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div ref="main_page" class="layout" :class="{ 'expanded-mobile-nav': isBrowseMenuOpen }">
|
||||
<div
|
||||
ref="main_page"
|
||||
class="layout"
|
||||
:class="{
|
||||
'expanded-mobile-nav': isBrowseMenuOpen,
|
||||
'purple-mode':
|
||||
cosmetics.accentColor === 'purple' && auth.user && isPermission(auth.user.badges, 1 << 0),
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="auth.user && !auth.user.email_verified && route.path !== '/auth/verify-email'"
|
||||
class="email-nag"
|
||||
|
||||
@@ -646,7 +646,7 @@ export default defineNuxtComponent({
|
||||
|
||||
.label-button[data-active="true"] {
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
--text-color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.links-modal {
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
<p>This data is used to deliver statistics.</p>
|
||||
|
||||
<h3>Usage data</h3>
|
||||
<p>When you interact with the Modrinth App or the Website, we collect through PostHog:</p>
|
||||
<p>When you interact with the Modrinth App or the Website, we collect through MixPanel:</p>
|
||||
<ul>
|
||||
<li>Your IP address</li>
|
||||
<li>Your anonymized user ID</li>
|
||||
@@ -150,10 +150,9 @@
|
||||
<a href="https://www.cloudflare.com/en-gb/gdpr/introduction/"> Cloudflare </a>
|
||||
</li>
|
||||
<li><a href="https://sentry.io/trust/privacy/">Sentry</a></li>
|
||||
<li><a href="https://posthog.com/privacy">PostHog</a></li>
|
||||
<li><a href="https://mixpanel.com/legal/privacy-policy">MixPanel</a></li>
|
||||
<li><a href="https://www.beehiiv.com/privacy">BeeHiiv</a></li>
|
||||
<li><a href="https://www.paypal.com/us/legalhub/privacy-full">PayPal</a></li>
|
||||
<li><a href="https://stripe.com/privacy">Stripe</a></li>
|
||||
</ul>
|
||||
<p>
|
||||
Data that we specifically collect isn't shared with any other third party. We do not sell any
|
||||
|
||||
@@ -654,7 +654,7 @@ const onBulkEditLinks = useClientTry(async () => {
|
||||
|
||||
.label-button[data-active="true"] {
|
||||
--background-color: var(--color-red);
|
||||
--text-color: var(--color-brand-inverted);
|
||||
--text-color: var(--color-accent-contrast);
|
||||
}
|
||||
|
||||
.links-modal {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="experimental-styles-within flex flex-col gap-6">
|
||||
<MessageBanner v-if="flags.developerMode" message-type="warning" class="developer-message">
|
||||
<CodeIcon class="inline-flex" />
|
||||
<IntlFormatted :message-id="developerModeBanner.description">
|
||||
@@ -13,14 +13,18 @@
|
||||
{{ formatMessage(developerModeBanner.deactivate) }}
|
||||
</Button>
|
||||
</MessageBanner>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(colorTheme.title) }}</h2>
|
||||
<p>{{ formatMessage(colorTheme.description) }}</p>
|
||||
<div class="theme-options mt-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="m-0 text-2xl font-extrabold text-contrast">
|
||||
{{ formatMessage(colorTheme.title) }}
|
||||
</h2>
|
||||
<p class="m-0">{{ formatMessage(colorTheme.description) }}</p>
|
||||
</div>
|
||||
<section class="theme-options">
|
||||
<button
|
||||
v-for="option in themeOptions"
|
||||
:key="option"
|
||||
class="preview-radio button-base"
|
||||
class="preview-radio transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ selected: theme.preferred === option }"
|
||||
@click="() => updateColorTheme(option)"
|
||||
>
|
||||
@@ -47,187 +51,153 @@
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
<div v-if="auth.user && isPermission(auth.user.badges, 1 << 0)" class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="m-0 flex items-center gap-2 text-2xl font-extrabold text-contrast">
|
||||
{{ formatMessage(accentColor.title) }}
|
||||
<span
|
||||
class="flex items-center justify-center gap-1 rounded-full bg-bg-purple px-2 py-0.5 text-sm font-bold text-purple"
|
||||
>
|
||||
<ModrinthIcon class="h-4 w-auto" />
|
||||
Modrinth+
|
||||
</span>
|
||||
</h2>
|
||||
<p class="m-0">{{ formatMessage(accentColor.description) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(projectListLayouts.title) }}</h2>
|
||||
<p class="mb-4">{{ formatMessage(projectListLayouts.description) }}</p>
|
||||
<div class="project-lists">
|
||||
<div v-for="projectType in listTypes" :key="projectType.id + '-project-list-layouts'">
|
||||
<section class="theme-options">
|
||||
<button
|
||||
v-for="option in accentOptions"
|
||||
:key="option"
|
||||
class="preview-radio transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{
|
||||
selected:
|
||||
cosmetics.accentColor === option || (!cosmetics.accentColor && option === 'green'),
|
||||
}"
|
||||
@click="() => (cosmetics.accentColor = option)"
|
||||
>
|
||||
<div class="preview" :class="`${option}-mode`">
|
||||
<TextLogo class="h-8 w-auto text-contrast" />
|
||||
</div>
|
||||
<div class="label">
|
||||
<div class="label__title">
|
||||
{{
|
||||
projectListLayouts[projectType.id]
|
||||
? formatMessage(projectListLayouts[projectType.id])
|
||||
: projectType.id
|
||||
}}
|
||||
</div>
|
||||
<RadioButtonChecked
|
||||
v-if="
|
||||
cosmetics.accentColor === option || (!cosmetics.accentColor && option === 'green')
|
||||
"
|
||||
class="radio"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio" />
|
||||
{{ accentColor[option] ? formatMessage(accentColor[option]) : option }}
|
||||
</div>
|
||||
<div class="project-list-layouts">
|
||||
<button
|
||||
class="preview-radio button-base"
|
||||
:class="{ selected: cosmetics.searchDisplayMode[projectType.id] === 'list' }"
|
||||
@click="() => (cosmetics.searchDisplayMode[projectType.id] = 'list')"
|
||||
>
|
||||
<div class="preview">
|
||||
<div class="layout-list-mode">
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<RadioButtonChecked
|
||||
v-if="cosmetics.searchDisplayMode[projectType.id] === 'list'"
|
||||
class="radio"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio" />
|
||||
Rows
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="preview-radio button-base"
|
||||
:class="{ selected: cosmetics.searchDisplayMode[projectType.id] === 'grid' }"
|
||||
@click="() => (cosmetics.searchDisplayMode[projectType.id] = 'grid')"
|
||||
>
|
||||
<div class="preview">
|
||||
<div class="layout-grid-mode">
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<RadioButtonChecked
|
||||
v-if="cosmetics.searchDisplayMode[projectType.id] === 'grid'"
|
||||
class="radio"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio" />
|
||||
Grid
|
||||
</div>
|
||||
</button>
|
||||
<button
|
||||
class="preview-radio button-base"
|
||||
:class="{ selected: cosmetics.searchDisplayMode[projectType.id] === 'gallery' }"
|
||||
@click="() => (cosmetics.searchDisplayMode[projectType.id] = 'gallery')"
|
||||
>
|
||||
<div class="preview">
|
||||
<div class="layout-gallery-mode">
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
<div class="example-card card"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="label">
|
||||
<RadioButtonChecked
|
||||
v-if="cosmetics.searchDisplayMode[projectType.id] === 'gallery'"
|
||||
class="radio"
|
||||
/>
|
||||
<RadioButtonIcon v-else class="radio" />
|
||||
Gallery
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
</section>
|
||||
</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex flex-col gap-1">
|
||||
<h2 class="m-0 text-2xl font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.title) }}
|
||||
</h2>
|
||||
<p class="m-0">{{ formatMessage(toggleFeatures.description) }}</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="universal-card">
|
||||
<h2 class="text-2xl">{{ formatMessage(toggleFeatures.title) }}</h2>
|
||||
<p class="mb-4">{{ formatMessage(toggleFeatures.description) }}</p>
|
||||
<div class="adjacent-input small">
|
||||
<label for="advanced-rendering">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
<label
|
||||
class="!m-0 grid cursor-pointer grid-cols-[1fr_auto] items-center gap-1 rounded-2xl border-2 border-solid border-bg-raised px-6 py-4 transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ 'bg-bg-raised': cosmetics.advancedRendering }"
|
||||
>
|
||||
<span class="col-start-1 font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.advancedRenderingTitle) }}
|
||||
</span>
|
||||
<span class="col-start-1">{{
|
||||
formatMessage(toggleFeatures.advancedRenderingDescription)
|
||||
}}</span>
|
||||
<input
|
||||
id="advanced-rendering"
|
||||
v-model="cosmetics.advancedRendering"
|
||||
class="switch stylized-toggle"
|
||||
class="switch stylized-toggle col-start-2 row-span-2 row-start-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input small">
|
||||
<label for="external-links-new-tab">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
</label>
|
||||
<label
|
||||
class="!m-0 grid cursor-pointer grid-cols-[1fr_auto] items-center gap-1 rounded-2xl border-2 border-solid border-bg-raised px-6 py-4 transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ 'bg-bg-raised': cosmetics.externalLinksNewTab }"
|
||||
>
|
||||
<span class="col-start-1 font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.externalLinksNewTabTitle) }}
|
||||
</span>
|
||||
<span class="col-start-1">{{
|
||||
formatMessage(toggleFeatures.externalLinksNewTabDescription)
|
||||
}}</span>
|
||||
<input
|
||||
id="external-links-new-tab"
|
||||
v-model="cosmetics.externalLinksNewTab"
|
||||
class="switch stylized-toggle"
|
||||
class="switch stylized-toggle col-start-2 row-span-2 row-start-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="false" class="adjacent-input small">
|
||||
<label for="modrinth-app-promos">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
</label>
|
||||
<label
|
||||
v-if="false"
|
||||
class="!m-0 grid cursor-pointer grid-cols-[1fr_auto] items-center gap-1 rounded-2xl border-2 border-solid border-bg-raised px-6 py-4 transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ 'bg-bg-raised': cosmetics.hideModrinthAppPromos }"
|
||||
>
|
||||
<span class="col-start-1 font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.hideModrinthAppPromosTitle) }}
|
||||
</span>
|
||||
<span class="col-start-1">{{
|
||||
formatMessage(toggleFeatures.hideModrinthAppPromosDescription)
|
||||
}}</span>
|
||||
<input
|
||||
id="modrinth-app-promos"
|
||||
v-model="cosmetics.hideModrinthAppPromos"
|
||||
class="switch stylized-toggle"
|
||||
class="switch stylized-toggle col-start-2 row-span-2 row-start-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input small">
|
||||
<label for="search-layout-toggle">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(toggleFeatures.rightAlignedFiltersSidebarTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(toggleFeatures.rightAlignedFiltersSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
</label>
|
||||
<label
|
||||
class="!m-0 grid cursor-pointer grid-cols-[1fr_auto] items-center gap-1 rounded-2xl border-2 border-solid border-bg-raised px-6 py-4 transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ 'bg-bg-raised': cosmetics.rightSearchLayout }"
|
||||
>
|
||||
<span class="col-start-1 font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.rightAlignedFiltersSidebarTitle) }}
|
||||
</span>
|
||||
<span class="col-start-1">{{
|
||||
formatMessage(toggleFeatures.rightAlignedFiltersSidebarDescription)
|
||||
}}</span>
|
||||
<input
|
||||
id="search-layout-toggle"
|
||||
v-model="cosmetics.rightSearchLayout"
|
||||
class="switch stylized-toggle"
|
||||
class="switch stylized-toggle col-start-2 row-span-2 row-start-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
<div class="adjacent-input small">
|
||||
<label for="project-layout-toggle">
|
||||
<span class="label__title">
|
||||
{{ formatMessage(toggleFeatures.leftAlignedContentSidebarTitle) }}
|
||||
</span>
|
||||
<span class="label__description">
|
||||
{{ formatMessage(toggleFeatures.leftAlignedContentSidebarDescription) }}
|
||||
</span>
|
||||
</label>
|
||||
</label>
|
||||
<label
|
||||
class="!m-0 grid cursor-pointer grid-cols-[1fr_auto] items-center gap-1 rounded-2xl border-2 border-solid border-bg-raised px-6 py-4 transition-transform hover:brightness-110 active:scale-[0.99] active:brightness-125"
|
||||
:class="{ 'bg-bg-raised': cosmetics.leftContentLayout }"
|
||||
>
|
||||
<span class="col-start-1 font-extrabold text-contrast">
|
||||
{{ formatMessage(toggleFeatures.leftAlignedContentSidebarTitle) }}
|
||||
</span>
|
||||
<span class="col-start-1">{{
|
||||
formatMessage(toggleFeatures.leftAlignedContentSidebarDescription)
|
||||
}}</span>
|
||||
<input
|
||||
id="project-layout-toggle"
|
||||
v-model="cosmetics.leftContentLayout"
|
||||
class="switch stylized-toggle"
|
||||
class="switch stylized-toggle col-start-2 row-span-2 row-start-1"
|
||||
type="checkbox"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CodeIcon, MoonIcon, RadioButtonChecked, RadioButtonIcon, SunIcon } from "@modrinth/assets";
|
||||
import { Button } from "@modrinth/ui";
|
||||
import {
|
||||
ModrinthIcon,
|
||||
CodeIcon,
|
||||
MoonIcon,
|
||||
RadioButtonChecked,
|
||||
RadioButtonIcon,
|
||||
SunIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { TextLogo, Button } from "@modrinth/ui";
|
||||
import MessageBanner from "~/components/ui/MessageBanner.vue";
|
||||
import type { DisplayLocation } from "~/plugins/cosmetics";
|
||||
import type { AccentColor, DisplayLocation } from "~/plugins/cosmetics";
|
||||
import { formatProjectType } from "~/plugins/shorthands.js";
|
||||
import { isDarkTheme, type Theme } from "~/plugins/theme/index.ts";
|
||||
|
||||
@@ -236,6 +206,7 @@ useHead({
|
||||
});
|
||||
|
||||
const { formatMessage } = useVIntl();
|
||||
const auth = await useAuth();
|
||||
|
||||
const developerModeBanner = defineMessages({
|
||||
description: {
|
||||
@@ -288,6 +259,26 @@ const colorTheme = defineMessages({
|
||||
},
|
||||
});
|
||||
|
||||
const accentColor = defineMessages({
|
||||
title: {
|
||||
id: "settings.display.accent.title",
|
||||
defaultMessage: "Accent color",
|
||||
},
|
||||
description: {
|
||||
id: "settings.display.accent.description",
|
||||
defaultMessage:
|
||||
"Thank you for supporting Modrinth! You can choose to make the Modrinth accent color purple if you'd like.",
|
||||
},
|
||||
green: {
|
||||
id: "settings.display.accent.green",
|
||||
defaultMessage: "Modrinth Green",
|
||||
},
|
||||
purple: {
|
||||
id: "settings.display.accent.purple",
|
||||
defaultMessage: "Plus Purple",
|
||||
},
|
||||
});
|
||||
|
||||
const projectListLayouts = defineMessages({
|
||||
title: {
|
||||
id: "settings.display.project-list-layouts.title",
|
||||
@@ -415,6 +406,10 @@ const themeOptions = computed(() => {
|
||||
return options;
|
||||
});
|
||||
|
||||
const accentOptions = computed(() => {
|
||||
return ["green", "purple"] as AccentColor[];
|
||||
});
|
||||
|
||||
function updateColorTheme(value: Theme | "system") {
|
||||
if (value !== "system") {
|
||||
if (isDarkTheme(value)) {
|
||||
@@ -462,7 +457,7 @@ const listTypes = computed(() => {
|
||||
border-radius: var(--radius-md);
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--color-divider);
|
||||
border: 1px solid var(--color-button-bg);
|
||||
background-color: var(--color-button-bg);
|
||||
color: var(--color-base);
|
||||
display: flex;
|
||||
@@ -471,6 +466,8 @@ const listTypes = computed(() => {
|
||||
|
||||
&.selected {
|
||||
color: var(--color-contrast);
|
||||
background-color: var(--color-brand-highlight);
|
||||
border-color: var(--color-brand-highlight);
|
||||
|
||||
.label {
|
||||
.radio {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { DarkTheme } from "./theme/index.ts";
|
||||
|
||||
export type DisplayMode = "list" | "gallery" | "grid";
|
||||
export type AccentColor = "green" | "purple";
|
||||
|
||||
export type DisplayLocation =
|
||||
| "mod"
|
||||
@@ -22,6 +23,7 @@ export interface Cosmetics {
|
||||
preferredDarkTheme: DarkTheme;
|
||||
searchDisplayMode: Record<DisplayLocation, DisplayMode>;
|
||||
hideStagingBanner: boolean;
|
||||
accentColor: AccentColor;
|
||||
}
|
||||
|
||||
export default defineNuxtPlugin({
|
||||
@@ -40,6 +42,7 @@ export default defineNuxtPlugin({
|
||||
externalLinksNewTab: true,
|
||||
notUsingBlockers: false,
|
||||
hideModrinthAppPromos: false,
|
||||
accentColor: "green",
|
||||
preferredDarkTheme: "dark",
|
||||
searchDisplayMode: {
|
||||
mod: "list",
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
{
|
||||
"name": "max_concurrent_writes",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "max_concurrent_downloads",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "theme",
|
||||
@@ -26,37 +26,37 @@
|
||||
{
|
||||
"name": "collapsed_navigation",
|
||||
"ordinal": 4,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "advanced_rendering",
|
||||
"ordinal": 5,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "native_decorations",
|
||||
"ordinal": 6,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "discord_rpc",
|
||||
"ordinal": 7,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "developer_mode",
|
||||
"ordinal": 8,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "telemetry",
|
||||
"ordinal": 9,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "onboarded",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "extra_launch_args",
|
||||
@@ -71,27 +71,27 @@
|
||||
{
|
||||
"name": "mc_memory_max",
|
||||
"ordinal": 13,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "mc_force_fullscreen",
|
||||
"ordinal": 14,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "mc_game_resolution_x",
|
||||
"ordinal": 15,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "mc_game_resolution_y",
|
||||
"ordinal": 16,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "hide_on_process_start",
|
||||
"ordinal": 17,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "hook_pre_launch",
|
||||
@@ -121,7 +121,7 @@
|
||||
{
|
||||
"name": "migrated",
|
||||
"ordinal": 23,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
12
packages/app-lib/.sqlx/query-1769b7033985bfdd04ee8912d9f28e0d15a8b893db47aca3aec054c7134f1f3f.json
generated
Normal file
12
packages/app-lib/.sqlx/query-1769b7033985bfdd04ee8912d9f28e0d15a8b893db47aca3aec054c7134f1f3f.json
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n DELETE FROM processes WHERE pid = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "1769b7033985bfdd04ee8912d9f28e0d15a8b893db47aca3aec054c7134f1f3f"
|
||||
}
|
||||
@@ -11,7 +11,7 @@
|
||||
{
|
||||
"name": "active",
|
||||
"ordinal": 1,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "session_id",
|
||||
@@ -21,7 +21,7 @@
|
||||
{
|
||||
"name": "expires",
|
||||
"ordinal": 3,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
{
|
||||
"name": "major_version",
|
||||
"ordinal": 0,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "full_version",
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
{
|
||||
"name": "expires",
|
||||
"ordinal": 4,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
|
||||
50
packages/app-lib/.sqlx/query-3cac786ad15ef1167bc50ca846d98facb3dee35c9e421209c1161ee7380b7a74.json
generated
Normal file
50
packages/app-lib/.sqlx/query-3cac786ad15ef1167bc50ca846d98facb3dee35c9e421209c1161ee7380b7a74.json
generated
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"db_name": "SQLite",
|
||||
"query": "\n SELECT\n pid, start_time, name, executable, profile_path, post_exit_command\n FROM processes\n WHERE 1=$1",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "pid",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "start_time",
|
||||
"ordinal": 1,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "executable",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "profile_path",
|
||||
"ordinal": 4,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "post_exit_command",
|
||||
"ordinal": 5,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "3cac786ad15ef1167bc50ca846d98facb3dee35c9e421209c1161ee7380b7a74"
|
||||
}
|
||||
@@ -56,32 +56,32 @@
|
||||
{
|
||||
"name": "locked",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 11,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "modified",
|
||||
"ordinal": 12,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "last_played",
|
||||
"ordinal": 13,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "submitted_time_played",
|
||||
"ordinal": 14,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "recent_time_played",
|
||||
"ordinal": 15,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_java_path",
|
||||
@@ -101,22 +101,22 @@
|
||||
{
|
||||
"name": "override_mc_memory_max",
|
||||
"ordinal": 19,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_force_fullscreen",
|
||||
"ordinal": 20,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_game_resolution_x",
|
||||
"ordinal": 21,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_game_resolution_y",
|
||||
"ordinal": 22,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_hook_pre_launch",
|
||||
|
||||
@@ -56,32 +56,32 @@
|
||||
{
|
||||
"name": "locked",
|
||||
"ordinal": 10,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "created",
|
||||
"ordinal": 11,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "modified",
|
||||
"ordinal": 12,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "last_played",
|
||||
"ordinal": 13,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "submitted_time_played",
|
||||
"ordinal": 14,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "recent_time_played",
|
||||
"ordinal": 15,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_java_path",
|
||||
@@ -101,22 +101,22 @@
|
||||
{
|
||||
"name": "override_mc_memory_max",
|
||||
"ordinal": 19,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_force_fullscreen",
|
||||
"ordinal": 20,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_game_resolution_x",
|
||||
"ordinal": 21,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_mc_game_resolution_y",
|
||||
"ordinal": 22,
|
||||
"type_info": "Integer"
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "override_hook_pre_launch",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user