Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75f9fa5daf | ||
|
|
c9d34feab7 |
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
name: 🌐 Website bug (modrinth.com)
|
name: 🌐 Website bug (modrinth.com)
|
||||||
description: Report an issue on the Modrinth website.
|
description: Report an issue on the Modrinth website.
|
||||||
labels: [bug, frontend]
|
labels: [bug, web]
|
||||||
body:
|
body:
|
||||||
- type: checkboxes
|
- type: checkboxes
|
||||||
attributes:
|
attributes:
|
||||||
@@ -49,4 +49,4 @@ body:
|
|||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add any other context about the problem here.
|
description: Add any other context about the problem here.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
@@ -9,6 +9,8 @@ body:
|
|||||||
options:
|
options:
|
||||||
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
|
- label: I checked the [existing issues](https://github.com/modrinth/code/issues) for duplicate feature requests
|
||||||
required: true
|
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)
|
- label: I have checked that this feature request is not on our [roadmap](https://roadmap.modrinth.com)
|
||||||
required: true
|
required: true
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
@@ -43,4 +45,4 @@ body:
|
|||||||
label: Additional context
|
label: Additional context
|
||||||
description: Add any other context or screenshots about the suggested enhancement here.
|
description: Add any other context or screenshots about the suggested enhancement here.
|
||||||
validations:
|
validations:
|
||||||
required: false
|
required: false
|
||||||
@@ -44,28 +44,7 @@ jobs:
|
|||||||
- name: Setup rust cache
|
- name: Setup rust cache
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: |
|
path: target/**
|
||||||
target/**
|
|
||||||
!target/*/release/bundle/*/*.dmg
|
|
||||||
!target/*/release/bundle/*/*.app.tar.gz
|
|
||||||
!target/*/release/bundle/*/*.app.tar.gz.sig
|
|
||||||
!target/release/bundle/*/*.dmg
|
|
||||||
!target/release/bundle/*/*.app.tar.gz
|
|
||||||
!target/release/bundle/*/*.app.tar.gz.sig
|
|
||||||
|
|
||||||
!target/release/bundle/*/*.AppImage
|
|
||||||
!target/release/bundle/*/*.AppImage.tar.gz
|
|
||||||
!target/release/bundle/*/*.AppImage.tar.gz.sig
|
|
||||||
!target/release/bundle/*/*.deb
|
|
||||||
!target/release/bundle/*/*.rpm
|
|
||||||
|
|
||||||
!target/release/bundle/msi/*.msi
|
|
||||||
!target/release/bundle/msi/*.msi.zip
|
|
||||||
!target/release/bundle/msi/*.msi.zip.sig
|
|
||||||
|
|
||||||
!target/release/bundle/nsis/*.exe
|
|
||||||
!target/release/bundle/nsis/*.nsis.zip
|
|
||||||
!target/release/bundle/nsis/*.nsis.zip.sig
|
|
||||||
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-rust-target-${{ hashFiles('**/Cargo.lock') }}
|
||||||
restore-keys: |
|
restore-keys: |
|
||||||
${{ runner.os }}-rust-target-
|
${{ runner.os }}-rust-target-
|
||||||
@@ -99,7 +78,7 @@ jobs:
|
|||||||
if: startsWith(matrix.platform, 'ubuntu')
|
if: startsWith(matrix.platform, 'ubuntu')
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
|
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev sqlite3
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
@@ -145,11 +124,8 @@ jobs:
|
|||||||
target/release/bundle/*/*.AppImage.tar.gz.sig
|
target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||||
target/release/bundle/*/*.deb
|
target/release/bundle/*/*.deb
|
||||||
target/release/bundle/*/*.rpm
|
target/release/bundle/*/*.rpm
|
||||||
|
|
||||||
|
target/release/bundle/*/*.msi
|
||||||
|
target/release/bundle/*/*.msi.zip
|
||||||
|
target/release/bundle/*/*.msi.zip.sig
|
||||||
|
|
||||||
target/release/bundle/msi/*.msi
|
|
||||||
target/release/bundle/msi/*.msi.zip
|
|
||||||
target/release/bundle/msi/*.msi.zip.sig
|
|
||||||
|
|
||||||
target/release/bundle/nsis/*.exe
|
|
||||||
target/release/bundle/nsis/*.nsis.zip
|
|
||||||
target/release/bundle/nsis/*.nsis.zip.sig
|
|
||||||
Generated
+409
-512
File diff suppressed because it is too large.
Load diff
@@ -16,6 +16,3 @@ strip = true # Remove debug symbols
|
|||||||
|
|
||||||
[profile.dev.package.sqlx-macros]
|
[profile.dev.package.sqlx-macros]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
|
|
||||||
[patch.crates-io]
|
|
||||||
wry = { git = "https://github.com/modrinth/wry", rev = "27fb16b" }
|
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['custom/vue'],
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { createConfigForNuxt } from '@nuxt/eslint-config/flat'
|
|
||||||
import { fixupPluginRules } from '@eslint/compat'
|
|
||||||
import turboPlugin from 'eslint-plugin-turbo'
|
|
||||||
|
|
||||||
export default createConfigForNuxt().append([
|
|
||||||
{
|
|
||||||
name: 'turbo',
|
|
||||||
plugins: {
|
|
||||||
turbo: fixupPluginRules(turboPlugin),
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'turbo/no-undeclared-env-vars': 'error',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'modrinth',
|
|
||||||
rules: {
|
|
||||||
'vue/html-self-closing': 'off',
|
|
||||||
'vue/multi-word-component-names': 'off',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
])
|
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<title>Modrinth App</title>
|
<title>Modrinth App</title>
|
||||||
|
|
||||||
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />
|
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "@modrinth/app-frontend",
|
"name": "@modrinth/app-frontend",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.8.9",
|
"version": "0.8.3-1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "vue-tsc --noEmit && vite build",
|
"build": "vite build",
|
||||||
"tsc:check": "vue-tsc --noEmit",
|
|
||||||
"lint": "eslint . && prettier --check .",
|
"lint": "eslint . && prettier --check .",
|
||||||
"fix": "eslint . --fix && prettier --write ."
|
"fix": "eslint . --fix && prettier --write ."
|
||||||
},
|
},
|
||||||
@@ -14,41 +13,36 @@
|
|||||||
"@modrinth/assets": "workspace:*",
|
"@modrinth/assets": "workspace:*",
|
||||||
"@modrinth/ui": "workspace:*",
|
"@modrinth/ui": "workspace:*",
|
||||||
"@modrinth/utils": "workspace:*",
|
"@modrinth/utils": "workspace:*",
|
||||||
"@sentry/vue": "^8.27.0",
|
|
||||||
"@tauri-apps/api": "^2.0.0-rc.3",
|
"@tauri-apps/api": "^2.0.0-rc.3",
|
||||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
|
||||||
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
|
|
||||||
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
||||||
|
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||||
"@vintl/vintl": "^4.4.1",
|
"@vintl/vintl": "^4.4.1",
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"floating-vue": "^5.2.2",
|
"floating-vue": "^5.2.2",
|
||||||
"ofetch": "^1.3.4",
|
"ofetch": "^1.3.4",
|
||||||
"pinia": "^2.1.7",
|
"pinia": "^2.1.7",
|
||||||
"posthog-js": "^1.158.2",
|
|
||||||
"vite-svg-loader": "^5.1.0",
|
"vite-svg-loader": "^5.1.0",
|
||||||
"vue": "^3.4.21",
|
"vue": "^3.4.21",
|
||||||
"vue-multiselect": "3.0.0",
|
"vue-multiselect": "3.0.0",
|
||||||
"vue-router": "4.3.0",
|
"vue-router": "4.3.0",
|
||||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
"vue-virtual-scroller": "v2.0.0-beta.8",
|
||||||
|
"posthog-js": "^1.158.2",
|
||||||
|
"@sentry/vue": "^8.27.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/compat": "^1.1.1",
|
"@tauri-apps/cli": "^2.0.0-rc",
|
||||||
"@nuxt/eslint-config": "^0.5.6",
|
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"eslint": "^9.9.1",
|
"eslint": "^8.57.0",
|
||||||
"eslint-config-custom": "workspace:*",
|
"eslint-config-custom": "workspace:*",
|
||||||
"eslint-plugin-turbo": "^2.1.1",
|
|
||||||
"postcss": "^8.4.39",
|
"postcss": "^8.4.39",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"sass": "^1.74.1",
|
"sass": "^1.74.1",
|
||||||
"tailwindcss": "^3.4.4",
|
"tailwindcss": "^3.4.4",
|
||||||
"tsconfig": "workspace:*",
|
"tsconfig": "workspace:*",
|
||||||
"typescript": "^5.5.4",
|
"vite": "^5.2.8"
|
||||||
"vite": "^5.2.8",
|
|
||||||
"vue-tsc": "^2.1.6"
|
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@9.4.0"
|
"packageManager": "pnpm@9.4.0"
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { computed, ref, onMounted } from 'vue'
|
import { computed, ref, onMounted } from 'vue'
|
||||||
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
|
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
|
||||||
import {
|
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, XIcon } from '@modrinth/assets'
|
||||||
HomeIcon,
|
|
||||||
SearchIcon,
|
|
||||||
LibraryIcon,
|
|
||||||
PlusIcon,
|
|
||||||
SettingsIcon,
|
|
||||||
XIcon,
|
|
||||||
DownloadIcon,
|
|
||||||
} from '@modrinth/assets'
|
|
||||||
import { Button, Notifications } from '@modrinth/ui'
|
import { Button, Notifications } from '@modrinth/ui'
|
||||||
import { useLoading, useTheming } from '@/store/state'
|
import { useLoading, useTheming } from '@/store/state'
|
||||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||||
@@ -24,7 +16,7 @@ import { handleError, useNotifications } from '@/store/notifications.js'
|
|||||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||||
import { type } from '@tauri-apps/plugin-os'
|
import { type } from '@tauri-apps/plugin-os'
|
||||||
import { isDev, getOS, restartApp } from '@/helpers/utils.js'
|
import { isDev, getOS } from '@/helpers/utils.js'
|
||||||
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
@@ -39,10 +31,6 @@ import { useInstall } from '@/store/install.js'
|
|||||||
import { invoke } from '@tauri-apps/api/core'
|
import { invoke } from '@tauri-apps/api/core'
|
||||||
import { open } from '@tauri-apps/plugin-shell'
|
import { open } from '@tauri-apps/plugin-shell'
|
||||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
|
||||||
import { renderString } from '@modrinth/utils'
|
|
||||||
import { useFetch } from '@/helpers/fetch.js'
|
|
||||||
import { check } from '@tauri-apps/plugin-updater'
|
|
||||||
|
|
||||||
const themeStore = useTheming()
|
const themeStore = useTheming()
|
||||||
|
|
||||||
@@ -63,8 +51,6 @@ const os = ref('')
|
|||||||
|
|
||||||
const stateInitialized = ref(false)
|
const stateInitialized = ref(false)
|
||||||
|
|
||||||
const criticalErrorMessage = ref()
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await useCheckDisableMouseover()
|
await useCheckDisableMouseover()
|
||||||
})
|
})
|
||||||
@@ -121,18 +107,7 @@ async function setupApp() {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
useFetch(
|
|
||||||
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
|
||||||
'criticalAnnouncements',
|
|
||||||
true,
|
|
||||||
).then((res) => {
|
|
||||||
if (res && res.header && res.body) {
|
|
||||||
criticalErrorMessage.value = res
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
get_opening_command().then(handleCommand)
|
get_opening_command().then(handleCommand)
|
||||||
checkUpdates()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stateFailed = ref(false)
|
const stateFailed = ref(false)
|
||||||
@@ -151,7 +126,6 @@ initialize_state()
|
|||||||
})
|
})
|
||||||
|
|
||||||
const handleClose = async () => {
|
const handleClose = async () => {
|
||||||
await saveWindowState(StateFlags.ALL)
|
|
||||||
await getCurrentWindow().close()
|
await getCurrentWindow().close()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,8 +171,7 @@ document.querySelector('body').addEventListener('click', function (e) {
|
|||||||
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
||||||
!target.classList.contains('router-link-active') &&
|
!target.classList.contains('router-link-active') &&
|
||||||
!target.href.startsWith('http://localhost') &&
|
!target.href.startsWith('http://localhost') &&
|
||||||
!target.href.startsWith('https://tauri.localhost') &&
|
!target.href.startsWith('https://tauri.localhost')
|
||||||
!target.href.startsWith('http://tauri.localhost')
|
|
||||||
) {
|
) {
|
||||||
open(target.href)
|
open(target.href)
|
||||||
}
|
}
|
||||||
@@ -242,20 +215,6 @@ async function handleCommand(e) {
|
|||||||
urlModal.value.show(e)
|
urlModal.value.show(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateAvailable = ref(false)
|
|
||||||
async function checkUpdates() {
|
|
||||||
const update = await check()
|
|
||||||
console.log(update)
|
|
||||||
updateAvailable.value = !!update
|
|
||||||
|
|
||||||
setTimeout(
|
|
||||||
() => {
|
|
||||||
checkUpdates()
|
|
||||||
},
|
|
||||||
5 * 1000 * 60,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -289,14 +248,6 @@ async function checkUpdates() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="settings pages-list">
|
<div class="settings pages-list">
|
||||||
<button
|
|
||||||
v-if="updateAvailable"
|
|
||||||
v-tooltip="'Install update'"
|
|
||||||
class="btn btn-outline btn-primary icon-only collapsed-button"
|
|
||||||
@click="restartApp()"
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</button>
|
|
||||||
<Button
|
<Button
|
||||||
v-tooltip="'Create profile'"
|
v-tooltip="'Create profile'"
|
||||||
class="sleek-primary collapsed-button"
|
class="sleek-primary collapsed-button"
|
||||||
@@ -312,10 +263,6 @@ async function checkUpdates() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="view">
|
<div class="view">
|
||||||
<div v-if="criticalErrorMessage" class="critical-error-banner" data-tauri-drag-region>
|
|
||||||
<h1>{{ criticalErrorMessage.header }}</h1>
|
|
||||||
<div class="markdown-body" v-html="renderString(criticalErrorMessage.body ?? '')"></div>
|
|
||||||
</div>
|
|
||||||
<div class="appbar-row">
|
<div class="appbar-row">
|
||||||
<div data-tauri-drag-region class="appbar">
|
<div data-tauri-drag-region class="appbar">
|
||||||
<section class="navigation-controls">
|
<section class="navigation-controls">
|
||||||
@@ -428,16 +375,6 @@ async function checkUpdates() {
|
|||||||
width: calc(100% - var(--sidebar-width));
|
width: calc(100% - var(--sidebar-width));
|
||||||
background-color: var(--color-raised-bg);
|
background-color: var(--color-raised-bg);
|
||||||
|
|
||||||
.critical-error-banner {
|
|
||||||
margin-top: -1.25rem;
|
|
||||||
padding: 1rem;
|
|
||||||
background-color: rgba(203, 34, 69, 0.1);
|
|
||||||
border-left: 2px solid var(--color-red);
|
|
||||||
border-bottom: 2px solid var(--color-red);
|
|
||||||
border-right: 2px solid var(--color-red);
|
|
||||||
border-radius: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.appbar {
|
.appbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, Card, DropdownSelect } from '@modrinth/ui'
|
import { ConfirmModal, Button, Card, DropdownSelect } from '@modrinth/ui'
|
||||||
import { formatCategoryHeader } from '@modrinth/utils'
|
import { formatCategoryHeader } from '@modrinth/utils'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
|
import { useTheming } from '@/store/theme.js'
|
||||||
import { duplicate, remove } from '@/helpers/profile.js'
|
import { duplicate, remove } from '@/helpers/profile.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instances: {
|
instances: {
|
||||||
@@ -35,6 +35,7 @@ const props = defineProps({
|
|||||||
const instanceOptions = ref(null)
|
const instanceOptions = ref(null)
|
||||||
const instanceComponents = ref(null)
|
const instanceComponents = ref(null)
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
const currentDeleteInstance = ref(null)
|
const currentDeleteInstance = ref(null)
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
|
|
||||||
@@ -229,12 +230,13 @@ const filteredResults = computed(() => {
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<template>
|
<template>
|
||||||
<ConfirmModalWrapper
|
<ConfirmModal
|
||||||
ref="confirmModal"
|
ref="confirmModal"
|
||||||
title="Are you sure you want to delete this instance?"
|
title="Are you sure you want to delete this instance?"
|
||||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||||
:has-to-type="false"
|
:has-to-type="false"
|
||||||
proceed-label="Delete"
|
proceed-label="Delete"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
@proceed="deleteProfile"
|
@proceed="deleteProfile"
|
||||||
/>
|
/>
|
||||||
<Card class="header">
|
<Card class="header">
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
EyeIcon,
|
EyeIcon,
|
||||||
ChevronRightIcon,
|
ChevronRightIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
import { ConfirmModal } from '@modrinth/ui'
|
||||||
import Instance from '@/components/ui/Instance.vue'
|
import Instance from '@/components/ui/Instance.vue'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
@@ -22,6 +22,7 @@ import { handleError } from '@/store/notifications.js'
|
|||||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||||
|
import { useTheming } from '@/store/state.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
import { handleSevereError } from '@/store/error.js'
|
||||||
import { install as installVersion } from '@/store/install.js'
|
import { install as installVersion } from '@/store/install.js'
|
||||||
@@ -52,6 +53,7 @@ const instanceComponents = ref(null)
|
|||||||
const rows = ref(null)
|
const rows = ref(null)
|
||||||
const deleteConfirmModal = ref(null)
|
const deleteConfirmModal = ref(null)
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
const currentDeleteInstance = ref(null)
|
const currentDeleteInstance = ref(null)
|
||||||
|
|
||||||
async function deleteProfile() {
|
async function deleteProfile() {
|
||||||
@@ -205,12 +207,13 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ConfirmModalWrapper
|
<ConfirmModal
|
||||||
ref="deleteConfirmModal"
|
ref="deleteConfirmModal"
|
||||||
title="Are you sure you want to delete this instance?"
|
title="Are you sure you want to delete this instance?"
|
||||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||||
:has-to-type="false"
|
:has-to-type="false"
|
||||||
proceed-label="Delete"
|
proceed-label="Delete"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
@proceed="deleteProfile"
|
@proceed="deleteProfile"
|
||||||
/>
|
/>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
v-tooltip.right="'Minecraft accounts'"
|
v-tooltip.right="'Minecraft accounts'"
|
||||||
class="button-base avatar-button"
|
class="button-base avatar-button"
|
||||||
:class="{ expanded: mode === 'expanded' }"
|
:class="{ expanded: mode === 'expanded' }"
|
||||||
@click="toggleMenu"
|
@click="showCard = !showCard"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
||||||
@@ -73,7 +73,6 @@ import { handleError } from '@/store/state.js'
|
|||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { process_listener } from '@/helpers/events'
|
import { process_listener } from '@/helpers/events'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
import { handleSevereError } from '@/store/error.js'
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
mode: {
|
mode: {
|
||||||
@@ -134,9 +133,9 @@ const logout = async (id) => {
|
|||||||
trackEvent('AccountLogOut')
|
trackEvent('AccountLogOut')
|
||||||
}
|
}
|
||||||
|
|
||||||
const showCard = ref(false)
|
let showCard = ref(false)
|
||||||
const card = ref(null)
|
let card = ref(null)
|
||||||
const button = ref(null)
|
let button = ref(null)
|
||||||
const handleClickOutside = (event) => {
|
const handleClickOutside = (event) => {
|
||||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||||
if (
|
if (
|
||||||
@@ -145,20 +144,7 @@ const handleClickOutside = (event) => {
|
|||||||
!elements.includes(card.value.$el) &&
|
!elements.includes(card.value.$el) &&
|
||||||
!button.value.contains(event.target)
|
!button.value.contains(event.target)
|
||||||
) {
|
) {
|
||||||
toggleMenu(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleMenu(override = true) {
|
|
||||||
if (showCard.value || !override) {
|
|
||||||
if (showCard.value) {
|
|
||||||
show_ads_window()
|
|
||||||
}
|
|
||||||
|
|
||||||
showCard.value = false
|
showCard.value = false
|
||||||
} else {
|
|
||||||
hide_ads_window()
|
|
||||||
showCard.value = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const handleAddContentFromFile = async () => {
|
|||||||
if (!newProject) return
|
if (!newProject) return
|
||||||
|
|
||||||
for (const project of newProject) {
|
for (const project of newProject) {
|
||||||
await add_project_from_path(props.instance.path, project.path ?? project).catch(handleError)
|
await add_project_from_path(props.instance.path, project).catch(handleError)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -25,7 +25,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
|
|
||||||
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
||||||
|
|
||||||
@@ -38,7 +37,6 @@ const shown = ref(false)
|
|||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
showMenu: (event, passedItem, passedOptions) => {
|
showMenu: (event, passedItem, passedOptions) => {
|
||||||
hide_ads_window()
|
|
||||||
item.value = passedItem
|
item.value = passedItem
|
||||||
options.value = passedOptions
|
options.value = passedOptions
|
||||||
|
|
||||||
@@ -71,9 +69,6 @@ const isLinkedData = (item) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const hideContextMenu = () => {
|
const hideContextMenu = () => {
|
||||||
if (shown.value) {
|
|
||||||
show_ads_window()
|
|
||||||
}
|
|
||||||
shown.value = false
|
shown.value = false
|
||||||
emit('menu-closed')
|
emit('menu-closed')
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
|
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
|
||||||
|
import { Modal } from '@modrinth/ui'
|
||||||
import { ChatIcon } from '@/assets/icons'
|
import { ChatIcon } from '@/assets/icons'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||||
@@ -8,7 +9,6 @@ import { handleSevereError } from '@/store/error.js'
|
|||||||
import { cancel_directory_change } from '@/helpers/settings.js'
|
import { cancel_directory_change } from '@/helpers/settings.js'
|
||||||
import { install } from '@/helpers/profile.js'
|
import { install } from '@/helpers/profile.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
const errorModal = ref()
|
const errorModal = ref()
|
||||||
const error = ref()
|
const error = ref()
|
||||||
@@ -121,7 +121,7 @@ async function repairInstance() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
|
<Modal ref="errorModal" :header="title" :closable="closable">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="markdown-body">
|
<div class="markdown-body">
|
||||||
<template v-if="errorType === 'minecraft_auth'">
|
<template v-if="errorType === 'minecraft_auth'">
|
||||||
@@ -230,7 +230,7 @@ async function repairInstance() {
|
|||||||
</p>
|
</p>
|
||||||
<p>You may be able to fix it through one of the following ways:</p>
|
<p>You may be able to fix it through one of the following ways:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Ensuring you are connected to the internet, then try restarting the app.</li>
|
<li>Ennsuring you are connected to the internet, then try restarting the app.</li>
|
||||||
<li>Redownloading the app.</li>
|
<li>Redownloading the app.</li>
|
||||||
</ul>
|
</ul>
|
||||||
</template>
|
</template>
|
||||||
@@ -272,7 +272,7 @@ async function repairInstance() {
|
|||||||
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, PlusIcon } from '@modrinth/assets'
|
import { XIcon, PlusIcon } from '@modrinth/assets'
|
||||||
import { Button, Checkbox } from '@modrinth/ui'
|
import { Button, Checkbox, Modal } from '@modrinth/ui'
|
||||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
import { useTheming } from '@/store/theme'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
@@ -30,6 +30,8 @@ const files = ref([])
|
|||||||
const folders = ref([])
|
const folders = ref([])
|
||||||
const showingFiles = ref(false)
|
const showingFiles = ref(false)
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const initFiles = async () => {
|
const initFiles = async () => {
|
||||||
const newFolders = new Map()
|
const newFolders = new Map()
|
||||||
const sep = '/'
|
const sep = '/'
|
||||||
@@ -104,7 +106,7 @@ const exportPack = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="exportModal" header="Export modpack">
|
<Modal ref="exportModal" header="Export modpack" :noblur="!themeStore.advancedRendering">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="labeled_input">
|
<div class="labeled_input">
|
||||||
<p>Modpack Name</p>
|
<p>Modpack Name</p>
|
||||||
@@ -206,7 +208,7 @@ const exportPack = async () => {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="modal" header="Create instance">
|
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
|
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
|
||||||
</div>
|
</div>
|
||||||
@@ -193,11 +193,10 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import {
|
import {
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
UploadIcon,
|
UploadIcon,
|
||||||
@@ -208,7 +207,7 @@ import {
|
|||||||
FolderSearchIcon,
|
FolderSearchIcon,
|
||||||
UpdatedIcon,
|
UpdatedIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Button, Chips, Checkbox } from '@modrinth/ui'
|
import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
|
||||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||||
import { get_loaders } from '@/helpers/tags'
|
import { get_loaders } from '@/helpers/tags'
|
||||||
import { create } from '@/helpers/profile'
|
import { create } from '@/helpers/profile'
|
||||||
@@ -218,6 +217,8 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
|||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import Multiselect from 'vue-multiselect'
|
import Multiselect from 'vue-multiselect'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { useTheming } from '@/store/state.js'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import { install_from_file } from '@/helpers/pack.js'
|
import { install_from_file } from '@/helpers/pack.js'
|
||||||
import {
|
import {
|
||||||
get_default_launcher_path,
|
get_default_launcher_path,
|
||||||
@@ -225,7 +226,8 @@ import {
|
|||||||
import_instance,
|
import_instance,
|
||||||
} from '@/helpers/import.js'
|
} from '@/helpers/import.js'
|
||||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const profile_name = ref('')
|
const profile_name = ref('')
|
||||||
const game_version = ref('')
|
const game_version = ref('')
|
||||||
@@ -255,15 +257,13 @@ defineExpose({
|
|||||||
isShowing.value = true
|
isShowing.value = true
|
||||||
modal.value.show()
|
modal.value.show()
|
||||||
|
|
||||||
unlistener.value = await getCurrentWebview().onDragDropEvent(async (event) => {
|
unlistener.value = await listen('tauri://file-drop', async (event) => {
|
||||||
// Only if modal is showing
|
// Only if modal is showing
|
||||||
if (!isShowing.value) return
|
if (!isShowing.value) return
|
||||||
if (event.payload.type !== 'drop') return
|
|
||||||
if (creationType.value !== 'from file') return
|
if (creationType.value !== 'from file') return
|
||||||
hide()
|
hide()
|
||||||
const { paths } = event.payload
|
if (event.payload && event.payload.length > 0 && event.payload[0].endsWith('.mrpack')) {
|
||||||
if (paths && paths.length > 0 && paths[0].endsWith('.mrpack')) {
|
await install_from_file(event.payload[0]).catch(handleError)
|
||||||
await install_from_file(paths[0]).catch(handleError)
|
|
||||||
trackEvent('InstanceCreate', {
|
trackEvent('InstanceCreate', {
|
||||||
source: 'CreationModalFileDrop',
|
source: 'CreationModalFileDrop',
|
||||||
})
|
})
|
||||||
@@ -371,7 +371,7 @@ const create_instance = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upload_icon = async () => {
|
const upload_icon = async () => {
|
||||||
const res = await open({
|
icon.value = await open({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
@@ -381,8 +381,6 @@ const upload_icon = async () => {
|
|||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
icon.value = res.path ?? res
|
|
||||||
|
|
||||||
if (!icon.value) return
|
if (!icon.value) return
|
||||||
display_icon.value = convertFileSrc(icon.value)
|
display_icon.value = convertFileSrc(icon.value)
|
||||||
}
|
}
|
||||||
@@ -419,7 +417,7 @@ const openFile = async () => {
|
|||||||
const newProject = await open({ multiple: false })
|
const newProject = await open({ multiple: false })
|
||||||
if (!newProject) return
|
if (!newProject) return
|
||||||
hide()
|
hide()
|
||||||
await install_from_file(newProject.path ?? newProject).catch(handleError)
|
await install_from_file(newProject).catch(handleError)
|
||||||
|
|
||||||
trackEvent('InstanceCreate', {
|
trackEvent('InstanceCreate', {
|
||||||
source: 'CreationModalFileOpen',
|
source: 'CreationModalFileOpen',
|
||||||
@@ -464,7 +462,7 @@ const promises = profileOptions.value.map(async (option) => {
|
|||||||
option.name,
|
option.name,
|
||||||
instances.map((name) => ({ name, selected: false })),
|
instances.map((name) => ({ name, selected: false })),
|
||||||
)
|
)
|
||||||
} catch {
|
} catch (error) {
|
||||||
// Allow failure silently
|
// Allow failure silently
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="detectJavaModal" header="Select java version">
|
<Modal ref="detectJavaModal" header="Select java version" :noblur="!themeStore.advancedRendering">
|
||||||
<div class="auto-detect-modal">
|
<div class="auto-detect-modal">
|
||||||
<div class="table">
|
<div class="table">
|
||||||
<div class="table-row table-head">
|
<div class="table-row table-head">
|
||||||
@@ -32,16 +32,18 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Modal, Button } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
|
import { useTheming } from '@/store/theme.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const chosenInstallOptions = ref([])
|
const chosenInstallOptions = ref([])
|
||||||
const detectJavaModal = ref(null)
|
const detectJavaModal = ref(null)
|
||||||
|
|||||||
@@ -124,19 +124,20 @@ async function testJava() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleJavaFileInput() {
|
async function handleJavaFileInput() {
|
||||||
const filePath = await open()
|
let filePath = await open()
|
||||||
|
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
let result = await get_jre(filePath.path ?? filePath)
|
let result = await get_jre(filePath)
|
||||||
if (!result) {
|
if (!result) {
|
||||||
result = {
|
result = {
|
||||||
path: filePath.path ?? filePath,
|
path: filePath,
|
||||||
version: props.version.toString(),
|
version: props.version.toString(),
|
||||||
architecture: 'x86',
|
architecture: 'x86',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trackEvent('JavaManualSelect', {
|
trackEvent('JavaManualSelect', {
|
||||||
|
path: filePath,
|
||||||
version: props.version,
|
version: props.version,
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -149,7 +150,7 @@ async function autoDetect() {
|
|||||||
if (!props.compact) {
|
if (!props.compact) {
|
||||||
detectJavaModal.value.show(props.version, props.modelValue)
|
detectJavaModal.value.show(props.version, props.modelValue)
|
||||||
} else {
|
} else {
|
||||||
const versions = await find_filtered_jres(props.version).catch(handleError)
|
let versions = await find_filtered_jres(props.version).catch(handleError)
|
||||||
if (versions.length > 0) {
|
if (versions.length > 0) {
|
||||||
emit('update:modelValue', versions[0])
|
emit('update:modelValue', versions[0])
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { CheckIcon } from '@modrinth/assets'
|
import { CheckIcon } from '@modrinth/assets'
|
||||||
import { Button, Badge } from '@modrinth/ui'
|
import { Button, Modal, Badge } from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
|
import { useTheming } from '@/store/theme'
|
||||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||||
import { releaseColor } from '@/helpers/utils'
|
import { releaseColor } from '@/helpers/utils'
|
||||||
import { SwapIcon } from '@/assets/icons/index.js'
|
import { SwapIcon } from '@/assets/icons/index.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
versions: {
|
versions: {
|
||||||
@@ -33,6 +33,8 @@ const installedVersion = computed(() => props.instance?.linked_data?.version_id)
|
|||||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||||
const inProgress = ref(false)
|
const inProgress = ref(false)
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const switchVersion = async (versionId) => {
|
const switchVersion = async (versionId) => {
|
||||||
inProgress.value = true
|
inProgress.value = true
|
||||||
await update_managed_modrinth_version(props.instance.path, versionId)
|
await update_managed_modrinth_version(props.instance.path, versionId)
|
||||||
@@ -41,10 +43,11 @@ const switchVersion = async (versionId) => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper
|
<Modal
|
||||||
ref="modpackVersionModal"
|
ref="modpackVersionModal"
|
||||||
class="modpack-version-modal"
|
class="modpack-version-modal"
|
||||||
header="Change modpack version"
|
header="Change modpack version"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
>
|
>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<Card v-if="instance.linked_data" class="mod-card">
|
<Card v-if="instance.linked_data" class="mod-card">
|
||||||
@@ -108,7 +111,7 @@ const switchVersion = async (versionId) => {
|
|||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,20 +1,12 @@
|
|||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onUnmounted } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
import { Promotion } from '@modrinth/ui'
|
||||||
import { get as getCreds } from '@/helpers/mr_auth.js'
|
import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import { get_user } from '@/helpers/cache.js'
|
import { get_user } from '@/helpers/cache.js'
|
||||||
import { ChevronRightIcon } from '@modrinth/assets'
|
|
||||||
import { init_ads_window, open_ads_link, record_ads_click } from '@/helpers/ads.js'
|
|
||||||
import { listen } from '@tauri-apps/api/event'
|
|
||||||
|
|
||||||
const showAd = ref(true)
|
const showAd = ref(true)
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
scroll() {
|
|
||||||
updateAdPosition()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const creds = await getCreds().catch(handleError)
|
const creds = await getCreds().catch(handleError)
|
||||||
if (creds && creds.user_id) {
|
if (creds && creds.user_id) {
|
||||||
const user = await get_user(creds.user_id).catch(handleError)
|
const user = await get_user(creds.user_id).catch(handleError)
|
||||||
@@ -24,103 +16,8 @@ if (creds && creds.user_id) {
|
|||||||
showAd.value = false
|
showAd.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const adsWrapper = ref(null)
|
|
||||||
let resizeObserver
|
|
||||||
let scrollHandler
|
|
||||||
let intersectionObserver
|
|
||||||
let mutationObserver
|
|
||||||
onMounted(() => {
|
|
||||||
if (showAd.value) {
|
|
||||||
updateAdPosition(true)
|
|
||||||
|
|
||||||
resizeObserver = new ResizeObserver(() => updateAdPosition())
|
|
||||||
resizeObserver.observe(adsWrapper.value)
|
|
||||||
|
|
||||||
intersectionObserver = new IntersectionObserver(() => updateAdPosition())
|
|
||||||
intersectionObserver.observe(adsWrapper.value)
|
|
||||||
|
|
||||||
mutationObserver = new MutationObserver(() => updateAdPosition())
|
|
||||||
mutationObserver.observe(adsWrapper.value, { attributes: true, childList: true, subtree: true })
|
|
||||||
|
|
||||||
// Add scroll event listener
|
|
||||||
scrollHandler = () => {
|
|
||||||
requestAnimationFrame(() => updateAdPosition())
|
|
||||||
}
|
|
||||||
window.addEventListener('scroll', scrollHandler, { passive: true })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function updateAdPosition(overrideShown = false) {
|
|
||||||
if (adsWrapper.value) {
|
|
||||||
const rect = adsWrapper.value.getBoundingClientRect()
|
|
||||||
|
|
||||||
let y = rect.top + window.scrollY
|
|
||||||
let height = rect.bottom - rect.top
|
|
||||||
|
|
||||||
// Prevent ad from overlaying the app bar
|
|
||||||
if (y <= 52) {
|
|
||||||
y = 52
|
|
||||||
height = rect.bottom - 52
|
|
||||||
|
|
||||||
if (height < 0) {
|
|
||||||
height = 0
|
|
||||||
y = -1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init_ads_window(rect.left + window.scrollX, y, rect.right - rect.left, height, overrideShown)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openPlusLink() {
|
|
||||||
await record_ads_click()
|
|
||||||
await open_ads_link('https://modrinth.com/plus', 'https://modrinth.com')
|
|
||||||
}
|
|
||||||
|
|
||||||
const unlisten = await listen('ads-scroll', (event) => {
|
|
||||||
if (adsWrapper.value) {
|
|
||||||
adsWrapper.value.parentNode.scrollTop += event.payload.scroll
|
|
||||||
updateAdPosition()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
|
||||||
if (resizeObserver) {
|
|
||||||
resizeObserver.disconnect()
|
|
||||||
}
|
|
||||||
if (intersectionObserver) {
|
|
||||||
intersectionObserver.disconnect()
|
|
||||||
}
|
|
||||||
if (mutationObserver) {
|
|
||||||
mutationObserver.disconnect()
|
|
||||||
}
|
|
||||||
if (scrollHandler) {
|
|
||||||
window.removeEventListener('scroll', scrollHandler)
|
|
||||||
}
|
|
||||||
|
|
||||||
unlisten()
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<Promotion v-if="showAd" :external="false" query-param="?r=launcher" />
|
||||||
v-if="showAd"
|
|
||||||
ref="adsWrapper"
|
|
||||||
class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised cursor-pointer"
|
|
||||||
>
|
|
||||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
|
||||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
|
||||||
<button
|
|
||||||
class="mt-auto items-center gap-1 text-purple hover:underline bg-transparent border-none text-left cursor-pointer outline-none"
|
|
||||||
@click="openPlusLink"
|
|
||||||
>
|
|
||||||
<span>
|
|
||||||
Support creators and Modrinth ad-free with
|
|
||||||
<span class="font-bold">Modrinth+</span>
|
|
||||||
</span>
|
|
||||||
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
@@ -88,6 +88,8 @@ import { loading_listener } from '@/helpers/events.js'
|
|||||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||||
import { XIcon } from '@modrinth/assets'
|
import { XIcon } from '@modrinth/assets'
|
||||||
import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js'
|
import { MaximizeIcon, MinimizeIcon } from '@/assets/icons/index.js'
|
||||||
|
import { TauriEvent } from '@tauri-apps/api/event'
|
||||||
|
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||||
import { getOS } from '@/helpers/utils.js'
|
import { getOS } from '@/helpers/utils.js'
|
||||||
import { useLoading } from '@/store/loading.js'
|
import { useLoading } from '@/store/loading.js'
|
||||||
|
|
||||||
@@ -128,16 +130,9 @@ const os = ref('')
|
|||||||
getOS().then((x) => (os.value = x))
|
getOS().then((x) => (os.value = x))
|
||||||
|
|
||||||
loading_listener(async (e) => {
|
loading_listener(async (e) => {
|
||||||
console.log(e)
|
|
||||||
if (e.event.type === 'directory_move') {
|
if (e.event.type === 'directory_move') {
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||||
message.value = 'Updating app directory...'
|
message.value = 'Updating app directory...'
|
||||||
} else if (e.event.type === 'launcher_update') {
|
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
|
||||||
message.value = 'Updating Modrinth App...'
|
|
||||||
} else if (e.event.type === 'checking_for_updates') {
|
|
||||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
|
||||||
message.value = 'Checking for updates...'
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { Button } from '@modrinth/ui'
|
import { Modal, Button } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||||
import { get_categories } from '@/helpers/tags.js'
|
import { get_categories } from '@/helpers/tags.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import { get_version, get_project } from '@/helpers/cache.js'
|
import { get_version, get_project } from '@/helpers/cache.js'
|
||||||
import { install as installVersion } from '@/store/install.js'
|
import { install as installVersion } from '@/store/install.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const project = ref(null)
|
const project = ref(null)
|
||||||
@@ -42,7 +41,7 @@ async function install() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
|
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<SearchCard
|
<SearchCard
|
||||||
:project="project"
|
:project="project"
|
||||||
@@ -61,7 +60,7 @@ async function install() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="incompatibleModal" header="Incompatibility warning" :on-hide="onInstall">
|
<Modal
|
||||||
|
ref="incompatibleModal"
|
||||||
|
header="Incompatibility warning"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
:on-hide="onInstall"
|
||||||
|
>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>
|
<p>
|
||||||
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
|
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
|
||||||
@@ -46,19 +51,20 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||||
import { Button, DropdownSelect } from '@modrinth/ui'
|
import { Button, Modal, DropdownSelect } from '@modrinth/ui'
|
||||||
import { formatCategory } from '@modrinth/utils'
|
import { formatCategory } from '@modrinth/utils'
|
||||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { handleError } from '@/store/state.js'
|
import { handleError, useTheming } from '@/store/state.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const instance = ref(null)
|
const instance = ref(null)
|
||||||
const project = ref(null)
|
const project = ref(null)
|
||||||
const versions = ref(null)
|
const versions = ref(null)
|
||||||
@@ -66,7 +72,7 @@ const selectedVersion = ref(null)
|
|||||||
const incompatibleModal = ref(null)
|
const incompatibleModal = ref(null)
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
|
||||||
const onInstall = ref(() => {})
|
let onInstall = ref(() => {})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
show: (instanceVal, projectVal, projectVersions, callback) => {
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||||
import { Button } from '@modrinth/ui'
|
import { Button, Modal } from '@modrinth/ui'
|
||||||
import { install as pack_install } from '@/helpers/pack'
|
import { install as pack_install } from '@/helpers/pack'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { useTheming } from '@/store/theme.js'
|
||||||
import { handleError } from '@/store/state.js'
|
import { handleError } from '@/store/state.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const versionId = ref()
|
const versionId = ref()
|
||||||
const project = ref()
|
const project = ref()
|
||||||
const confirmModal = ref(null)
|
const confirmModal = ref(null)
|
||||||
const installing = ref(false)
|
const installing = ref(false)
|
||||||
|
|
||||||
const onInstall = ref(() => {})
|
let onInstall = ref(() => {})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: (projectVal, versionIdVal, callback) => {
|
show: (projectVal, versionIdVal, callback) => {
|
||||||
@@ -50,7 +52,12 @@ async function install() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="confirmModal" header="Are you sure?" :on-hide="onInstall">
|
<Modal
|
||||||
|
ref="confirmModal"
|
||||||
|
header="Are you sure?"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
:on-hide="onInstall"
|
||||||
|
>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||||
<div class="input-group push-right">
|
<div class="input-group push-right">
|
||||||
@@ -60,7 +67,7 @@ async function install() {
|
|||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
RightArrowIcon,
|
RightArrowIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
import { Avatar, Modal, Button, Card } from '@modrinth/ui'
|
||||||
import { computed, ref } from 'vue'
|
import { computed, ref } from 'vue'
|
||||||
import {
|
import {
|
||||||
add_project_from_version as installMod,
|
add_project_from_version as installMod,
|
||||||
@@ -19,11 +19,12 @@ import {
|
|||||||
import { open } from '@tauri-apps/plugin-dialog'
|
import { open } from '@tauri-apps/plugin-dialog'
|
||||||
import { installVersionDependencies } from '@/store/install.js'
|
import { installVersionDependencies } from '@/store/install.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
|
import { useTheming } from '@/store/theme.js'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const versions = ref()
|
const versions = ref()
|
||||||
@@ -48,7 +49,7 @@ const shownProfiles = computed(() =>
|
|||||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||||
})
|
})
|
||||||
.filter((profile) => {
|
.filter((profile) => {
|
||||||
const loaders = versions.value.flatMap((v) => v.loaders)
|
let loaders = versions.value.flatMap((v) => v.loaders)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
||||||
@@ -59,7 +60,7 @@ const shownProfiles = computed(() =>
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const onInstall = ref(() => {})
|
let onInstall = ref(() => {})
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
show: async (projectVal, versionsVal, callback) => {
|
show: async (projectVal, versionsVal, callback) => {
|
||||||
@@ -77,7 +78,7 @@ defineExpose({
|
|||||||
onInstall.value = callback
|
onInstall.value = callback
|
||||||
|
|
||||||
const profilesVal = await list().catch(handleError)
|
const profilesVal = await list().catch(handleError)
|
||||||
for (const profile of profilesVal) {
|
for (let profile of profilesVal) {
|
||||||
profile.installing = false
|
profile.installing = false
|
||||||
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
||||||
handleError,
|
handleError,
|
||||||
@@ -141,7 +142,7 @@ const toggleCreation = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const upload_icon = async () => {
|
const upload_icon = async () => {
|
||||||
const res = await open({
|
icon.value = await open({
|
||||||
multiple: false,
|
multiple: false,
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
@@ -150,7 +151,6 @@ const upload_icon = async () => {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
icon.value = res.path ?? res
|
|
||||||
|
|
||||||
if (!icon.value) return
|
if (!icon.value) return
|
||||||
display_icon.value = convertFileSrc(icon.value)
|
display_icon.value = convertFileSrc(icon.value)
|
||||||
@@ -213,7 +213,12 @@ const createInstance = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
|
<Modal
|
||||||
|
ref="installModal"
|
||||||
|
header="Install project to instance"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
:on-hide="onInstall"
|
||||||
|
>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<input
|
<input
|
||||||
v-model="searchFilter"
|
v-model="searchFilter"
|
||||||
@@ -230,7 +235,7 @@ const createInstance = async () => {
|
|||||||
@click="installModal.hide()"
|
@click="installModal.hide()"
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
:src="profile.icon_path ? convertFileSrc(profile.icon_path) : null"
|
:src="profile.icon_path ? tauri.convertFileSrc(profile.icon_path) : null"
|
||||||
class="profile-image"
|
class="profile-image"
|
||||||
/>
|
/>
|
||||||
{{ profile.name }}
|
{{ profile.name }}
|
||||||
@@ -299,7 +304,7 @@ const createInstance = async () => {
|
|||||||
<Button @click="installModal.hide()">Cancel</Button>
|
<Button @click="installModal.hide()">Cancel</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ConfirmModal } from '@modrinth/ui'
|
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
import { useTheming } from '@/store/theme.js'
|
|
||||||
|
|
||||||
const themeStore = useTheming()
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
confirmationText: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hasToType: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
type: String,
|
|
||||||
default: 'No title defined',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
type: String,
|
|
||||||
default: 'No description defined',
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
proceedLabel: {
|
|
||||||
type: String,
|
|
||||||
default: 'Proceed',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const emit = defineEmits(['proceed'])
|
|
||||||
const modal = ref(null)
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
show: () => {
|
|
||||||
hide_ads_window()
|
|
||||||
modal.value.show()
|
|
||||||
},
|
|
||||||
hide: () => {
|
|
||||||
onModalHide()
|
|
||||||
modal.value.hide()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function onModalHide() {
|
|
||||||
show_ads_window()
|
|
||||||
}
|
|
||||||
|
|
||||||
function proceed() {
|
|
||||||
emit('proceed')
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ConfirmModal
|
|
||||||
ref="modal"
|
|
||||||
:confirmation-text="confirmationText"
|
|
||||||
:has-to-type="hasToType"
|
|
||||||
:title="title"
|
|
||||||
:description="description"
|
|
||||||
:proceed-label="proceedLabel"
|
|
||||||
:on-hide="onModalHide"
|
|
||||||
:noblur="!themeStore.advancedRendering"
|
|
||||||
@proceed="proceed"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { Modal } from '@modrinth/ui'
|
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
import { useTheming } from '@/store/theme.js'
|
|
||||||
|
|
||||||
const themeStore = useTheming()
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
header: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
closable: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
onHide: {
|
|
||||||
type: Function,
|
|
||||||
default() {
|
|
||||||
return () => {}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const modal = ref(null)
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
show: () => {
|
|
||||||
hide_ads_window()
|
|
||||||
modal.value.show()
|
|
||||||
},
|
|
||||||
hide: () => {
|
|
||||||
onModalHide()
|
|
||||||
modal.value.hide()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function onModalHide() {
|
|
||||||
show_ads_window()
|
|
||||||
props.onHide()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
|
|
||||||
<slot />
|
|
||||||
</Modal>
|
|
||||||
</template>
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
import { ShareModal } from '@modrinth/ui'
|
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
import { useTheming } from '@/store/theme.js'
|
|
||||||
|
|
||||||
const themeStore = useTheming()
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
header: {
|
|
||||||
type: String,
|
|
||||||
default: 'Share',
|
|
||||||
},
|
|
||||||
shareTitle: {
|
|
||||||
type: String,
|
|
||||||
default: 'Modrinth',
|
|
||||||
},
|
|
||||||
shareText: {
|
|
||||||
type: String,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
link: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
openInNewTab: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const modal = ref(null)
|
|
||||||
|
|
||||||
defineExpose({
|
|
||||||
show: (passedContent) => {
|
|
||||||
hide_ads_window()
|
|
||||||
modal.value.show(passedContent)
|
|
||||||
},
|
|
||||||
hide: () => {
|
|
||||||
onModalHide()
|
|
||||||
modal.value.hide()
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
function onModalHide() {
|
|
||||||
show_ads_window()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ShareModal
|
|
||||||
ref="modal"
|
|
||||||
:header="header"
|
|
||||||
:share-title="shareTitle"
|
|
||||||
:share-text="shareText"
|
|
||||||
:link="link"
|
|
||||||
:open-in-new-tab="openInNewTab"
|
|
||||||
:on-hide="onModalHide"
|
|
||||||
:noblur="!themeStore.advancedRendering"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
||||||
import { Button, Card, Checkbox } from '@modrinth/ui'
|
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
DiscordIcon,
|
DiscordIcon,
|
||||||
GithubIcon,
|
GithubIcon,
|
||||||
@@ -13,7 +13,6 @@ import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.
|
|||||||
import { handleError, useNotifications } from '@/store/state.js'
|
import { handleError, useNotifications } from '@/store/state.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { handleSevereError } from '@/store/error.js'
|
import { handleSevereError } from '@/store/error.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
callback: {
|
callback: {
|
||||||
@@ -133,7 +132,7 @@ async function createAccount() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<ModalWrapper ref="modal" :on-hide="removeWidget">
|
<Modal ref="modal" :on-hide="removeWidget">
|
||||||
<Card>
|
<Card>
|
||||||
<template v-if="twoFactorFlow">
|
<template v-if="twoFactorFlow">
|
||||||
<h1>Enter two-factor code</h1>
|
<h1>Enter two-factor code</h1>
|
||||||
@@ -218,17 +217,17 @@ async function createAccount() {
|
|||||||
v-else-if="loggingIn"
|
v-else-if="loggingIn"
|
||||||
color="primary"
|
color="primary"
|
||||||
large
|
large
|
||||||
:disabled="!turnstileToken"
|
|
||||||
@click="signIn"
|
@click="signIn"
|
||||||
|
:disabled="!turnstileToken"
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
<Button v-else color="primary" large :disabled="!turnstileToken" @click="createAccount">
|
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
|
||||||
Create account
|
Create account
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core'
|
|
||||||
|
|
||||||
export async function init_ads_window(x, y, width, height, overrideShown = false) {
|
|
||||||
return await invoke('plugin:ads|init_ads_window', { x, y, width, height, overrideShown })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function show_ads_window() {
|
|
||||||
return await invoke('plugin:ads|show_ads_window')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function hide_ads_window(reset) {
|
|
||||||
return await invoke('plugin:ads|hide_ads_window', { reset })
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function record_ads_click() {
|
|
||||||
return await invoke('plugin:ads|record_ads_click')
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function open_ads_link(path, origin) {
|
|
||||||
return await invoke('plugin:ads|open_link', { path, origin })
|
|
||||||
}
|
|
||||||
@@ -33,10 +33,6 @@ export async function highlightModInProfile(profilePath, projectPath) {
|
|||||||
return await highlightInFolder(fullPath)
|
return await highlightInFolder(fullPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function restartApp() {
|
|
||||||
return await invoke('restart_app')
|
|
||||||
}
|
|
||||||
|
|
||||||
export const releaseColor = (releaseType) => {
|
export const releaseColor = (releaseType) => {
|
||||||
switch (releaseType) {
|
switch (releaseType) {
|
||||||
case 'release':
|
case 'release':
|
||||||
@@ -56,7 +52,7 @@ export function debounce(fn, wait) {
|
|||||||
if (timer) {
|
if (timer) {
|
||||||
clearTimeout(timer) // clear any pre-existing timer
|
clearTimeout(timer) // clear any pre-existing timer
|
||||||
}
|
}
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||||
const context = this // get the current context
|
const context = this // get the current context
|
||||||
timer = setTimeout(() => {
|
timer = setTimeout(() => {
|
||||||
fn.apply(context, args) // call the function if time expires
|
fn.apply(context, args) // call the function if time expires
|
||||||
|
|||||||
@@ -381,20 +381,20 @@ const sortedCategories = computed(() => {
|
|||||||
// identifier[0], then if it ties, identifier[1], etc
|
// identifier[0], then if it ties, identifier[1], etc
|
||||||
async function sortByNameOrNumber(sortable, identifiers) {
|
async function sortByNameOrNumber(sortable, identifiers) {
|
||||||
sortable.sort((a, b) => {
|
sortable.sort((a, b) => {
|
||||||
for (const identifier of identifiers) {
|
for (let identifier of identifiers) {
|
||||||
const aNum = parseFloat(a[identifier])
|
let aNum = parseFloat(a[identifier])
|
||||||
const bNum = parseFloat(b[identifier])
|
let bNum = parseFloat(b[identifier])
|
||||||
if (isNaN(aNum) && isNaN(bNum)) {
|
if (isNaN(aNum) && isNaN(bNum)) {
|
||||||
// Both are strings, sort alphabetically
|
// Both are strings, sort alphabetically
|
||||||
const stringComp = a[identifier].localeCompare(b[identifier])
|
let stringComp = a[identifier].localeCompare(b[identifier])
|
||||||
if (stringComp != 0) return stringComp
|
if (stringComp != 0) return stringComp
|
||||||
} else if (!isNaN(aNum) && !isNaN(bNum)) {
|
} else if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||||
// Both are numbers, sort numerically
|
// Both are numbers, sort numerically
|
||||||
const numComp = aNum - bNum
|
let numComp = aNum - bNum
|
||||||
if (numComp != 0) return numComp
|
if (numComp != 0) return numComp
|
||||||
} else {
|
} else {
|
||||||
// One is a number and one is a string, numbers go first
|
// One is a number and one is a string, numbers go first
|
||||||
const numStringComp = isNaN(aNum) ? 1 : -1
|
let numStringComp = isNaN(aNum) ? 1 : -1
|
||||||
if (numStringComp != 0) return numStringComp
|
if (numStringComp != 0) return numStringComp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -528,8 +528,7 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div ref="searchWrapper" class="search-container">
|
<div ref="searchWrapper" class="search-container">
|
||||||
<aside class="filter-panel" @scroll="$refs.promo.scroll()">
|
<aside class="filter-panel">
|
||||||
<PromotionWrapper ref="promo" />
|
|
||||||
<Card v-if="instanceContext" class="small-instance">
|
<Card v-if="instanceContext" class="small-instance">
|
||||||
<router-link :to="`/instance/${encodeURIComponent(instanceContext.path)}`" class="instance">
|
<router-link :to="`/instance/${encodeURIComponent(instanceContext.path)}`" class="instance">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -676,7 +675,8 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
|||||||
</Card>
|
</Card>
|
||||||
</aside>
|
</aside>
|
||||||
<div class="search">
|
<div class="search">
|
||||||
<Card class="project-type-container mt-4">
|
<PromotionWrapper class="mt-4" />
|
||||||
|
<Card class="project-type-container">
|
||||||
<NavRow :links="selectableProjectTypes" />
|
<NavRow :links="selectableProjectTypes" />
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="search-panel-container">
|
<Card class="search-panel-container">
|
||||||
@@ -878,13 +878,13 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
|||||||
|
|
||||||
.filter-panel {
|
.filter-panel {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
width: 20rem;
|
||||||
padding: 1rem 0.5rem 1rem 1rem;
|
padding: 1rem 0.5rem 1rem 1rem;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
min-height: calc(100vh - 3.25rem);
|
min-height: calc(100vh - 3.25rem);
|
||||||
max-height: calc(100vh - 3.25rem);
|
max-height: calc(100vh - 3.25rem);
|
||||||
width: 20rem;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@@ -903,8 +903,8 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
|||||||
}
|
}
|
||||||
|
|
||||||
.search {
|
.search {
|
||||||
margin: 0 1rem 0.5rem calc(20rem + 1rem);
|
margin: 0 1rem 0.5rem 20.5rem;
|
||||||
width: calc(100% - calc(20rem + 1rem));
|
width: calc(100% - 20.5rem);
|
||||||
|
|
||||||
.offline {
|
.offline {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
import { ref, onUnmounted, computed } from 'vue'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import RowDisplay from '@/components/RowDisplay.vue'
|
import RowDisplay from '@/components/RowDisplay.vue'
|
||||||
import { list } from '@/helpers/profile.js'
|
import { list } from '@/helpers/profile.js'
|
||||||
@@ -8,11 +8,6 @@ import { useBreadcrumbs } from '@/store/breadcrumbs'
|
|||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { get_search_results } from '@/helpers/cache.js'
|
import { get_search_results } from '@/helpers/cache.js'
|
||||||
import { hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
hide_ads_window(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
const featuredModpacks = ref({})
|
const featuredModpacks = ref({})
|
||||||
const featuredMods = ref({})
|
const featuredMods = ref({})
|
||||||
@@ -47,7 +42,7 @@ const getInstances = async () => {
|
|||||||
return dateB - dateA
|
return dateB - dateA
|
||||||
})
|
})
|
||||||
|
|
||||||
const filters = []
|
let filters = []
|
||||||
for (const instance of recentInstances.value) {
|
for (const instance of recentInstances.value) {
|
||||||
if (instance.linked_data && instance.linked_data.project_id) {
|
if (instance.linked_data && instance.linked_data.project_id) {
|
||||||
filters.push(`NOT"project_id"="${instance.linked_data.project_id}"`)
|
filters.push(`NOT"project_id"="${instance.linked_data.project_id}"`)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'
|
import { onUnmounted, ref, shallowRef } from 'vue'
|
||||||
import GridDisplay from '@/components/GridDisplay.vue'
|
import GridDisplay from '@/components/GridDisplay.vue'
|
||||||
import { list } from '@/helpers/profile.js'
|
import { list } from '@/helpers/profile.js'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
@@ -10,11 +10,6 @@ import { Button } from '@modrinth/ui'
|
|||||||
import { PlusIcon } from '@modrinth/assets'
|
import { PlusIcon } from '@modrinth/assets'
|
||||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||||
import { NewInstanceImage } from '@/assets/icons'
|
import { NewInstanceImage } from '@/assets/icons'
|
||||||
import { hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
hide_ads_window(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { ref, watch, onMounted } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
||||||
import { Card, Slider, DropdownSelect, Toggle, Button } from '@modrinth/ui'
|
import { Card, Slider, DropdownSelect, Toggle, ConfirmModal, Button } from '@modrinth/ui'
|
||||||
import { handleError, useTheming } from '@/store/state'
|
import { handleError, useTheming } from '@/store/state'
|
||||||
import { get, set } from '@/helpers/settings'
|
import { get, set } from '@/helpers/settings'
|
||||||
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
|
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
|
||||||
@@ -13,12 +13,6 @@ import { open } from '@tauri-apps/plugin-dialog'
|
|||||||
import { getOS } from '@/helpers/utils.js'
|
import { getOS } from '@/helpers/utils.js'
|
||||||
import { getVersion } from '@tauri-apps/api/app'
|
import { getVersion } from '@tauri-apps/api/app'
|
||||||
import { get_user, purge_cache_types } from '@/helpers/cache.js'
|
import { get_user, purge_cache_types } from '@/helpers/cache.js'
|
||||||
import { hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
hide_ads_window(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
const pageOptions = ['Home', 'Library']
|
const pageOptions = ['Home', 'Library']
|
||||||
|
|
||||||
@@ -175,12 +169,13 @@ async function purgeCache() {
|
|||||||
Sign in
|
Sign in
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmModalWrapper
|
<ConfirmModal
|
||||||
ref="purgeCacheConfirmModal"
|
ref="purgeCacheConfirmModal"
|
||||||
title="Are you sure you want to purge the cache?"
|
title="Are you sure you want to purge the cache?"
|
||||||
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
|
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
|
||||||
:has-to-type="false"
|
:has-to-type="false"
|
||||||
proceed-label="Purge cache"
|
proceed-label="Purge cache"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
@proceed="purgeCache"
|
@proceed="purgeCache"
|
||||||
/>
|
/>
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
@@ -363,25 +358,6 @@ async function purgeCache() {
|
|||||||
<span class="label__title size-card-header">Privacy</span>
|
<span class="label__title size-card-header">Privacy</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="adjacent-input">
|
|
||||||
<label for="opt-out-analytics">
|
|
||||||
<span class="label__title">Personalized ads</span>
|
|
||||||
<span class="label__description">
|
|
||||||
Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
|
|
||||||
option, you opt out and ads will no longer be shown based on your interests.
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
<Toggle
|
|
||||||
id="opt-out-analytics"
|
|
||||||
:model-value="settings.personalized_ads"
|
|
||||||
:checked="settings.personalized_ads"
|
|
||||||
@update:model-value="
|
|
||||||
(e) => {
|
|
||||||
settings.personalized_ads = e
|
|
||||||
}
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="adjacent-input">
|
<div class="adjacent-input">
|
||||||
<label for="opt-out-analytics">
|
<label for="opt-out-analytics">
|
||||||
<span class="label__title">Telemetry</span>
|
<span class="label__title">Telemetry</span>
|
||||||
@@ -425,14 +401,14 @@ async function purgeCache() {
|
|||||||
<span class="label__title size-card-header">Java settings</span>
|
<span class="label__title size-card-header">Java settings</span>
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<template v-for="javaVersion in [21, 17, 8]" :key="`java-${javaVersion}`">
|
<template v-for="version in [21, 17, 8]">
|
||||||
<label :for="'java-' + javaVersion">
|
<label :for="'java-' + version">
|
||||||
<span class="label__title">Java {{ javaVersion }} location</span>
|
<span class="label__title">Java {{ version }} location</span>
|
||||||
</label>
|
</label>
|
||||||
<JavaSelector
|
<JavaSelector
|
||||||
:id="'java-selector-' + javaVersion"
|
:id="'java-selector-' + version"
|
||||||
v-model="javaVersions[javaVersion]"
|
v-model="javaVersions[version]"
|
||||||
:version="javaVersion"
|
:version="version"
|
||||||
@update:model-value="updateJavaVersion"
|
@update:model-value="updateJavaVersion"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="instance-container">
|
<div class="instance-container">
|
||||||
<div class="side-cards pb-4" @scroll="$refs.promo.scroll()">
|
<div class="side-cards">
|
||||||
<Card class="instance-card" @contextmenu.prevent.stop="handleRightClick">
|
<Card class="instance-card" @contextmenu.prevent.stop="handleRightClick">
|
||||||
<Avatar size="md" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
|
<Avatar size="lg" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
|
||||||
<div class="instance-info">
|
<div class="instance-info">
|
||||||
<h2 class="name">{{ instance.name }}</h2>
|
<h2 class="name">{{ instance.name }}</h2>
|
||||||
<span class="metadata"> {{ instance.loader }} {{ instance.game_version }} </span>
|
<span class="metadata"> {{ instance.loader }} {{ instance.game_version }} </span>
|
||||||
@@ -61,9 +61,9 @@
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
<PromotionWrapper ref="promo" class="mt-4" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
<PromotionWrapper />
|
||||||
<RouterView v-slot="{ Component }">
|
<RouterView v-slot="{ Component }">
|
||||||
<template v-if="Component">
|
<template v-if="Component">
|
||||||
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
||||||
@@ -311,6 +311,7 @@ onUnmounted(() => {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
width: 17rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
@@ -324,13 +325,12 @@ Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.side-cards {
|
.side-cards {
|
||||||
position: fixed;
|
position: absolute;
|
||||||
width: 300px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 1rem;
|
||||||
min-height: calc(100vh - 3.25rem);
|
min-height: calc(100% - 3.25rem);
|
||||||
max-height: calc(100vh - 3.25rem);
|
max-height: calc(100% - 3.25rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
-ms-overflow-style: none;
|
-ms-overflow-style: none;
|
||||||
scrollbar-width: none;
|
scrollbar-width: none;
|
||||||
@@ -374,7 +374,10 @@ Button {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
padding: 1rem;
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
margin-left: 19rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.instance-info {
|
.instance-info {
|
||||||
@@ -448,10 +451,10 @@ Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
margin: 0 1rem 0.5rem 20rem;
|
width: 100%;
|
||||||
width: calc(100% - 20rem);
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
padding: 1rem 1rem 0 0;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</RecycleScroller>
|
</RecycleScroller>
|
||||||
</div>
|
</div>
|
||||||
<ShareModalWrapper
|
<ShareModal
|
||||||
ref="shareModal"
|
ref="shareModal"
|
||||||
header="Share Log"
|
header="Share Log"
|
||||||
share-title="Instance Log"
|
share-title="Instance Log"
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
|
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
|
||||||
import { Button, Card, Checkbox, DropdownSelect } from '@modrinth/ui'
|
import { Button, Card, ShareModal, Checkbox, DropdownSelect } from '@modrinth/ui'
|
||||||
import {
|
import {
|
||||||
delete_logs_by_filename,
|
delete_logs_by_filename,
|
||||||
get_logs,
|
get_logs,
|
||||||
@@ -107,7 +107,6 @@ import { handleError } from '@/store/notifications.js'
|
|||||||
import { ofetch } from 'ofetch'
|
import { ofetch } from 'ofetch'
|
||||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
|
||||||
|
|
||||||
dayjs.extend(isToday)
|
dayjs.extend(isToday)
|
||||||
dayjs.extend(isYesterday)
|
dayjs.extend(isYesterday)
|
||||||
@@ -296,7 +295,7 @@ if (logs.value.length > 1 && !props.playing) {
|
|||||||
|
|
||||||
const deleteLog = async () => {
|
const deleteLog = async () => {
|
||||||
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
||||||
const deleteIndex = selectedLogIndex.value
|
let deleteIndex = selectedLogIndex.value
|
||||||
selectedLogIndex.value = deleteIndex - 1
|
selectedLogIndex.value = deleteIndex - 1
|
||||||
await delete_logs_by_filename(
|
await delete_logs_by_filename(
|
||||||
props.instance.path,
|
props.instance.path,
|
||||||
|
|||||||
@@ -284,7 +284,7 @@
|
|||||||
:link-function="(page) => `?page=${page}`"
|
:link-function="(page) => `?page=${page}`"
|
||||||
@switch-page="switchPage"
|
@switch-page="switchPage"
|
||||||
/>
|
/>
|
||||||
<ModalWrapper ref="deleteWarning" header="Are you sure?">
|
<Modal ref="deleteWarning" header="Are you sure?">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="markdown-body">
|
<div class="markdown-body">
|
||||||
<p>
|
<p>
|
||||||
@@ -302,8 +302,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
<ModalWrapper ref="deleteDisabledWarning" header="Are you sure?">
|
<Modal ref="deleteDisabledWarning" header="Are you sure?">
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="markdown-body">
|
<div class="markdown-body">
|
||||||
<p>
|
<p>
|
||||||
@@ -325,8 +325,8 @@
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
<ShareModalWrapper
|
<ShareModal
|
||||||
ref="shareModal"
|
ref="shareModal"
|
||||||
share-title="Sharing modpack content"
|
share-title="Sharing modpack content"
|
||||||
share-text="Check out the projects I'm using in my modpack!"
|
share-text="Check out the projects I'm using in my modpack!"
|
||||||
@@ -360,6 +360,8 @@ import {
|
|||||||
import {
|
import {
|
||||||
Pagination,
|
Pagination,
|
||||||
DropdownSelect,
|
DropdownSelect,
|
||||||
|
ShareModal,
|
||||||
|
Modal,
|
||||||
Checkbox,
|
Checkbox,
|
||||||
AnimatedLogo,
|
AnimatedLogo,
|
||||||
Avatar,
|
Avatar,
|
||||||
@@ -378,6 +380,7 @@ import {
|
|||||||
} from '@/helpers/profile.js'
|
} from '@/helpers/profile.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
|
import { listen } from '@tauri-apps/api/event'
|
||||||
import { highlightModInProfile } from '@/helpers/utils.js'
|
import { highlightModInProfile } from '@/helpers/utils.js'
|
||||||
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
import { MenuIcon, ToggleIcon, TextInputIcon, AddProjectImage, PackageIcon } from '@/assets/icons'
|
||||||
import ExportModal from '@/components/ui/ExportModal.vue'
|
import ExportModal from '@/components/ui/ExportModal.vue'
|
||||||
@@ -390,9 +393,6 @@ import {
|
|||||||
get_version_many,
|
get_version_many,
|
||||||
} from '@/helpers/cache.js'
|
} from '@/helpers/cache.js'
|
||||||
import { profile_listener } from '@/helpers/events.js'
|
import { profile_listener } from '@/helpers/events.js'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
|
||||||
import { getCurrentWebview } from '@tauri-apps/api/webview'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
instance: {
|
instance: {
|
||||||
@@ -717,7 +717,7 @@ const updateProject = async (mod) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const locks = {}
|
let locks = {}
|
||||||
|
|
||||||
const toggleDisableMod = async (mod) => {
|
const toggleDisableMod = async (mod) => {
|
||||||
// Use mod's id as the key for the lock. If mod doesn't have a unique id, replace `mod.id` with some unique property.
|
// Use mod's id as the key for the lock. If mod doesn't have a unique id, replace `mod.id` with some unique property.
|
||||||
@@ -725,7 +725,7 @@ const toggleDisableMod = async (mod) => {
|
|||||||
locks[mod.id] = ref(null)
|
locks[mod.id] = ref(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
const lock = locks[mod.id]
|
let lock = locks[mod.id]
|
||||||
|
|
||||||
while (lock.value) {
|
while (lock.value) {
|
||||||
await lock.value
|
await lock.value
|
||||||
@@ -784,7 +784,6 @@ const deleteDisabled = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shareNames = async () => {
|
const shareNames = async () => {
|
||||||
console.log(functionValues.value)
|
|
||||||
await shareModal.value.show(functionValues.value.map((x) => x.name).join('\n'))
|
await shareModal.value.show(functionValues.value.map((x) => x.name).join('\n'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -879,10 +878,8 @@ async function refreshProjects() {
|
|||||||
refreshingProjects.value = false
|
refreshingProjects.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const unlisten = await getCurrentWebview().onDragDropEvent(async (event) => {
|
const unlisten = await listen('tauri://file-drop', async (event) => {
|
||||||
if (event.payload.type !== 'drop') return
|
for (const file of event.payload) {
|
||||||
|
|
||||||
for (const file of event.payload.paths) {
|
|
||||||
if (file.endsWith('.mrpack')) continue
|
if (file.endsWith('.mrpack')) continue
|
||||||
await add_project_from_path(props.instance.path, file).catch(handleError)
|
await add_project_from_path(props.instance.path, file).catch(handleError)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<ConfirmModalWrapper
|
<ConfirmModal
|
||||||
ref="modal_confirm"
|
ref="modal_confirm"
|
||||||
title="Are you sure you want to delete this instance?"
|
title="Are you sure you want to delete this instance?"
|
||||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||||
:has-to-type="false"
|
:has-to-type="false"
|
||||||
proceed-label="Delete"
|
proceed-label="Delete"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
@proceed="removeProfile"
|
@proceed="removeProfile"
|
||||||
/>
|
/>
|
||||||
<ModalWrapper ref="modalConfirmUnlock" header="Are you sure you want to unlock this instance?">
|
<Modal
|
||||||
|
ref="modalConfirmUnlock"
|
||||||
|
header="Are you sure you want to unlock this instance?"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
>
|
||||||
<div class="modal-delete">
|
<div class="modal-delete">
|
||||||
<div
|
<div
|
||||||
class="markdown-body"
|
class="markdown-body"
|
||||||
@@ -26,9 +31,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
|
|
||||||
<ModalWrapper ref="modalConfirmUnpair" header="Are you sure you want to unpair this instance?">
|
<Modal
|
||||||
|
ref="modalConfirmUnpair"
|
||||||
|
header="Are you sure you want to unpair this instance?"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
>
|
||||||
<div class="modal-delete">
|
<div class="modal-delete">
|
||||||
<div
|
<div
|
||||||
class="markdown-body"
|
class="markdown-body"
|
||||||
@@ -47,9 +56,13 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
|
|
||||||
<ModalWrapper ref="changeVersionsModal" header="Change instance versions">
|
<Modal
|
||||||
|
ref="changeVersionsModal"
|
||||||
|
header="Change instance versions"
|
||||||
|
:noblur="!themeStore.advancedRendering"
|
||||||
|
>
|
||||||
<div class="change-versions-modal universal-body">
|
<div class="change-versions-modal universal-body">
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<p class="input-label">Loader</p>
|
<p class="input-label">Loader</p>
|
||||||
@@ -93,7 +106,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</Modal>
|
||||||
<section class="card">
|
<section class="card">
|
||||||
<div class="label">
|
<div class="label">
|
||||||
<h3>
|
<h3>
|
||||||
@@ -498,7 +511,18 @@ import {
|
|||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Button, Toggle, Card, Slider, Checkbox, Avatar, Chips, DropdownSelect } from '@modrinth/ui'
|
import {
|
||||||
|
Button,
|
||||||
|
Toggle,
|
||||||
|
ConfirmModal,
|
||||||
|
Card,
|
||||||
|
Slider,
|
||||||
|
Checkbox,
|
||||||
|
Avatar,
|
||||||
|
Modal,
|
||||||
|
Chips,
|
||||||
|
DropdownSelect,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { SwapIcon } from '@/assets/icons'
|
import { SwapIcon } from '@/assets/icons'
|
||||||
|
|
||||||
import { Multiselect } from 'vue-multiselect'
|
import { Multiselect } from 'vue-multiselect'
|
||||||
@@ -522,11 +546,10 @@ import { open } from '@tauri-apps/plugin-dialog'
|
|||||||
import { get_loader_versions } from '@/helpers/metadata.js'
|
import { get_loader_versions } from '@/helpers/metadata.js'
|
||||||
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||||
import { handleError } from '@/store/notifications.js'
|
import { handleError } from '@/store/notifications.js'
|
||||||
|
import { useTheming } from '@/store/theme.js'
|
||||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
||||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
|
||||||
|
|
||||||
const breadcrumbs = useBreadcrumbs()
|
const breadcrumbs = useBreadcrumbs()
|
||||||
|
|
||||||
@@ -547,6 +570,8 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const themeStore = useTheming()
|
||||||
|
|
||||||
const title = ref(props.instance.name)
|
const title = ref(props.instance.name)
|
||||||
const icon = ref(props.instance.icon_path)
|
const icon = ref(props.instance.icon_path)
|
||||||
const groups = ref(props.instance.groups)
|
const groups = ref(props.instance.groups)
|
||||||
@@ -581,7 +606,7 @@ async function setIcon() {
|
|||||||
|
|
||||||
if (!value) return
|
if (!value) return
|
||||||
|
|
||||||
icon.value = value.path ?? value
|
icon.value = value
|
||||||
await edit_icon(props.instance.path, icon.value).catch(handleError)
|
await edit_icon(props.instance.path, icon.value).catch(handleError)
|
||||||
|
|
||||||
trackEvent('InstanceSetIcon')
|
trackEvent('InstanceSetIcon')
|
||||||
@@ -596,12 +621,12 @@ const overrideJavaInstall = ref(!!props.instance.java_path)
|
|||||||
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
||||||
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
||||||
|
|
||||||
const overrideJavaArgs = ref(props.instance.extra_launch_args?.length !== undefined)
|
const overrideJavaArgs = ref(!!props.instance.extra_launch_args)
|
||||||
const javaArgs = ref(
|
const javaArgs = ref(
|
||||||
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
||||||
)
|
)
|
||||||
|
|
||||||
const overrideEnvVars = ref(props.instance.custom_env_vars?.length !== undefined)
|
const overrideEnvVars = ref(!!props.instance.custom_env_vars)
|
||||||
const envVars = ref(
|
const envVars = ref(
|
||||||
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
||||||
.map((x) => x.join('='))
|
.map((x) => x.join('='))
|
||||||
@@ -685,15 +710,19 @@ const editProfileObject = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (overrideJavaArgs.value) {
|
if (overrideJavaArgs.value) {
|
||||||
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
if (javaArgs.value !== '') {
|
||||||
|
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overrideEnvVars.value) {
|
if (overrideEnvVars.value) {
|
||||||
editProfile.custom_env_vars = envVars.value
|
if (envVars.value !== '') {
|
||||||
.trim()
|
editProfile.custom_env_vars = envVars.value
|
||||||
.split(/\s+/)
|
.trim()
|
||||||
.filter(Boolean)
|
.split(/\s+/)
|
||||||
.map((x) => x.split('=').filter(Boolean))
|
.filter(Boolean)
|
||||||
|
.map((x) => x.split('=').filter(Boolean))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (overrideMemorySettings.value) {
|
if (overrideMemorySettings.value) {
|
||||||
@@ -876,7 +905,7 @@ const editing = ref(false)
|
|||||||
async function saveGvLoaderEdits() {
|
async function saveGvLoaderEdits() {
|
||||||
editing.value = true
|
editing.value = true
|
||||||
|
|
||||||
const editProfile = editProfileObject.value
|
let editProfile = editProfileObject.value
|
||||||
editProfile.loader = loader.value
|
editProfile.loader = loader.value
|
||||||
editProfile.game_version = gameVersion.value
|
editProfile.game_version = gameVersion.value
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,14 @@
|
|||||||
</span>
|
</span>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="expandedGalleryItem" class="expanded-image-modal" @click="hideImage">
|
<div v-if="expandedGalleryItem" class="expanded-image-modal" @click="expandedGalleryItem = null">
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<img
|
<img
|
||||||
class="image"
|
class="image"
|
||||||
:class="{ 'zoomed-in': zoomedIn }"
|
:class="{ 'zoomed-in': zoomedIn }"
|
||||||
:src="
|
:src="
|
||||||
expandedGalleryItem.raw_url
|
expandedGalleryItem.url
|
||||||
? expandedGalleryItem.raw_url
|
? expandedGalleryItem.url
|
||||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||||
"
|
"
|
||||||
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
||||||
@@ -45,15 +45,15 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<Button class="close" icon-only @click="hideImage">
|
<Button class="close" icon-only @click="expandedGalleryItem = null">
|
||||||
<XIcon aria-hidden="true" />
|
<XIcon aria-hidden="true" />
|
||||||
</Button>
|
</Button>
|
||||||
<a
|
<a
|
||||||
class="open btn icon-only"
|
class="open btn icon-only"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:href="
|
:href="
|
||||||
expandedGalleryItem.raw_url
|
expandedGalleryItem.url
|
||||||
? expandedGalleryItem.raw_url
|
? expandedGalleryItem.url
|
||||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
@@ -94,7 +94,6 @@ import {
|
|||||||
import { Button, Card } from '@modrinth/ui'
|
import { Button, Card } from '@modrinth/ui'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { trackEvent } from '@/helpers/analytics'
|
import { trackEvent } from '@/helpers/analytics'
|
||||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
project: {
|
project: {
|
||||||
@@ -103,14 +102,9 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const expandedGalleryItem = ref(null)
|
let expandedGalleryItem = ref(null)
|
||||||
const expandedGalleryIndex = ref(0)
|
let expandedGalleryIndex = ref(0)
|
||||||
const zoomedIn = ref(false)
|
let zoomedIn = ref(false)
|
||||||
|
|
||||||
const hideImage = () => {
|
|
||||||
expandedGalleryItem.value = null
|
|
||||||
show_ads_window()
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextImage = () => {
|
const nextImage = () => {
|
||||||
expandedGalleryIndex.value++
|
expandedGalleryIndex.value++
|
||||||
@@ -137,7 +131,6 @@ const previousImage = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const expandImage = (item, index) => {
|
const expandImage = (item, index) => {
|
||||||
hide_ads_window()
|
|
||||||
expandedGalleryItem.value = item
|
expandedGalleryItem.value = item
|
||||||
expandedGalleryIndex.value = index
|
expandedGalleryIndex.value = index
|
||||||
zoomedIn.value = false
|
zoomedIn.value = false
|
||||||
@@ -147,20 +140,6 @@ const expandImage = (item, index) => {
|
|||||||
url: item.url,
|
url: item.url,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function keyListener(e) {
|
|
||||||
if (expandedGalleryItem.value) {
|
|
||||||
e.preventDefault()
|
|
||||||
if (e.key === 'Escape') {
|
|
||||||
hideImage()
|
|
||||||
} else if (e.key === 'ArrowLeft') {
|
|
||||||
previousImage()
|
|
||||||
} else if (e.key === 'ArrowRight') {
|
|
||||||
nextImage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('keypress', keyListener)
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="root-container">
|
<div class="root-container">
|
||||||
<div v-if="data" class="project-sidebar" @scroll="$refs.promo.scroll()">
|
<div v-if="data" class="project-sidebar">
|
||||||
<Card v-if="instance" class="small-instance">
|
<Card v-if="instance" class="small-instance">
|
||||||
<router-link class="instance" :to="`/instance/${encodeURIComponent(instance.path)}`">
|
<router-link class="instance" :to="`/instance/${encodeURIComponent(instance.path)}`">
|
||||||
<Avatar
|
<Avatar
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
</router-link>
|
</router-link>
|
||||||
</Card>
|
</Card>
|
||||||
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
||||||
<Avatar size="md" :src="data.icon_url" />
|
<Avatar size="lg" :src="data.icon_url" />
|
||||||
<div class="instance-info">
|
<div class="instance-info">
|
||||||
<h2 class="name">{{ data.title }}</h2>
|
<h2 class="name">{{ data.title }}</h2>
|
||||||
{{ data.description }}
|
{{ data.description }}
|
||||||
@@ -61,9 +61,7 @@
|
|||||||
Site
|
Site
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
<hr class="card-divider" />
|
||||||
<PromotionWrapper ref="promo" />
|
|
||||||
<Card class="sidebar-card">
|
|
||||||
<div class="stats">
|
<div class="stats">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<DownloadIcon aria-hidden="true" />
|
<DownloadIcon aria-hidden="true" />
|
||||||
@@ -165,6 +163,7 @@
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="data" class="content-container">
|
<div v-if="data" class="content-container">
|
||||||
|
<PromotionWrapper />
|
||||||
<Card class="tabs">
|
<Card class="tabs">
|
||||||
<NavRow
|
<NavRow
|
||||||
v-if="data.gallery.length > 0"
|
v-if="data.gallery.length > 0"
|
||||||
@@ -232,7 +231,15 @@ import {
|
|||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
} from '@modrinth/assets'
|
} from '@modrinth/assets'
|
||||||
import { Categories, EnvironmentIndicator, Card, Avatar, Button, NavRow } from '@modrinth/ui'
|
import {
|
||||||
|
Categories,
|
||||||
|
EnvironmentIndicator,
|
||||||
|
Card,
|
||||||
|
Avatar,
|
||||||
|
Button,
|
||||||
|
Promotion,
|
||||||
|
NavRow,
|
||||||
|
} from '@modrinth/ui'
|
||||||
import { formatNumber } from '@modrinth/utils'
|
import { formatNumber } from '@modrinth/utils'
|
||||||
import {
|
import {
|
||||||
BuyMeACoffeeIcon,
|
BuyMeACoffeeIcon,
|
||||||
@@ -253,7 +260,7 @@ import { handleError } from '@/store/notifications.js'
|
|||||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||||
import { install as installVersion } from '@/store/install.js'
|
import { install as installVersion } from '@/store/install.js'
|
||||||
import { get_project, get_team, get_version_many } from '@/helpers/cache.js'
|
import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js'
|
||||||
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||||
|
|
||||||
dayjs.extend(relativeTime)
|
dayjs.extend(relativeTime)
|
||||||
@@ -302,13 +309,11 @@ async function fetchProjectData() {
|
|||||||
|
|
||||||
await fetchProjectData()
|
await fetchProjectData()
|
||||||
|
|
||||||
const promo = ref(null)
|
|
||||||
watch(
|
watch(
|
||||||
() => route.params.id,
|
() => route.params.id,
|
||||||
async () => {
|
async () => {
|
||||||
if (route.params.id && route.path.startsWith('/project')) {
|
if (route.params.id && route.path.startsWith('/project')) {
|
||||||
await fetchProjectData()
|
await fetchProjectData()
|
||||||
promo.value.scroll()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@@ -372,7 +377,7 @@ const handleOptionsClick = (args) => {
|
|||||||
|
|
||||||
.project-sidebar {
|
.project-sidebar {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
width: calc(300px + 1.5rem);
|
width: 20rem;
|
||||||
min-height: calc(100vh - 3.25rem);
|
min-height: calc(100vh - 3.25rem);
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
max-height: calc(100vh - 3.25rem);
|
max-height: calc(100vh - 3.25rem);
|
||||||
@@ -398,7 +403,7 @@ const handleOptionsClick = (args) => {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin-left: calc(300px + 1rem);
|
margin-left: 19.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-group {
|
.button-group {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowJs": true,
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "preserve",
|
|
||||||
|
|
||||||
"strict": true,
|
|
||||||
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,12 @@
|
|||||||
{
|
{
|
||||||
"files": [],
|
"compilerOptions": {
|
||||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
},
|
||||||
|
"target": "ESNext"
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2022",
|
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
"strict": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@@ -42,8 +42,8 @@ export default defineConfig({
|
|||||||
port: 1420,
|
port: 1420,
|
||||||
strictPort: true,
|
strictPort: true,
|
||||||
},
|
},
|
||||||
// to make use of `TAURI_ENV_DEBUG` and other env variables
|
// to make use of `TAURI_DEBUG` and other env variables
|
||||||
// https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands
|
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||||
envPrefix: ['VITE_', 'TAURI_'],
|
envPrefix: ['VITE_', 'TAURI_'],
|
||||||
build: {
|
build: {
|
||||||
// Tauri supports es2021
|
// Tauri supports es2021
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ theseus = { path = "../../packages/app-lib", features = ["cli"] }
|
|||||||
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
tauri = "2.0.0-rc.4"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
|
|||||||
+4
-8
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus_gui"
|
name = "theseus_gui"
|
||||||
version = "0.8.9"
|
version = "0.8.3-1"
|
||||||
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
||||||
license = "GPL-3.0-only"
|
license = "GPL-3.0-only"
|
||||||
repository = "https://github.com/modrinth/code/apps/app/"
|
repository = "https://github.com/modrinth/code/apps/app/"
|
||||||
@@ -16,14 +16,13 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
|
||||||
tauri = { version = "2.0.0-rc", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
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-window-state = "2.0.0-rc"
|
||||||
tauri-plugin-deep-link = "2.0.0-rc"
|
tauri-plugin-deep-link = "2.0.0-rc"
|
||||||
tauri-plugin-os = "2.0.0-rc"
|
tauri-plugin-os = "2.0.0-rc"
|
||||||
tauri-plugin-shell = "2.0.0-rc"
|
tauri-plugin-shell = "2.0.0-rc"
|
||||||
tauri-plugin-dialog = "2.0.0-rc"
|
tauri-plugin-dialog = "2.0.0-rc"
|
||||||
tauri-plugin-updater = { version = "2.0.0-rc" }
|
tauri-plugin-updater = { version = "2.0.0-rc.1", optional = true }
|
||||||
tauri-plugin-single-instance = { version = "2.0.0-rc" }
|
|
||||||
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
@@ -58,9 +57,6 @@ cocoa = "0.25.0"
|
|||||||
objc = "0.2.7"
|
objc = "0.2.7"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
|
||||||
tauri-plugin-updater = { version = "2.0.0-rc", optional = true, features = ["native-tls-vendored", "zip"], default-features = false }
|
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL
|
||||||
@@ -68,4 +64,4 @@ default = ["custom-protocol"]
|
|||||||
# this feature is used for production builds where `devPath` points to the filesystem
|
# this feature is used for production builds where `devPath` points to the filesystem
|
||||||
# DO NOT remove this
|
# DO NOT remove this
|
||||||
custom-protocol = ["tauri/custom-protocol"]
|
custom-protocol = ["tauri/custom-protocol"]
|
||||||
updater = []
|
updater = ["dep:tauri-plugin-updater"]
|
||||||
+1
-1
@@ -15,7 +15,7 @@ Before you begin, ensure you have the following installed on your machine:
|
|||||||
- [Node.js](https://nodejs.org/en/)
|
- [Node.js](https://nodejs.org/en/)
|
||||||
- [pnpm](https://pnpm.io/)
|
- [pnpm](https://pnpm.io/)
|
||||||
- [Rust](https://www.rust-lang.org/tools/install)
|
- [Rust](https://www.rust-lang.org/tools/install)
|
||||||
- [Tauri](https://v2.tauri.app/start/prerequisites/)
|
- [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites/#installing)
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
|
|||||||
+1
-17
@@ -62,7 +62,7 @@ fn main() {
|
|||||||
InlinedPlugin::new()
|
InlinedPlugin::new()
|
||||||
.commands(&[
|
.commands(&[
|
||||||
"get_java_versions",
|
"get_java_versions",
|
||||||
"set_java_version",
|
"set_java_versions",
|
||||||
"jre_find_filtered_jres",
|
"jre_find_filtered_jres",
|
||||||
"jre_get_jre",
|
"jre_get_jre",
|
||||||
"jre_test_jre",
|
"jre_test_jre",
|
||||||
@@ -217,22 +217,6 @@ fn main() {
|
|||||||
.default_permission(
|
.default_permission(
|
||||||
DefaultPermissionRule::AllowAllCommands,
|
DefaultPermissionRule::AllowAllCommands,
|
||||||
),
|
),
|
||||||
)
|
|
||||||
.plugin(
|
|
||||||
"ads",
|
|
||||||
InlinedPlugin::new()
|
|
||||||
.commands(&[
|
|
||||||
"init_ads_window",
|
|
||||||
"hide_ads_window",
|
|
||||||
"scroll_ads_window",
|
|
||||||
"show_ads_window",
|
|
||||||
"record_ads_click",
|
|
||||||
"open_link",
|
|
||||||
"get_ads_personalization",
|
|
||||||
])
|
|
||||||
.default_permission(
|
|
||||||
DefaultPermissionRule::AllowAllCommands,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.expect("Failed to run tauri-build");
|
.expect("Failed to run tauri-build");
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"identifier": "ads",
|
|
||||||
"description": "",
|
|
||||||
"local": false,
|
|
||||||
"remote": {
|
|
||||||
"urls": ["https://modrinth.com/*", "http://localhost:3000/*"]
|
|
||||||
},
|
|
||||||
"webviews": [
|
|
||||||
"ads-window"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"ads:default"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -35,7 +35,6 @@
|
|||||||
"cache:default",
|
"cache:default",
|
||||||
"settings:default",
|
"settings:default",
|
||||||
"tags:default",
|
"tags:default",
|
||||||
"utils:default",
|
"utils:default"
|
||||||
"ads:default"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
{
|
|
||||||
"identifier": "updater",
|
|
||||||
"description": "",
|
|
||||||
"local": true,
|
|
||||||
"windows": [
|
|
||||||
"main"
|
|
||||||
],
|
|
||||||
"permissions": [
|
|
||||||
"updater:default"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
{"ads":{"default_permission":{"identifier":"default","description":"Default plugin permissions.","permissions":["allow-init-ads-window","allow-hide-ads-window","allow-scroll-ads-window","allow-show-ads-window","allow-record-ads-click","allow-open-link","allow-get-ads-personalization"]},"permissions":{"allow-get-ads-personalization":{"identifier":"allow-get-ads-personalization","description":"Enables the get_ads_personalization command without any pre-configured scope.","commands":{"allow":["get_ads_personalization"],"deny":[]}},"allow-hide-ads-window":{"identifier":"allow-hide-ads-window","description":"Enables the hide_ads_window command without any pre-configured scope.","commands":{"allow":["hide_ads_window"],"deny":[]}},"allow-init-ads-window":{"identifier":"allow-init-ads-window","description":"Enables the init_ads_window command without any pre-configured scope.","commands":{"allow":["init_ads_window"],"deny":[]}},"allow-open-link":{"identifier":"allow-open-link","description":"Enables the open_link command without any pre-configured scope.","commands":{"allow":["open_link"],"deny":[]}},"allow-record-ads-click":{"identifier":"allow-record-ads-click","description":"Enables the record_ads_click command without any pre-configured scope.","commands":{"allow":["record_ads_click"],"deny":[]}},"allow-scroll-ads-window":{"identifier":"allow-scroll-ads-window","description":"Enables the scroll_ads_window command without any pre-configured scope.","commands":{"allow":["scroll_ads_window"],"deny":[]}},"allow-show-ads-window":{"identifier":"allow-show-ads-window","description":"Enables the show_ads_window command without any pre-configured scope.","commands":{"allow":["show_ads_window"],"deny":[]}},"deny-get-ads-personalization":{"identifier":"deny-get-ads-personalization","description":"Denies the get_ads_personalization command without any pre-configured scope.","commands":{"allow":[],"deny":["get_ads_personalization"]}},"deny-hide-ads-window":{"identifier":"deny-hide-ads-window","description":"Denies the hide_ads_window command without any pre-configured scope.","commands":{"allow":[],"deny":["hide_ads_window"]}},"deny-init-ads-window":{"identifier":"deny-init-ads-window","description":"Denies the init_ads_window command without any pre-configured scope.","commands":{"allow":[],"deny":["init_ads_window"]}},"deny-open-link":{"identifier":"deny-open-link","description":"Denies the open_link command without any pre-configured scope.","commands":{"allow":[],"deny":["open_link"]}},"deny-record-ads-click":{"identifier":"deny-record-ads-click","description":"Denies the record_ads_click command without any pre-configured scope.","commands":{"allow":[],"deny":["record_ads_click"]}},"deny-scroll-ads-window":{"identifier":"deny-scroll-ads-window","description":"Denies the scroll_ads_window command without any pre-configured scope.","commands":{"allow":[],"deny":["scroll_ads_window"]}},"deny-show-ads-window":{"identifier":"deny-show-ads-window","description":"Denies the show_ads_window command without any pre-configured scope.","commands":{"allow":[],"deny":["show_ads_window"]}}},"permission_sets":{},"global_scope_schema":null},"auth":{"default_permission":{"identifier":"default","description":"Default plugin permissions.","permissions":["allow-login","allow-remove-user","allow-get-default-user","allow-set-default-user","allow-get-users"]},"permissions":{"allow-get-default-user":{"identifier":"allow-get-default-user","description":"Enables the get_default_user command without any pre-configured scope.","commands":{"allow":["get_default_user"],"deny":[]}},"allow-get-users":{"identifier":"allow-get-users","description":"Enables the get_users command without any pre-configured scope.","commands":{"allow":["get_users"],"deny":[]}},"allow-login":{"identifier":"allow-login","description":"Enables the login command without any pre-configured scope.","commands":{"allow":["login"],"deny":[]}},"allow-remove-user":{"identifier":"allow-remove-user","description":"Enables the remove_user command without any pre-configured scope.","commands":{"allow":["remove_user"],"deny":[]}},"allow-set-default-user":{"identifier":"allow-set-default-user","description":"Enables the set_default_user command without any pre-configured scope.","commands":{"allow":["set_default_user"],"deny":[]}},"deny-get-default-user":{"identifier":"deny-get-default-user","description":"Denies the get_default_user command without any pre-configured scope.","commands":{"allow":[],"deny":["get_default_user"]}},"deny-get-users":{"identifier":"deny-get-users","description":"Denies the get_users command without any pre-configured scope.","commands":{"allow":[],"deny":["get_users"]}},"deny-login":{"identifier":"deny-login","description":"Denies the login command without any pre-configured scope.","commands":{"allow":[],"deny":["login"]}},"deny-remove-user":{"identifier":"deny-remove-user","description":"Denies the remove_user command without any pre-configured scope.","commLine truncated
|
{"auth":{"default_permission":{"identifier":"default","description":"Default plugin permissions.","permissions":["allow-login","allow-remove-user","allow-get-default-user","allow-set-default-user","allow-get-users"]},"permissions":{"allow-get-default-user":{"identifier":"allow-get-default-user","description":"Enables the get_default_user command without any pre-configured scope.","commands":{"allow":["get_default_user"],"deny":[]}},"allow-get-users":{"identifier":"allow-get-users","description":"Enables the get_users command without any pre-configured scope.","commands":{"allow":["get_users"],"deny":[]}},"allow-login":{"identifier":"allow-login","description":"Enables the login command without any pre-configured scope.","commands":{"allow":["login"],"deny":[]}},"allow-remove-user":{"identifier":"allow-remove-user","description":"Enables the remove_user command without any pre-configured scope.","commands":{"allow":["remove_user"],"deny":[]}},"allow-set-default-user":{"identifier":"allow-set-default-user","description":"Enables the set_default_user command without any pre-configured scope.","commands":{"allow":["set_default_user"],"deny":[]}},"deny-get-default-user":{"identifier":"deny-get-default-user","description":"Denies the get_default_user command without any pre-configured scope.","commands":{"allow":[],"deny":["get_default_user"]}},"deny-get-users":{"identifier":"deny-get-users","description":"Denies the get_users command without any pre-configured scope.","commands":{"allow":[],"deny":["get_users"]}},"deny-login":{"identifier":"deny-login","description":"Denies the login command without any pre-configured scope.","commands":{"allow":[],"deny":["login"]}},"deny-remove-user":{"identifier":"deny-remove-user","description":"Denies the remove_user command without any pre-configured scope.","commands":{"allow":[],"deny":["remove_user"]}},"deny-set-default-user":{"identifier":"deny-set-default-user","description":"Denies the set_default_user command without any pre-configured scope.","commands":{"allow":[],"deny":["set_default_user"]}}},"permission_sets":{},"global_scope_schema":null},"cache":{"default_permission":{"identifier":"default","description":"Default plugin permissions.","permissions":["allow-get-project","allow-get-project-many","allow-get-version","allow-get-version-many","allow-get-user","allow-get-user-many","allow-get-team","allow-get-team-many","allow-get-organization","allow-get-organization-many","allow-get-search-results","allow-get-search-results-many","allow-purge-cache-types"]},"permissions":{"allow-get-organization":{"identifier":"allow-get-organization","description":"Enables the get_organization command without any pre-configured scope.","commands":{"allow":["get_organization"],"deny":[]}},"allow-get-organization-many":{"identifier":"allow-get-organization-many","description":"Enables the get_organization_many command without any pre-configured scope.","commands":{"allow":["get_organization_many"],"deny":[]}},"allow-get-project":{"identifier":"allow-get-project","description":"Enables the get_project command without any pre-configured scope.","commands":{"allow":["get_project"],"deny":[]}},"allow-get-project-many":{"identifier":"allow-get-project-many","description":"Enables the get_project_many command without any pre-configured scope.","commands":{"allow":["get_project_many"],"deny":[]}},"allow-get-search-results":{"identifier":"allow-get-search-results","description":"Enables the get_search_results command without any pre-configured scope.","commands":{"allow":["get_search_results"],"deny":[]}},"allow-get-search-results-many":{"identifier":"allow-get-search-results-many","description":"Enables the get_search_results_many command without any pre-configured scope.","commands":{"allow":["get_search_results_many"],"deny":[]}},"allow-get-team":{"identifier":"allow-get-team","description":"Enables the get_team command without any pre-configured scope.","commands":{"allow":["get_team"],"deny":[]}},"allow-get-team-many":{"identifier":"allow-get-team-many","description":"Enables the get_team_many command without any pre-configured scope.","commands":{"allow":["get_team_many"],"deny":[]}},"allow-get-user":{"identifier":"allow-get-user","description":"Enables the get_user command without any pre-configured scope.","commands":{"allow":["get_user"],"deny":[]}},"allow-get-user-many":{"identifier":"allow-get-user-many","description":"Enables the get_user_many command without any pre-configured scope.","commands":{"allow":["get_user_many"],"deny":[]}},"allow-get-version":{"identifier":"allow-get-version","description":"Enables the get_version command without any pre-configured scope.","commands":{"allow":["get_version"],"deny":[]}},"allow-get-version-many":{"identifier":"allow-get-version-many","description":"Enables the get_version_many command without any pre-configured scope.","commands":{"allow":["get_version_many"],"deny":[]}},"allow-purge-cache-types":{"identifier":"allow-purge-cache-typLine truncated
|
||||||
@@ -1 +1 @@
|
|||||||
{"ads":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
{"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"]}}
|
||||||
+2262
-1423
File diff suppressed because it is too large.
Load diff
@@ -299,69 +299,6 @@
|
|||||||
},
|
},
|
||||||
"Identifier": {
|
"Identifier": {
|
||||||
"oneOf": [
|
"oneOf": [
|
||||||
{
|
|
||||||
"description": "ads:default -> Default plugin permissions.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:default"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:allow-hide-ads-window -> Enables the hide_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:allow-hide-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:allow-init-ads-window -> Enables the init_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:allow-init-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:allow-scroll-ads-window -> Enables the scroll_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:allow-scroll-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:allow-show-ads-window -> Enables the show_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:allow-show-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:deny-hide-ads-window -> Denies the hide_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:deny-hide-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:deny-init-ads-window -> Denies the init_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:deny-init-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:deny-scroll-ads-window -> Denies the scroll_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:deny-scroll-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "ads:deny-show-ads-window -> Denies the show_ads_window command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"ads:deny-show-ads-window"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"description": "auth:default -> Default plugin permissions.",
|
"description": "auth:default -> Default plugin permissions.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -2848,10 +2785,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
|
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"jre:allow-set-java-version"
|
"jre:allow-set-java-versions"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -2897,10 +2834,10 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
|
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": [
|
"enum": [
|
||||||
"jre:deny-set-java-version"
|
"jre:deny-set-java-versions"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
+2262
-1423
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,346 @@
|
|||||||
|
<?if $(sys.BUILDARCH)="x86"?>
|
||||||
|
<?define Win64 = "no" ?>
|
||||||
|
<?define PlatformProgramFilesFolder = "ProgramFilesFolder" ?>
|
||||||
|
<?elseif $(sys.BUILDARCH)="x64"?>
|
||||||
|
<?define Win64 = "yes" ?>
|
||||||
|
<?define PlatformProgramFilesFolder = "ProgramFiles64Folder" ?>
|
||||||
|
<?else?>
|
||||||
|
<?error Unsupported value of sys.BUILDARCH=$(sys.BUILDARCH)?>
|
||||||
|
<?endif?>
|
||||||
|
|
||||||
|
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
|
||||||
|
<Product
|
||||||
|
Id="*"
|
||||||
|
Name="{{product_name}}"
|
||||||
|
UpgradeCode="{{upgrade_code}}"
|
||||||
|
Language="!(loc.TauriLanguage)"
|
||||||
|
Manufacturer="{{manufacturer}}"
|
||||||
|
Version="{{version}}">
|
||||||
|
|
||||||
|
<Package Id="*"
|
||||||
|
Keywords="Installer"
|
||||||
|
InstallerVersion="450"
|
||||||
|
Languages="0"
|
||||||
|
Compressed="yes"
|
||||||
|
InstallScope="perMachine"
|
||||||
|
SummaryCodepage="!(loc.TauriCodepage)"/>
|
||||||
|
|
||||||
|
<!-- https://docs.microsoft.com/en-us/windows/win32/msi/reinstallmode -->
|
||||||
|
<!-- reinstall all files; rewrite all registry entries; reinstall all shortcuts -->
|
||||||
|
<Property Id="REINSTALLMODE" Value="amus" />
|
||||||
|
|
||||||
|
{{#if allow_downgrades}}
|
||||||
|
<MajorUpgrade Schedule="afterInstallInitialize" AllowDowngrades="yes" />
|
||||||
|
{{else}}
|
||||||
|
<MajorUpgrade Schedule="afterInstallInitialize" DowngradeErrorMessage="!(loc.DowngradeErrorMessage)" AllowSameVersionUpgrades="yes" />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<RemoveShortcuts>Installed AND NOT UPGRADINGPRODUCTCODE</RemoveShortcuts>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
|
||||||
|
<Media Id="1" Cabinet="app.cab" EmbedCab="yes" />
|
||||||
|
|
||||||
|
{{#if banner_path}}
|
||||||
|
<WixVariable Id="WixUIBannerBmp" Value="{{banner_path}}" />
|
||||||
|
{{/if}}
|
||||||
|
{{#if dialog_image_path}}
|
||||||
|
<WixVariable Id="WixUIDialogBmp" Value="{{dialog_image_path}}" />
|
||||||
|
{{/if}}
|
||||||
|
{{#if license}}
|
||||||
|
<WixVariable Id="WixUILicenseRtf" Value="{{license}}" />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<Icon Id="ProductIcon" SourceFile="{{icon_path}}"/>
|
||||||
|
<Property Id="ARPPRODUCTICON" Value="ProductIcon" />
|
||||||
|
<Property Id="ARPNOREPAIR" Value="yes" Secure="yes" /> <!-- Remove repair -->
|
||||||
|
<SetProperty Id="ARPNOMODIFY" Value="1" After="InstallValidate" Sequence="execute"/>
|
||||||
|
|
||||||
|
<!-- initialize with previous InstallDir -->
|
||||||
|
<Property Id="INSTALLDIR">
|
||||||
|
<RegistrySearch Id="PrevInstallDirReg" Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="InstallDir" Type="raw"/>
|
||||||
|
</Property>
|
||||||
|
|
||||||
|
<!-- launch app checkbox -->
|
||||||
|
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="!(loc.LaunchApp)" />
|
||||||
|
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1"/>
|
||||||
|
<Property Id="WixShellExecTarget" Value="[!Path]" />
|
||||||
|
<CustomAction Id="LaunchApplication" BinaryKey="WixCA" DllEntry="WixShellExec" Impersonate="yes" />
|
||||||
|
|
||||||
|
<UI>
|
||||||
|
<!-- launch app checkbox -->
|
||||||
|
<Publish Dialog="ExitDialog" Control="Finish" Event="DoAction" Value="LaunchApplication">WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed</Publish>
|
||||||
|
|
||||||
|
<Property Id="WIXUI_INSTALLDIR" Value="INSTALLDIR" />
|
||||||
|
|
||||||
|
{{#unless license}}
|
||||||
|
<!-- Skip license dialog -->
|
||||||
|
<Publish Dialog="WelcomeDlg"
|
||||||
|
Control="Next"
|
||||||
|
Event="NewDialog"
|
||||||
|
Value="InstallDirDlg"
|
||||||
|
Order="2">1</Publish>
|
||||||
|
<Publish Dialog="InstallDirDlg"
|
||||||
|
Control="Back"
|
||||||
|
Event="NewDialog"
|
||||||
|
Value="WelcomeDlg"
|
||||||
|
Order="2">1</Publish>
|
||||||
|
{{/unless}}
|
||||||
|
</UI>
|
||||||
|
|
||||||
|
<UIRef Id="WixUI_InstallDir" />
|
||||||
|
|
||||||
|
<Directory Id="TARGETDIR" Name="SourceDir">
|
||||||
|
<Directory Id="DesktopFolder" Name="Desktop">
|
||||||
|
<Component Id="ApplicationShortcutDesktop" Guid="*">
|
||||||
|
<Shortcut Id="ApplicationDesktopShortcut" Name="{{product_name}}" Description="Runs {{product_name}}" Target="[!Path]" WorkingDirectory="INSTALLDIR" />
|
||||||
|
<RemoveFolder Id="DesktopFolder" On="uninstall" />
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Desktop Shortcut" Type="integer" Value="1" KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="$(var.PlatformProgramFilesFolder)" Name="PFiles">
|
||||||
|
<Directory Id="INSTALLDIR" Name="{{product_name}}"/>
|
||||||
|
</Directory>
|
||||||
|
<Directory Id="ProgramMenuFolder">
|
||||||
|
<Directory Id="ApplicationProgramsFolder" Name="{{product_name}}"/>
|
||||||
|
</Directory>
|
||||||
|
</Directory>
|
||||||
|
|
||||||
|
<DirectoryRef Id="INSTALLDIR">
|
||||||
|
<Component Id="RegistryEntries" Guid="*">
|
||||||
|
<RegistryKey Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}">
|
||||||
|
<RegistryValue Name="InstallDir" Type="string" Value="[INSTALLDIR]" KeyPath="yes" />
|
||||||
|
</RegistryKey>
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<Component Id="Path" Guid="{{path_component_guid}}" Win64="$(var.Win64)">
|
||||||
|
<File Id="Path" Source="{{app_exe_source}}" KeyPath="yes" Checksum="yes"/>
|
||||||
|
<!-- THESEUS -->
|
||||||
|
<ProgId Id="theseus.mrpack.Document" Description="Modrinth File">
|
||||||
|
<Extension Id="mrpack" ContentType="application/mrpack">
|
||||||
|
<!-- no flags on argument, so we can hijack deep link library-->
|
||||||
|
<Verb Id="open" Command="Open" TargetFile="Path" Argument=""%1"" />
|
||||||
|
</Extension>
|
||||||
|
</ProgId>
|
||||||
|
<!-- /THESEUS -->
|
||||||
|
</Component>
|
||||||
|
{{#each binaries as |bin| ~}}
|
||||||
|
<Component Id="{{ bin.id }}" Guid="{{bin.guid}}" Win64="$(var.Win64)">
|
||||||
|
<File Id="Bin_{{ bin.id }}" Source="{{bin.path}}" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
{{/each~}}
|
||||||
|
{{#if enable_elevated_update_task}}
|
||||||
|
<Component Id="UpdateTask" Guid="C492327D-9720-4CD5-8DB8-F09082AF44BE" Win64="$(var.Win64)">
|
||||||
|
<File Id="UpdateTask" Source="update.xml" KeyPath="yes" Checksum="yes"/>
|
||||||
|
</Component>
|
||||||
|
<Component Id="UpdateTaskInstaller" Guid="011F25ED-9BE3-50A7-9E9B-3519ED2B9932" Win64="$(var.Win64)">
|
||||||
|
<File Id="UpdateTaskInstaller" Source="install-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||||
|
</Component>
|
||||||
|
<Component Id="UpdateTaskUninstaller" Guid="D4F6CC3F-32DC-5FD0-95E8-782FFD7BBCE1" Win64="$(var.Win64)">
|
||||||
|
<File Id="UpdateTaskUninstaller" Source="uninstall-task.ps1" KeyPath="yes" Checksum="yes"/>
|
||||||
|
</Component>
|
||||||
|
{{/if}}
|
||||||
|
{{resources}}
|
||||||
|
<Component Id="CMP_UninstallShortcut" Guid="*">
|
||||||
|
|
||||||
|
<Shortcut Id="UninstallShortcut"
|
||||||
|
Name="Uninstall {{product_name}}"
|
||||||
|
Description="Uninstalls {{product_name}}"
|
||||||
|
Target="[System64Folder]msiexec.exe"
|
||||||
|
Arguments="/x [ProductCode]" />
|
||||||
|
|
||||||
|
<RemoveFolder Id="INSTALLDIR"
|
||||||
|
On="uninstall" />
|
||||||
|
|
||||||
|
<RegistryValue Root="HKCU"
|
||||||
|
Key="Software\\{{manufacturer}}\\{{product_name}}"
|
||||||
|
Name="Uninstaller Shortcut"
|
||||||
|
Type="integer"
|
||||||
|
Value="1"
|
||||||
|
KeyPath="yes" />
|
||||||
|
</Component>
|
||||||
|
|
||||||
|
<!-- THESEUS -->
|
||||||
|
<Component Id="FileTypeAssociationsReg" Guid="*">
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationDescription" Value="theseus" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationIcon" Value="[INSTALLDIR]theseus,0" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities" Name="ApplicationName" Value="theseus" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\DefaultIcon" Value="[INSTALLDIR]theseus,1" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\FileAssociations" Name=".mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\MIMEAssociations" Name="application/mrpack" Value="theseus.mrpack.Document" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\modrinth\theseus\Capabilities\shell\Open\command" Value=""[INSTALLDIR]theseus" -e "%1"" Type="string" />
|
||||||
|
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\RegisteredApplications" Name="theseus" Value="SOFTWARE\modrinth\theseus\Capabilities" Type="string" KeyPath="yes" />
|
||||||
|
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\theseus.mrpack.Document" Name="MRPACK File" Value="Modrinth Modpack Installer" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack" Name="Content Type" Value="application/mrpack" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithList\theseus" Value="" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\.mrpack\OpenWithProgids" Name="theseus.mrpack.Document" Value="" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\SupportedTypes" Name=".mrpack" Value="" Type="string" />
|
||||||
|
<RegistryValue Root="HKLM" Key="SOFTWARE\Classes\Applications\mrpack\shell\open" Name="FriendlyAppName" Value="theseus" Type="string" />
|
||||||
|
</Component>
|
||||||
|
<!-- /THESEUS -->
|
||||||
|
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<DirectoryRef Id="ApplicationProgramsFolder">
|
||||||
|
<Component Id="ApplicationShortcut" Guid="*">
|
||||||
|
<Shortcut Id="ApplicationStartMenuShortcut"
|
||||||
|
Name="{{product_name}}"
|
||||||
|
Description="Runs {{product_name}}"
|
||||||
|
Target="[!Path]"
|
||||||
|
Icon="ProductIcon"
|
||||||
|
WorkingDirectory="INSTALLDIR">
|
||||||
|
<ShortcutProperty Key="System.AppUserModel.ID" Value="{{bundle_id}}"/>
|
||||||
|
</Shortcut>
|
||||||
|
<RemoveFolder Id="ApplicationProgramsFolder" On="uninstall"/>
|
||||||
|
<RegistryValue Root="HKCU" Key="Software\\{{manufacturer}}\\{{product_name}}" Name="Start Menu Shortcut" Type="integer" Value="1" KeyPath="yes"/>
|
||||||
|
</Component>
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
{{#each merge_modules as |msm| ~}}
|
||||||
|
<DirectoryRef Id="TARGETDIR">
|
||||||
|
<Merge Id="{{ msm.name }}" SourceFile="{{ msm.path }}" DiskId="1" Language="!(loc.TauriLanguage)" />
|
||||||
|
</DirectoryRef>
|
||||||
|
|
||||||
|
<Feature Id="{{ msm.name }}" Title="{{ msm.name }}" AllowAdvertise="no" Display="hidden" Level="1">
|
||||||
|
<MergeRef Id="{{ msm.name }}"/>
|
||||||
|
</Feature>
|
||||||
|
{{/each~}}
|
||||||
|
|
||||||
|
<Feature
|
||||||
|
Id="MainProgram"
|
||||||
|
Title="Application"
|
||||||
|
Description="!(loc.InstallAppFeature)"
|
||||||
|
Level="1"
|
||||||
|
ConfigurableDirectory="INSTALLDIR"
|
||||||
|
AllowAdvertise="no"
|
||||||
|
Display="expand"
|
||||||
|
Absent="disallow">
|
||||||
|
|
||||||
|
<ComponentRef Id="RegistryEntries"/>
|
||||||
|
|
||||||
|
<!-- THESEUS -->
|
||||||
|
<ComponentRef Id="FileTypeAssociationsReg" />
|
||||||
|
<!-- /THESEUS -->
|
||||||
|
|
||||||
|
{{#each resource_file_ids as |resource_file_id| ~}}
|
||||||
|
<ComponentRef Id="{{ resource_file_id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
|
||||||
|
{{#if enable_elevated_update_task}}
|
||||||
|
<ComponentRef Id="UpdateTask" />
|
||||||
|
<ComponentRef Id="UpdateTaskInstaller" />
|
||||||
|
<ComponentRef Id="UpdateTaskUninstaller" />
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<Feature Id="ShortcutsFeature"
|
||||||
|
Title="Shortcuts"
|
||||||
|
Level="1">
|
||||||
|
<ComponentRef Id="Path"/>
|
||||||
|
<ComponentRef Id="CMP_UninstallShortcut" />
|
||||||
|
<ComponentRef Id="ApplicationShortcut" />
|
||||||
|
<ComponentRef Id="ApplicationShortcutDesktop" />
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<Feature
|
||||||
|
Id="Environment"
|
||||||
|
Title="PATH Environment Variable"
|
||||||
|
Description="!(loc.PathEnvVarFeature)"
|
||||||
|
Level="1"
|
||||||
|
Absent="allow">
|
||||||
|
<ComponentRef Id="Path"/>
|
||||||
|
{{#each binaries as |bin| ~}}
|
||||||
|
<ComponentRef Id="{{ bin.id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
</Feature>
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
<Feature Id="External" AllowAdvertise="no" Absent="disallow">
|
||||||
|
{{#each component_group_refs as |id| ~}}
|
||||||
|
<ComponentGroupRef Id="{{ id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
{{#each component_refs as |id| ~}}
|
||||||
|
<ComponentRef Id="{{ id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
{{#each feature_group_refs as |id| ~}}
|
||||||
|
<FeatureGroupRef Id="{{ id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
{{#each feature_refs as |id| ~}}
|
||||||
|
<FeatureRef Id="{{ id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
{{#each merge_refs as |id| ~}}
|
||||||
|
<MergeRef Id="{{ id }}"/>
|
||||||
|
{{/each~}}
|
||||||
|
</Feature>
|
||||||
|
|
||||||
|
{{#if install_webview}}
|
||||||
|
<!-- WebView2 -->
|
||||||
|
<Property Id="WVRTINSTALLED">
|
||||||
|
<RegistrySearch Id="WVRTInstalledSystem" Root="HKLM" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw" Win64="no" />
|
||||||
|
<RegistrySearch Id="WVRTInstalledUser" Root="HKCU" Key="SOFTWARE\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}" Name="pv" Type="raw"/>
|
||||||
|
</Property>
|
||||||
|
|
||||||
|
{{#if download_bootstrapper}}
|
||||||
|
<CustomAction Id='DownloadAndInvokeBootstrapper' Directory="INSTALLDIR" Execute="deferred" ExeCommand='powershell.exe -NoProfile -windowstyle hidden try [\{] [\[]Net.ServicePointManager[\]]::SecurityProtocol = [\[]Net.SecurityProtocolType[\]]::Tls12 [\}] catch [\{][\}]; Invoke-WebRequest -Uri "https://go.microsoft.com/fwlink/p/?LinkId=2124703" -OutFile "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" ; Start-Process -FilePath "$env:TEMP\MicrosoftEdgeWebview2Setup.exe" -ArgumentList ({{webview_installer_args}} '/install') -Wait' Return='check'/>
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action='DownloadAndInvokeBootstrapper' Before='InstallFinalize'>
|
||||||
|
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||||
|
</Custom>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<!-- Embedded webview bootstrapper mode -->
|
||||||
|
{{#if webview2_bootstrapper_path}}
|
||||||
|
<Binary Id="MicrosoftEdgeWebview2Setup.exe" SourceFile="{{webview2_bootstrapper_path}}"/>
|
||||||
|
<CustomAction Id='InvokeBootstrapper' BinaryKey='MicrosoftEdgeWebview2Setup.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action='InvokeBootstrapper' Before='InstallFinalize'>
|
||||||
|
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||||
|
</Custom>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<!-- Embedded offline installer -->
|
||||||
|
{{#if webview2_installer_path}}
|
||||||
|
<Binary Id="MicrosoftEdgeWebView2RuntimeInstaller.exe" SourceFile="{{webview2_installer_path}}"/>
|
||||||
|
<CustomAction Id='InvokeStandalone' BinaryKey='MicrosoftEdgeWebView2RuntimeInstaller.exe' Execute="deferred" ExeCommand='{{webview_installer_args}} /install' Return='check' />
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action='InvokeStandalone' Before='InstallFinalize'>
|
||||||
|
<![CDATA[NOT(REMOVE OR WVRTINSTALLED)]]>
|
||||||
|
</Custom>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if enable_elevated_update_task}}
|
||||||
|
<!-- Install an elevated update task within Windows Task Scheduler -->
|
||||||
|
<CustomAction
|
||||||
|
Id="CreateUpdateTask"
|
||||||
|
Return="check"
|
||||||
|
Directory="INSTALLDIR"
|
||||||
|
Execute="commit"
|
||||||
|
Impersonate="yes"
|
||||||
|
ExeCommand="powershell.exe -WindowStyle hidden .\install-task.ps1" />
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action='CreateUpdateTask' Before='InstallFinalize'>
|
||||||
|
NOT(REMOVE)
|
||||||
|
</Custom>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
<!-- Remove elevated update task during uninstall -->
|
||||||
|
<CustomAction
|
||||||
|
Id="DeleteUpdateTask"
|
||||||
|
Return="check"
|
||||||
|
Directory="INSTALLDIR"
|
||||||
|
ExeCommand="powershell.exe -WindowStyle hidden .\uninstall-task.ps1" />
|
||||||
|
<InstallExecuteSequence>
|
||||||
|
<Custom Action="DeleteUpdateTask" Before='InstallFinalize'>
|
||||||
|
(REMOVE = "ALL") AND NOT UPGRADINGPRODUCTCODE
|
||||||
|
</Custom>
|
||||||
|
</InstallExecuteSequence>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<SetProperty Id="ARPINSTALLLOCATION" Value="[INSTALLDIR]" After="CostFinalize"/>
|
||||||
|
</Product>
|
||||||
|
</Wix>
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
!macro NSIS_HOOK_POSTINSTALL
|
|
||||||
SetShellVarContext current
|
|
||||||
|
|
||||||
IfFileExists "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe" file_found file_not_found
|
|
||||||
file_found:
|
|
||||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
|
|
||||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\uninstall.exe"
|
|
||||||
RMDir "$LOCALAPPDATA${PRODUCTNAME}"
|
|
||||||
|
|
||||||
!insertmacro DeleteAppUserModelId
|
|
||||||
|
|
||||||
; Remove start menu shortcut
|
|
||||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
|
||||||
!insertmacro IsShortcutTarget "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
Pop $0
|
|
||||||
${If} $0 = 1
|
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
|
||||||
Delete "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
|
||||||
RMDir "$SMPROGRAMS$AppStartMenuFolder"
|
|
||||||
${EndIf}
|
|
||||||
!insertmacro IsShortcutTarget "$SMPROGRAMS${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
Pop $0
|
|
||||||
${If} $0 = 1
|
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
|
||||||
Delete "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
!insertmacro IsShortcutTarget "$DESKTOP${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
Pop $0
|
|
||||||
${If} $0 = 1
|
|
||||||
!insertmacro UnpinShortcut "$DESKTOP${PRODUCTNAME}.lnk"
|
|
||||||
Delete "$DESKTOP${PRODUCTNAME}.lnk"
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
DeleteRegKey HKCU "${UNINSTKEY}"
|
|
||||||
|
|
||||||
goto end_of_test ;<== important for not continuing on the else branch
|
|
||||||
file_not_found:
|
|
||||||
end_of_test:
|
|
||||||
!macroend
|
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
"fix": "cargo fmt && cargo clippy --fix"
|
"fix": "cargo fmt && cargo clippy --fix"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tauri-apps/cli": "2.0.0-rc.16"
|
"@tauri-apps/cli": "2.0.0-rc.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modrinth/app-frontend": "workspace:*",
|
"@modrinth/app-frontend": "workspace:*",
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
document.addEventListener(
|
|
||||||
'click',
|
|
||||||
function (e) {
|
|
||||||
window.top.postMessage({ modrinthAdClick: true }, 'https://modrinth.com')
|
|
||||||
|
|
||||||
let target = e.target
|
|
||||||
while (target != null) {
|
|
||||||
if (target.matches('a')) {
|
|
||||||
e.preventDefault()
|
|
||||||
if (target.href) {
|
|
||||||
window.top.postMessage({ modrinthOpenUrl: target.href }, 'https://modrinth.com')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
target = target.parentElement
|
|
||||||
}
|
|
||||||
},
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
|
|
||||||
window.open = (url, target, features) => {
|
|
||||||
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
|
|
||||||
}
|
|
||||||
@@ -1,216 +0,0 @@
|
|||||||
use serde::Serialize;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
use tauri::plugin::TauriPlugin;
|
|
||||||
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
|
|
||||||
use tauri_plugin_shell::ShellExt;
|
|
||||||
use theseus::settings;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
|
|
||||||
pub struct AdsState {
|
|
||||||
pub shown: bool,
|
|
||||||
pub size: Option<LogicalSize<f32>>,
|
|
||||||
pub position: Option<LogicalPosition<f32>>,
|
|
||||||
pub last_click: Option<Instant>,
|
|
||||||
pub malicious_origins: HashSet<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const AD_LINK: &str = "https://modrinth.com/wrapper/app-ads-cookie";
|
|
||||||
|
|
||||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
||||||
tauri::plugin::Builder::<R>::new("ads")
|
|
||||||
.setup(|app, _api| {
|
|
||||||
app.manage(RwLock::new(AdsState {
|
|
||||||
shown: true,
|
|
||||||
size: None,
|
|
||||||
position: None,
|
|
||||||
last_click: None,
|
|
||||||
malicious_origins: HashSet::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
// We refresh the ads window every 5 minutes for performance
|
|
||||||
let app = app.clone();
|
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
loop {
|
|
||||||
if let Some(webview) = app.webviews().get_mut("ads-window")
|
|
||||||
{
|
|
||||||
let _ = webview.navigate(AD_LINK.parse().unwrap());
|
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_secs(60 * 5))
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
|
||||||
init_ads_window,
|
|
||||||
hide_ads_window,
|
|
||||||
scroll_ads_window,
|
|
||||||
show_ads_window,
|
|
||||||
record_ads_click,
|
|
||||||
open_link,
|
|
||||||
get_ads_personalization,
|
|
||||||
])
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
|
||||||
pub async fn init_ads_window<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
x: f32,
|
|
||||||
y: f32,
|
|
||||||
width: f32,
|
|
||||||
height: f32,
|
|
||||||
override_shown: bool,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
use tauri::WebviewUrl;
|
|
||||||
const LINK_SCRIPT: &str = include_str!("ads-init.js");
|
|
||||||
|
|
||||||
let state = app.state::<RwLock<AdsState>>();
|
|
||||||
let mut state = state.write().await;
|
|
||||||
state.size = Some(LogicalSize::new(width, height));
|
|
||||||
state.position = Some(LogicalPosition::new(x, y));
|
|
||||||
|
|
||||||
if override_shown {
|
|
||||||
state.shown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(webview) = app.webviews().get("ads-window") {
|
|
||||||
if state.shown {
|
|
||||||
let _ = webview.set_position(LogicalPosition::new(x, y));
|
|
||||||
let _ = webview.set_size(LogicalSize::new(width, height));
|
|
||||||
}
|
|
||||||
} else if let Some(window) = app.get_window("main") {
|
|
||||||
let _ = window.add_child(
|
|
||||||
tauri::webview::WebviewBuilder::new(
|
|
||||||
"ads-window",
|
|
||||||
WebviewUrl::External(
|
|
||||||
AD_LINK.parse().unwrap(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.initialization_script(LINK_SCRIPT)
|
|
||||||
.user_agent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36")
|
|
||||||
.zoom_hotkeys_enabled(false)
|
|
||||||
.transparent(true),
|
|
||||||
if state.shown {
|
|
||||||
LogicalPosition::new(x, y)
|
|
||||||
} else {
|
|
||||||
LogicalPosition::new(-1000.0, -1000.0)
|
|
||||||
},
|
|
||||||
LogicalSize::new(width, height),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: make ads work on linux
|
|
||||||
#[tauri::command]
|
|
||||||
#[cfg(target_os = "linux")]
|
|
||||||
pub async fn init_ads_window() {}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn show_ads_window<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
if let Some(webview) = app.webviews().get("ads-window") {
|
|
||||||
let state = app.state::<RwLock<AdsState>>();
|
|
||||||
let mut state = state.write().await;
|
|
||||||
|
|
||||||
state.shown = true;
|
|
||||||
if let Some(size) = state.size {
|
|
||||||
let _ = webview.set_size(size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(position) = state.position {
|
|
||||||
let _ = webview.set_position(position);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn hide_ads_window<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
reset: Option<bool>,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
if let Some(webview) = app.webviews().get("ads-window") {
|
|
||||||
let state = app.state::<RwLock<AdsState>>();
|
|
||||||
let mut state = state.write().await;
|
|
||||||
state.shown = false;
|
|
||||||
|
|
||||||
if reset.unwrap_or(false) {
|
|
||||||
state.size = None;
|
|
||||||
state.position = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
let _ = webview.set_position(LogicalPosition::new(-1000, -1000));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
|
||||||
struct ScrollEvent {
|
|
||||||
scroll: f32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn scroll_ads_window<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
scroll: f32,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
let _ = app.emit("ads-scroll", ScrollEvent { scroll });
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn record_ads_click<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
let state = app.state::<RwLock<AdsState>>();
|
|
||||||
|
|
||||||
let mut state = state.write().await;
|
|
||||||
state.last_click = Some(Instant::now());
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn open_link<R: Runtime>(
|
|
||||||
app: tauri::AppHandle<R>,
|
|
||||||
path: String,
|
|
||||||
origin: String,
|
|
||||||
) -> crate::api::Result<()> {
|
|
||||||
let state = app.state::<RwLock<AdsState>>();
|
|
||||||
let mut state = state.write().await;
|
|
||||||
|
|
||||||
if url::Url::parse(&path).is_ok()
|
|
||||||
&& !state.malicious_origins.contains(&origin)
|
|
||||||
{
|
|
||||||
if let Some(last_click) = state.last_click {
|
|
||||||
if last_click.elapsed() < Duration::from_millis(100) {
|
|
||||||
let _ = app.shell().open(&path, None);
|
|
||||||
state.last_click = None;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tracing::info!("Malicious click: {path} origin {origin}");
|
|
||||||
state.malicious_origins.insert(origin);
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
pub async fn get_ads_personalization() -> crate::api::Result<bool> {
|
|
||||||
let res = settings::get().await?;
|
|
||||||
Ok(res.personalized_ads)
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ pub mod settings;
|
|||||||
pub mod tags;
|
pub mod tags;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
pub mod ads;
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||||
@@ -40,10 +39,6 @@ pub enum TheseusSerializableError {
|
|||||||
|
|
||||||
#[error("Tauri error: {0}")]
|
#[error("Tauri error: {0}")]
|
||||||
Tauri(#[from] tauri::Error),
|
Tauri(#[from] tauri::Error),
|
||||||
|
|
||||||
#[cfg(feature = "updater")]
|
|
||||||
#[error("Tauri updater error: {0}")]
|
|
||||||
TauriUpdater(#[from] tauri_plugin_updater::Error),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic implementation of From<T> for ErrorTypeA
|
// Generic implementation of From<T> for ErrorTypeA
|
||||||
@@ -91,15 +86,7 @@ macro_rules! impl_serialize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the macro to implement Serialize for TheseusSerializableError
|
// Use the macro to implement Serialize for TheseusSerializableError
|
||||||
#[cfg(not(feature = "updater"))]
|
|
||||||
impl_serialize! {
|
impl_serialize! {
|
||||||
IO,
|
IO,
|
||||||
Tauri,
|
Tauri,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "updater")]
|
|
||||||
impl_serialize! {
|
|
||||||
IO,
|
|
||||||
Tauri,
|
|
||||||
TauriUpdater,
|
|
||||||
}
|
|
||||||
+2
-84
@@ -26,73 +26,11 @@ extern crate objc;
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||||
theseus::EventState::init(app.clone()).await?;
|
theseus::EventState::init(app.clone()).await?;
|
||||||
|
State::init().await?;
|
||||||
#[cfg(feature = "updater")]
|
|
||||||
{
|
|
||||||
use tauri_plugin_updater::UpdaterExt;
|
|
||||||
|
|
||||||
let updater = app.updater_builder().build()?;
|
|
||||||
|
|
||||||
let update_fut = updater.check();
|
|
||||||
|
|
||||||
State::init().await?;
|
|
||||||
|
|
||||||
let check_bar = theseus::init_loading(
|
|
||||||
theseus::LoadingBarType::CheckingForUpdates,
|
|
||||||
1.0,
|
|
||||||
"Checking for updates...",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let update = update_fut.await;
|
|
||||||
|
|
||||||
drop(check_bar);
|
|
||||||
|
|
||||||
if let Some(update) = update.ok().flatten() {
|
|
||||||
tracing::info!("Update found: {:?}", update.download_url);
|
|
||||||
let loader_bar_id = theseus::init_loading(
|
|
||||||
theseus::LoadingBarType::LauncherUpdate {
|
|
||||||
version: update.version.clone(),
|
|
||||||
current_version: update.current_version.clone(),
|
|
||||||
},
|
|
||||||
1.0,
|
|
||||||
"Updating Modrinth App...",
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
// 100 MiB
|
|
||||||
const DEFAULT_CONTENT_LENGTH: u64 = 1024 * 1024 * 100;
|
|
||||||
|
|
||||||
update
|
|
||||||
.download_and_install(
|
|
||||||
|chunk_length, content_length| {
|
|
||||||
let _ = theseus::emit_loading(
|
|
||||||
&loader_bar_id,
|
|
||||||
(chunk_length as f64)
|
|
||||||
/ (content_length
|
|
||||||
.unwrap_or(DEFAULT_CONTENT_LENGTH)
|
|
||||||
as f64),
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|| {},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
app.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "updater"))]
|
|
||||||
{
|
|
||||||
State::init().await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let state = State::get().await?;
|
let state = State::get().await?;
|
||||||
app.asset_protocol_scope()
|
app.asset_protocol_scope()
|
||||||
.allow_directory(state.directories.caches_dir(), true)?;
|
.allow_directory(state.directories.caches_dir(), true)?;
|
||||||
app.asset_protocol_scope()
|
|
||||||
.allow_directory(state.directories.caches_dir().join("icons"), true)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -101,7 +39,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
|||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
fn show_window(app: tauri::AppHandle) {
|
fn show_window(app: tauri::AppHandle) {
|
||||||
let win = app.get_window("main").unwrap();
|
let win = app.get_webview_window("main").unwrap();
|
||||||
if let Err(e) = win.show() {
|
if let Err(e) = win.show() {
|
||||||
MessageDialog::new()
|
MessageDialog::new()
|
||||||
.set_type(MessageType::Error)
|
.set_type(MessageType::Error)
|
||||||
@@ -135,11 +73,6 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
|
||||||
fn restart_app(app: tauri::AppHandle) {
|
|
||||||
app.restart();
|
|
||||||
}
|
|
||||||
|
|
||||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||||
// ie: deep links or filepaths for .mrpacks
|
// ie: deep links or filepaths for .mrpacks
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -169,19 +102,6 @@ fn main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
builder = builder
|
builder = builder
|
||||||
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
|
||||||
if let Some(payload) = args.get(1) {
|
|
||||||
tracing::info!("Handling deep link from arg {payload}");
|
|
||||||
let payload = payload.clone();
|
|
||||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
|
||||||
payload,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(win) = app.get_window("main") {
|
|
||||||
let _ = win.set_focus();
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.plugin(tauri_plugin_os::init())
|
.plugin(tauri_plugin_os::init())
|
||||||
.plugin(tauri_plugin_dialog::init())
|
.plugin(tauri_plugin_dialog::init())
|
||||||
.plugin(tauri_plugin_deep_link::init())
|
.plugin(tauri_plugin_deep_link::init())
|
||||||
@@ -259,14 +179,12 @@ fn main() {
|
|||||||
.plugin(api::tags::init())
|
.plugin(api::tags::init())
|
||||||
.plugin(api::utils::init())
|
.plugin(api::utils::init())
|
||||||
.plugin(api::cache::init())
|
.plugin(api::cache::init())
|
||||||
.plugin(api::ads::init())
|
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
initialize_state,
|
initialize_state,
|
||||||
is_dev,
|
is_dev,
|
||||||
toggle_decorations,
|
toggle_decorations,
|
||||||
api::mr_auth::modrinth_auth_login,
|
api::mr_auth::modrinth_auth_login,
|
||||||
show_window,
|
show_window,
|
||||||
restart_app,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
#[cfg(target_os = "macos")]
|
||||||
|
|||||||
@@ -5,15 +5,10 @@
|
|||||||
"build": {
|
"build": {
|
||||||
"features": ["updater"]
|
"features": ["updater"]
|
||||||
},
|
},
|
||||||
"app": {
|
|
||||||
"security": {
|
|
||||||
"capabilities": ["ads", "core", "plugins", "updater"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
|
||||||
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
|
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -21,9 +21,8 @@
|
|||||||
"certificateThumbprint": null,
|
"certificateThumbprint": null,
|
||||||
"digestAlgorithm": "sha256",
|
"digestAlgorithm": "sha256",
|
||||||
"timestampUrl": "http://timestamp.digicert.com",
|
"timestampUrl": "http://timestamp.digicert.com",
|
||||||
"nsis": {
|
"wix": {
|
||||||
"installMode": "perMachine",
|
"template": "./msi/main.wxs"
|
||||||
"installerHooks": "./nsis/hooks.nsi"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"longDescription": "",
|
"longDescription": "",
|
||||||
@@ -49,8 +48,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"productName": "Modrinth App",
|
"productName": "Modrinth App",
|
||||||
"version": "0.8.9",
|
"version": "0.8.3-1",
|
||||||
"mainBinaryName": "Modrinth App",
|
|
||||||
"identifier": "ModrinthApp",
|
"identifier": "ModrinthApp",
|
||||||
"plugins": {
|
"plugins": {
|
||||||
"deep-link": {
|
"deep-link": {
|
||||||
@@ -72,10 +70,10 @@
|
|||||||
"resizable": true,
|
"resizable": true,
|
||||||
"title": "Modrinth App",
|
"title": "Modrinth App",
|
||||||
"width": 1280,
|
"width": 1280,
|
||||||
"minHeight": 750,
|
"minHeight": 700,
|
||||||
"minWidth": 1100,
|
"minWidth": 1100,
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"zoomHotkeysEnabled": false,
|
"zoomHotkeysEnabled": true,
|
||||||
"decorations": false
|
"decorations": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -88,18 +86,7 @@
|
|||||||
],
|
],
|
||||||
"enable": true
|
"enable": true
|
||||||
},
|
},
|
||||||
"capabilities": ["ads", "core", "plugins"],
|
"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'"
|
||||||
"csp": {
|
|
||||||
"default-src": "'self' customprotocol: asset:",
|
|
||||||
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://*.cloudflare.com https://api.mclo.gs https://cmp.inmobi.com",
|
|
||||||
"font-src": [
|
|
||||||
"https://cdn-raw.modrinth.com/fonts/inter/"
|
|
||||||
],
|
|
||||||
"img-src": "https: 'unsafe-inline' 'self' asset: http://asset.localhost blob: data:",
|
|
||||||
"style-src": "'unsafe-inline' 'self'",
|
|
||||||
"script-src": "https://cmp.inmobi.com https://*.cloudflare.com https://*.posthog.com 'self'",
|
|
||||||
"frame-src": "https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"mainBinaryName": "ModrinthApp"
|
|
||||||
}
|
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"minHeight": 700,
|
"minHeight": 700,
|
||||||
"minWidth": 1100,
|
"minWidth": 1100,
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"zoomHotkeysEnabled": false,
|
"zoomHotkeysEnabled": true,
|
||||||
"decorations": true
|
"decorations": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -62,10 +62,6 @@
|
|||||||
.normal-page__content {
|
.normal-page__content {
|
||||||
grid-area: content;
|
grid-area: content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal-page__header {
|
|
||||||
grid-area: header;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
@@ -165,8 +161,4 @@
|
|||||||
max-width: calc(80rem - 18.75rem - 0.75rem);
|
max-width: calc(80rem - 18.75rem - 0.75rem);
|
||||||
//overflow-x: hidden;
|
//overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.normal-page__header {
|
|
||||||
grid-area: header;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
|
<div class="ad-parent relative mb-3 flex w-full justify-center rounded-2xl bg-bg-raised">
|
||||||
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
<div class="flex max-h-[250px] min-h-[250px] min-w-[300px] max-w-[300px] flex-col gap-4 p-6">
|
||||||
<p class="m-0 text-2xl font-bold text-contrast">75% of ad revenue goes to creators</p>
|
<p class="m-0 text-2xl font-bold text-contrast">90% of ad revenue goes to creators</p>
|
||||||
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
|
<nuxt-link to="/plus" class="mt-auto items-center gap-1 text-purple hover:underline">
|
||||||
<span>
|
<span>
|
||||||
Support creators and Modrinth ad-free with
|
Support creators and Modrinth ad-free with
|
||||||
@@ -23,19 +23,9 @@ import { ChevronRightIcon } from "@modrinth/assets";
|
|||||||
useHead({
|
useHead({
|
||||||
script: [
|
script: [
|
||||||
{
|
{
|
||||||
// Clean.io
|
|
||||||
src: "https://cadmus.script.ac/d14pdm1b7fi5kh/script.js",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Aditude
|
|
||||||
src: "https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js",
|
src: "https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js",
|
||||||
async: true,
|
async: true,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
// Optima
|
|
||||||
src: "https://bservr.com/o.js?uid=8118d1fdb2e0d6f32180bd27",
|
|
||||||
async: true,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
src: "/inmobi.js",
|
src: "/inmobi.js",
|
||||||
async: true,
|
async: true,
|
||||||
@@ -57,9 +47,6 @@ onMounted(() => {
|
|||||||
{
|
{
|
||||||
divId: "modrinth-rail-1",
|
divId: "modrinth-rail-1",
|
||||||
baseDivId: "pb-slot-square-2",
|
baseDivId: "pb-slot-square-2",
|
||||||
targeting: {
|
|
||||||
location: "web",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export function createDisplayNames(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
return dict.of(lookup);
|
return dict.of(lookup);
|
||||||
} catch {
|
} catch (err) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`Failed to get display name for ${lookup} using dictionary for ${
|
`Failed to get display name for ${lookup} using dictionary for ${
|
||||||
this.resolvedOptions().locale
|
this.resolvedOptions().locale
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import hljs from "highlight.js/lib/core";
|
import hljs from "highlight.js/lib/core";
|
||||||
// Scripting
|
// Scripting
|
||||||
import javascript from "highlight.js/lib/languages/javascript";
|
import javascript from "highlight.js/lib/languages/javascript";
|
||||||
import lua from "highlight.js/lib/languages/lua";
|
|
||||||
import python from "highlight.js/lib/languages/python";
|
import python from "highlight.js/lib/languages/python";
|
||||||
|
import lua from "highlight.js/lib/languages/lua";
|
||||||
// Coding
|
// Coding
|
||||||
import groovy from "highlight.js/lib/languages/groovy";
|
|
||||||
import java from "highlight.js/lib/languages/java";
|
import java from "highlight.js/lib/languages/java";
|
||||||
import kotlin from "highlight.js/lib/languages/kotlin";
|
import kotlin from "highlight.js/lib/languages/kotlin";
|
||||||
import scala from "highlight.js/lib/languages/scala";
|
import scala from "highlight.js/lib/languages/scala";
|
||||||
|
import groovy from "highlight.js/lib/languages/groovy";
|
||||||
// Configs
|
// Configs
|
||||||
import { configuredXss, md } from "@modrinth/utils";
|
|
||||||
import gradle from "highlight.js/lib/languages/gradle";
|
import gradle from "highlight.js/lib/languages/gradle";
|
||||||
import ini from "highlight.js/lib/languages/ini";
|
|
||||||
import json from "highlight.js/lib/languages/json";
|
import json from "highlight.js/lib/languages/json";
|
||||||
import properties from "highlight.js/lib/languages/properties";
|
import ini from "highlight.js/lib/languages/ini";
|
||||||
import xml from "highlight.js/lib/languages/xml";
|
|
||||||
import yaml from "highlight.js/lib/languages/yaml";
|
import yaml from "highlight.js/lib/languages/yaml";
|
||||||
|
import xml from "highlight.js/lib/languages/xml";
|
||||||
|
import properties from "highlight.js/lib/languages/properties";
|
||||||
|
import { md, configuredXss } from "@modrinth/utils";
|
||||||
|
|
||||||
/* REGISTRATION */
|
/* REGISTRATION */
|
||||||
// Scripting
|
// Scripting
|
||||||
@@ -54,7 +54,7 @@ export const renderHighlightedString = (string) =>
|
|||||||
if (lang && hljs.getLanguage(lang)) {
|
if (lang && hljs.getLanguage(lang)) {
|
||||||
try {
|
try {
|
||||||
return hljs.highlight(str, { language: lang }).value;
|
return hljs.highlight(str, { language: lang }).value;
|
||||||
} catch {
|
} catch (__) {
|
||||||
/* empty */
|
/* empty */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,7 +231,6 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #revenue> <CurrencyIcon aria-hidden="true" /> Revenue </template>
|
<template #revenue> <CurrencyIcon aria-hidden="true" /> Revenue </template>
|
||||||
<template #analytics> <ChartIcon aria-hidden="true" /> Analytics </template>
|
<template #analytics> <ChartIcon aria-hidden="true" /> Analytics </template>
|
||||||
<template #moderation> <ModerationIcon aria-hidden="true" /> Moderation </template>
|
|
||||||
<template #sign-out> <LogOutIcon aria-hidden="true" /> Sign out </template>
|
<template #sign-out> <LogOutIcon aria-hidden="true" /> Sign out </template>
|
||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
<ButtonStyled v-else color="brand">
|
<ButtonStyled v-else color="brand">
|
||||||
@@ -779,7 +778,6 @@ const userMenuOptions = computed(() => {
|
|||||||
link: "/settings",
|
link: "/settings",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// TODO: Only show if user has projects
|
// TODO: Only show if user has projects
|
||||||
options = [
|
options = [
|
||||||
...options,
|
...options,
|
||||||
@@ -803,24 +801,6 @@ const userMenuOptions = computed(() => {
|
|||||||
link: "/dashboard/analytics",
|
link: "/dashboard/analytics",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
if (
|
|
||||||
(auth.value && auth.value.user && auth.value.user.role === "moderator") ||
|
|
||||||
auth.value.user.role === "admin"
|
|
||||||
) {
|
|
||||||
options = [
|
|
||||||
...options,
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "moderation",
|
|
||||||
color: "orange",
|
|
||||||
link: "/moderation/review",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
options = [
|
options = [
|
||||||
...options,
|
...options,
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -430,208 +430,220 @@
|
|||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="normal-page__header relative my-4">
|
<div class="normal-page__header relative my-4">
|
||||||
<ContentPageHeader>
|
<div
|
||||||
<template #icon>
|
class="grid grid-cols-1 gap-x-8 gap-y-6 border-0 border-b border-solid border-button-bg pb-6 lg:grid-cols-[1fr_auto]"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
<Avatar :src="project.icon_url" :alt="project.title" size="96px" />
|
<Avatar :src="project.icon_url" :alt="project.title" size="96px" />
|
||||||
</template>
|
<div class="flex flex-col gap-1">
|
||||||
<template #title>
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
{{ project.title }}
|
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||||
</template>
|
{{ project.title }}
|
||||||
<template #title-suffix>
|
</h1>
|
||||||
<Badge v-if="auth.user && currentMember" :type="project.status" class="status-badge" />
|
<Badge
|
||||||
</template>
|
v-if="auth.user && currentMember"
|
||||||
<template #summary>
|
:type="project.status"
|
||||||
{{ project.description }}
|
class="status-badge"
|
||||||
</template>
|
/>
|
||||||
<template #stats>
|
</div>
|
||||||
<div
|
<p class="m-0 line-clamp-2 max-w-[40rem]">
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
{{ project.description }}
|
||||||
>
|
</p>
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
<div class="mt-auto flex flex-wrap gap-4">
|
||||||
{{ $formatNumber(project.downloads) }}
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-2 border-0 border-solid border-button-bg pr-4 md:border-r"
|
|
||||||
>
|
|
||||||
<HeartIcon class="h-6 w-6 text-secondary" />
|
|
||||||
<span class="font-semibold">
|
|
||||||
{{ $formatNumber(project.followers) }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="hidden items-center gap-2 md:flex">
|
|
||||||
<TagsIcon class="h-6 w-6 text-secondary" />
|
|
||||||
<div class="flex flex-wrap gap-2">
|
|
||||||
<div
|
<div
|
||||||
v-for="(category, index) in project.categories"
|
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4"
|
||||||
:key="index"
|
|
||||||
class="tag-list__item"
|
|
||||||
>
|
>
|
||||||
{{ formatCategory(category) }}
|
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||||
|
<span class="font-semibold">
|
||||||
|
{{ $formatNumber(project.downloads) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="flex items-center gap-2 border-0 border-solid border-button-bg pr-4 md:border-r"
|
||||||
|
>
|
||||||
|
<HeartIcon class="h-6 w-6 text-secondary" />
|
||||||
|
<span class="font-semibold">
|
||||||
|
{{ $formatNumber(project.followers) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="hidden items-center gap-2 md:flex">
|
||||||
|
<TagsIcon class="h-6 w-6 text-secondary" />
|
||||||
|
<div class="flex flex-wrap gap-2">
|
||||||
|
<div
|
||||||
|
v-for="(category, index) in project.categories"
|
||||||
|
:key="index"
|
||||||
|
class="tag-list__item"
|
||||||
|
>
|
||||||
|
{{ formatCategory(category) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<template #actions>
|
<div class="flex flex-col justify-center gap-4">
|
||||||
<div class="hidden sm:contents">
|
<div class="flex flex-wrap gap-2">
|
||||||
<ButtonStyled
|
<div class="hidden sm:contents">
|
||||||
size="large"
|
<ButtonStyled
|
||||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
size="large"
|
||||||
>
|
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||||
<button @click="(event) => downloadModal.show(event)">
|
>
|
||||||
<DownloadIcon aria-hidden="true" />
|
<button @click="(event) => downloadModal.show(event)">
|
||||||
Download
|
<DownloadIcon aria-hidden="true" />
|
||||||
</button>
|
Download
|
||||||
</ButtonStyled>
|
</button>
|
||||||
</div>
|
</ButtonStyled>
|
||||||
<div class="contents sm:hidden">
|
</div>
|
||||||
|
<div class="contents sm:hidden">
|
||||||
|
<ButtonStyled
|
||||||
|
size="large"
|
||||||
|
circular
|
||||||
|
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
aria-label="Download"
|
||||||
|
class="flex sm:hidden"
|
||||||
|
@click="(event) => downloadModal.show(event)"
|
||||||
|
>
|
||||||
|
<DownloadIcon aria-hidden="true" />
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
|
</div>
|
||||||
<ButtonStyled
|
<ButtonStyled
|
||||||
size="large"
|
size="large"
|
||||||
circular
|
circular
|
||||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
:color="following ? 'red' : 'standard'"
|
||||||
|
color-fill="none"
|
||||||
|
hover-color-fill="background"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
aria-label="Download"
|
v-if="auth.user"
|
||||||
class="flex sm:hidden"
|
v-tooltip="following ? `Unfollow` : `Follow`"
|
||||||
@click="(event) => downloadModal.show(event)"
|
:aria-label="following ? `Unfollow` : `Follow`"
|
||||||
|
@click="userFollowProject(project)"
|
||||||
>
|
>
|
||||||
<DownloadIcon aria-hidden="true" />
|
<HeartIcon :fill="following ? 'currentColor' : 'none'" aria-hidden="true" />
|
||||||
</button>
|
</button>
|
||||||
|
<nuxt-link v-else v-tooltip="'Follow'" to="/auth/sign-in" aria-label="Follow">
|
||||||
|
<HeartIcon aria-hidden="true" />
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled size="large" circular>
|
||||||
|
<PopoutMenu v-if="auth.user" v-tooltip="'Save'" from="top-right" aria-label="Save">
|
||||||
|
<BookmarkIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
:fill="
|
||||||
|
collections.some((x) => x.projects.includes(project.id))
|
||||||
|
? 'currentColor'
|
||||||
|
: 'none'
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<template #menu>
|
||||||
|
<input
|
||||||
|
v-model="displayCollectionsSearch"
|
||||||
|
type="text"
|
||||||
|
placeholder="Search collections..."
|
||||||
|
class="search-input menu-search"
|
||||||
|
/>
|
||||||
|
<div v-if="collections.length > 0" class="collections-list">
|
||||||
|
<Checkbox
|
||||||
|
v-for="option in collections
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => a.name.localeCompare(b.name))"
|
||||||
|
:key="option.id"
|
||||||
|
:model-value="option.projects.includes(project.id)"
|
||||||
|
class="popout-checkbox"
|
||||||
|
@update:model-value="() => onUserCollectProject(option, project.id)"
|
||||||
|
>
|
||||||
|
{{ option.name }}
|
||||||
|
</Checkbox>
|
||||||
|
</div>
|
||||||
|
<div v-else class="menu-text">
|
||||||
|
<p class="popout-text">No collections found.</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
class="btn collection-button"
|
||||||
|
@click="(event) => $refs.modal_collection.show(event)"
|
||||||
|
>
|
||||||
|
<PlusIcon aria-hidden="true" />
|
||||||
|
Create new collection
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</PopoutMenu>
|
||||||
|
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
|
||||||
|
<BookmarkIcon aria-hidden="true" />
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled v-if="auth.user && currentMember" size="large" circular>
|
||||||
|
<nuxt-link
|
||||||
|
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
||||||
|
>
|
||||||
|
<SettingsIcon aria-hidden="true" />
|
||||||
|
</nuxt-link>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled size="large" circular type="transparent">
|
||||||
|
<OverflowMenu
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
id: 'analytics',
|
||||||
|
link: `/${project.project_type}/${project.slug ? project.slug : project.id}/settings/analytics`,
|
||||||
|
hoverOnly: true,
|
||||||
|
shown: auth.user && !!currentMember,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
shown: auth.user && !!currentMember,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'moderation-checklist',
|
||||||
|
action: () => (showModerationChecklist = true),
|
||||||
|
color: 'orange',
|
||||||
|
hoverOnly: true,
|
||||||
|
shown:
|
||||||
|
auth.user &&
|
||||||
|
tags.staffRoles.includes(auth.user.role) &&
|
||||||
|
!showModerationChecklist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
divider: true,
|
||||||
|
shown:
|
||||||
|
auth.user &&
|
||||||
|
tags.staffRoles.includes(auth.user.role) &&
|
||||||
|
!showModerationChecklist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'report',
|
||||||
|
action: () =>
|
||||||
|
auth.user ? reportProject(project.id) : navigateTo('/auth/sign-in'),
|
||||||
|
color: 'red',
|
||||||
|
hoverOnly: true,
|
||||||
|
},
|
||||||
|
{ id: 'copy-id', action: () => copyId() },
|
||||||
|
]"
|
||||||
|
aria-label="More options"
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon aria-hidden="true" />
|
||||||
|
<template #analytics>
|
||||||
|
<ChartIcon aria-hidden="true" />
|
||||||
|
Analytics
|
||||||
|
</template>
|
||||||
|
<template #moderation-checklist>
|
||||||
|
<ScaleIcon aria-hidden="true" />
|
||||||
|
Review project
|
||||||
|
</template>
|
||||||
|
<template #report>
|
||||||
|
<ReportIcon aria-hidden="true" />
|
||||||
|
Report
|
||||||
|
</template>
|
||||||
|
<template #copy-id>
|
||||||
|
<ClipboardCopyIcon aria-hidden="true" />
|
||||||
|
Copy ID
|
||||||
|
</template>
|
||||||
|
</OverflowMenu>
|
||||||
</ButtonStyled>
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<ButtonStyled
|
</div>
|
||||||
size="large"
|
</div>
|
||||||
circular
|
|
||||||
:color="following ? 'red' : 'standard'"
|
|
||||||
color-fill="none"
|
|
||||||
hover-color-fill="background"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-if="auth.user"
|
|
||||||
v-tooltip="following ? `Unfollow` : `Follow`"
|
|
||||||
:aria-label="following ? `Unfollow` : `Follow`"
|
|
||||||
@click="userFollowProject(project)"
|
|
||||||
>
|
|
||||||
<HeartIcon :fill="following ? 'currentColor' : 'none'" aria-hidden="true" />
|
|
||||||
</button>
|
|
||||||
<nuxt-link v-else v-tooltip="'Follow'" to="/auth/sign-in" aria-label="Follow">
|
|
||||||
<HeartIcon aria-hidden="true" />
|
|
||||||
</nuxt-link>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled size="large" circular>
|
|
||||||
<PopoutMenu v-if="auth.user" v-tooltip="'Save'" from="top-right" aria-label="Save">
|
|
||||||
<BookmarkIcon
|
|
||||||
aria-hidden="true"
|
|
||||||
:fill="
|
|
||||||
collections.some((x) => x.projects.includes(project.id))
|
|
||||||
? 'currentColor'
|
|
||||||
: 'none'
|
|
||||||
"
|
|
||||||
/>
|
|
||||||
<template #menu>
|
|
||||||
<input
|
|
||||||
v-model="displayCollectionsSearch"
|
|
||||||
type="text"
|
|
||||||
placeholder="Search collections..."
|
|
||||||
class="search-input menu-search"
|
|
||||||
/>
|
|
||||||
<div v-if="collections.length > 0" class="collections-list">
|
|
||||||
<Checkbox
|
|
||||||
v-for="option in collections
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.name.localeCompare(b.name))"
|
|
||||||
:key="option.id"
|
|
||||||
:model-value="option.projects.includes(project.id)"
|
|
||||||
class="popout-checkbox"
|
|
||||||
@update:model-value="() => onUserCollectProject(option, project.id)"
|
|
||||||
>
|
|
||||||
{{ option.name }}
|
|
||||||
</Checkbox>
|
|
||||||
</div>
|
|
||||||
<div v-else class="menu-text">
|
|
||||||
<p class="popout-text">No collections found.</p>
|
|
||||||
</div>
|
|
||||||
<button
|
|
||||||
class="btn collection-button"
|
|
||||||
@click="(event) => $refs.modal_collection.show(event)"
|
|
||||||
>
|
|
||||||
<PlusIcon aria-hidden="true" />
|
|
||||||
Create new collection
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</PopoutMenu>
|
|
||||||
<nuxt-link v-else v-tooltip="'Save'" to="/auth/sign-in" aria-label="Save">
|
|
||||||
<BookmarkIcon aria-hidden="true" />
|
|
||||||
</nuxt-link>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled v-if="auth.user && currentMember" size="large" circular>
|
|
||||||
<nuxt-link
|
|
||||||
:to="`/${project.project_type}/${project.slug ? project.slug : project.id}/settings`"
|
|
||||||
>
|
|
||||||
<SettingsIcon aria-hidden="true" />
|
|
||||||
</nuxt-link>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled size="large" circular type="transparent">
|
|
||||||
<OverflowMenu
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
id: 'analytics',
|
|
||||||
link: `/${project.project_type}/${project.slug ? project.slug : project.id}/settings/analytics`,
|
|
||||||
hoverOnly: true,
|
|
||||||
shown: auth.user && !!currentMember,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
shown: auth.user && !!currentMember,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'moderation-checklist',
|
|
||||||
action: () => (showModerationChecklist = true),
|
|
||||||
color: 'orange',
|
|
||||||
hoverOnly: true,
|
|
||||||
shown:
|
|
||||||
auth.user &&
|
|
||||||
tags.staffRoles.includes(auth.user.role) &&
|
|
||||||
!showModerationChecklist,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
divider: true,
|
|
||||||
shown:
|
|
||||||
auth.user &&
|
|
||||||
tags.staffRoles.includes(auth.user.role) &&
|
|
||||||
!showModerationChecklist,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'report',
|
|
||||||
action: () =>
|
|
||||||
auth.user ? reportProject(project.id) : navigateTo('/auth/sign-in'),
|
|
||||||
color: 'red',
|
|
||||||
hoverOnly: true,
|
|
||||||
},
|
|
||||||
{ id: 'copy-id', action: () => copyId() },
|
|
||||||
]"
|
|
||||||
aria-label="More options"
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon aria-hidden="true" />
|
|
||||||
<template #analytics>
|
|
||||||
<ChartIcon aria-hidden="true" />
|
|
||||||
Analytics
|
|
||||||
</template>
|
|
||||||
<template #moderation-checklist>
|
|
||||||
<ScaleIcon aria-hidden="true" />
|
|
||||||
Review project
|
|
||||||
</template>
|
|
||||||
<template #report>
|
|
||||||
<ReportIcon aria-hidden="true" />
|
|
||||||
Report
|
|
||||||
</template>
|
|
||||||
<template #copy-id>
|
|
||||||
<ClipboardCopyIcon aria-hidden="true" />
|
|
||||||
Copy ID
|
|
||||||
</template>
|
|
||||||
</OverflowMenu>
|
|
||||||
</ButtonStyled>
|
|
||||||
</template>
|
|
||||||
</ContentPageHeader>
|
|
||||||
<ProjectMemberHeader
|
<ProjectMemberHeader
|
||||||
v-if="currentMember"
|
v-if="currentMember"
|
||||||
:project="project"
|
:project="project"
|
||||||
@@ -689,13 +701,13 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<h3>{{ formatMessage(compatibilityMessages.environments) }}</h3>
|
<h3>{{ formatMessage(compatibilityMessages.environments) }}</h3>
|
||||||
<div class="tag-list">
|
<div class="status-list">
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
(project.client_side === 'required' && project.server_side !== 'required') ||
|
(project.client_side === 'required' && project.server_side !== 'required') ||
|
||||||
(project.client_side === 'optional' && project.server_side === 'optional')
|
(project.client_side === 'optional' && project.server_side === 'optional')
|
||||||
"
|
"
|
||||||
class="tag-list__item"
|
class="status-list__item"
|
||||||
>
|
>
|
||||||
<ClientIcon aria-hidden="true" />
|
<ClientIcon aria-hidden="true" />
|
||||||
Client-side
|
Client-side
|
||||||
@@ -705,29 +717,34 @@
|
|||||||
(project.server_side === 'required' && project.client_side !== 'required') ||
|
(project.server_side === 'required' && project.client_side !== 'required') ||
|
||||||
(project.client_side === 'optional' && project.server_side === 'optional')
|
(project.client_side === 'optional' && project.server_side === 'optional')
|
||||||
"
|
"
|
||||||
class="tag-list__item"
|
class="status-list__item"
|
||||||
>
|
>
|
||||||
<ServerIcon aria-hidden="true" />
|
<ServerIcon aria-hidden="true" />
|
||||||
Server-side
|
Server-side
|
||||||
</div>
|
</div>
|
||||||
<div v-if="false" class="tag-list__item">
|
<div v-if="false" class="status-list__item">
|
||||||
<UserIcon aria-hidden="true" />
|
<UserIcon aria-hidden="true" />
|
||||||
Singleplayer
|
Singleplayer
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="project.client_side === 'required' && project.server_side === 'required'"
|
||||||
project.project_type !== 'datapack' &&
|
class="status-list__item"
|
||||||
((project.client_side === 'required' && project.server_side === 'required') ||
|
|
||||||
project.client_side === 'optional' ||
|
|
||||||
(project.client_side === 'required' && project.server_side === 'optional') ||
|
|
||||||
project.server_side === 'optional' ||
|
|
||||||
(project.server_side === 'required' && project.client_side === 'optional'))
|
|
||||||
"
|
|
||||||
class="tag-list__item"
|
|
||||||
>
|
>
|
||||||
<MonitorSmartphoneIcon aria-hidden="true" />
|
<MonitorSmartphoneIcon aria-hidden="true" />
|
||||||
Client and server
|
Client and server
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-else-if="
|
||||||
|
project.client_side === 'optional' ||
|
||||||
|
(project.client_side === 'required' && project.server_side === 'optional') ||
|
||||||
|
project.server_side === 'optional' ||
|
||||||
|
(project.server_side === 'required' && project.client_side === 'optional')
|
||||||
|
"
|
||||||
|
class="status-list__item"
|
||||||
|
>
|
||||||
|
<MonitorSmartphoneIcon aria-hidden="true" />
|
||||||
|
Client and server <span class="text-sm">(optional)</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@@ -1027,7 +1044,6 @@ import {
|
|||||||
OverflowMenu,
|
OverflowMenu,
|
||||||
PopoutMenu,
|
PopoutMenu,
|
||||||
ScrollablePanel,
|
ScrollablePanel,
|
||||||
ContentPageHeader,
|
|
||||||
} from "@modrinth/ui";
|
} from "@modrinth/ui";
|
||||||
import { formatCategory, isRejected, isStaff, isUnderReview, renderString } from "@modrinth/utils";
|
import { formatCategory, isRejected, isStaff, isUnderReview, renderString } from "@modrinth/utils";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
@@ -1398,7 +1414,7 @@ try {
|
|||||||
|
|
||||||
versions = shallowRef(toRaw(versions));
|
versions = shallowRef(toRaw(versions));
|
||||||
featuredVersions = shallowRef(toRaw(featuredVersions));
|
featuredVersions = shallowRef(toRaw(featuredVersions));
|
||||||
} catch {
|
} catch (error) {
|
||||||
throw createError({
|
throw createError({
|
||||||
fatal: true,
|
fatal: true,
|
||||||
statusCode: 404,
|
statusCode: 404,
|
||||||
@@ -1718,6 +1734,10 @@ const navLinks = computed(() => {
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
.normal-page__header {
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
|||||||
@@ -139,8 +139,8 @@
|
|||||||
class="image"
|
class="image"
|
||||||
:class="{ 'zoomed-in': zoomedIn }"
|
:class="{ 'zoomed-in': zoomedIn }"
|
||||||
:src="
|
:src="
|
||||||
expandedGalleryItem.raw_url
|
expandedGalleryItem.url
|
||||||
? expandedGalleryItem.raw_url
|
? expandedGalleryItem.url
|
||||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||||
"
|
"
|
||||||
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
||||||
@@ -165,8 +165,8 @@
|
|||||||
class="open circle-button"
|
class="open circle-button"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
:href="
|
:href="
|
||||||
expandedGalleryItem.raw_url
|
expandedGalleryItem.url
|
||||||
? expandedGalleryItem.raw_url
|
? expandedGalleryItem.url
|
||||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ const props = defineProps<{
|
|||||||
members: User[];
|
members: User[];
|
||||||
currentMember: User;
|
currentMember: User;
|
||||||
dependencies: Dependency[];
|
dependencies: Dependency[];
|
||||||
resetProject: (opts?: { dedupe?: "cancel" | "defer" }) => Promise<void>;
|
resetProject: Function;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const version = computed(() => {
|
const version = computed(() => {
|
||||||
|
|||||||
@@ -567,7 +567,6 @@
|
|||||||
:show-labels="false"
|
:show-labels="false"
|
||||||
:limit="6"
|
:limit="6"
|
||||||
:hide-selected="true"
|
:hide-selected="true"
|
||||||
:custom-label="(version) => version"
|
|
||||||
placeholder="Choose versions..."
|
placeholder="Choose versions..."
|
||||||
/>
|
/>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ const windowsLink = ref(null);
|
|||||||
const linuxLinks = {
|
const linuxLinks = {
|
||||||
appImage: null,
|
appImage: null,
|
||||||
deb: null,
|
deb: null,
|
||||||
rpm: null,
|
|
||||||
thirdParty: "https://support.modrinth.com/en/articles/9298760",
|
thirdParty: "https://support.modrinth.com/en/articles/9298760",
|
||||||
};
|
};
|
||||||
const macLinks = {
|
const macLinks = {
|
||||||
@@ -58,7 +57,6 @@ macLinks.intel = launcherUpdates.value.platforms["darwin-x86_64"].install_urls[0
|
|||||||
windowsLink.value = launcherUpdates.value.platforms["windows-x86_64"].install_urls[0];
|
windowsLink.value = launcherUpdates.value.platforms["windows-x86_64"].install_urls[0];
|
||||||
linuxLinks.appImage = launcherUpdates.value.platforms["linux-x86_64"].install_urls[1];
|
linuxLinks.appImage = launcherUpdates.value.platforms["linux-x86_64"].install_urls[1];
|
||||||
linuxLinks.deb = launcherUpdates.value.platforms["linux-x86_64"].install_urls[0];
|
linuxLinks.deb = launcherUpdates.value.platforms["linux-x86_64"].install_urls[0];
|
||||||
linuxLinks.rpm = launcherUpdates.value.platforms["linux-x86_64"].install_urls[2];
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
os.value = navigator?.platform.toString();
|
os.value = navigator?.platform.toString();
|
||||||
@@ -881,11 +879,7 @@ useSeoMeta({
|
|||||||
</a>
|
</a>
|
||||||
<a :href="linuxLinks.deb" download="">
|
<a :href="linuxLinks.deb" download="">
|
||||||
<DownloadIcon />
|
<DownloadIcon />
|
||||||
<span> Download the DEB </span>
|
<span> Download the Deb </span>
|
||||||
</a>
|
|
||||||
<a :href="linuxLinks.rpm" download="">
|
|
||||||
<DownloadIcon />
|
|
||||||
<span> Download the RPM </span>
|
|
||||||
</a>
|
</a>
|
||||||
<a :href="linuxLinks.thirdParty" download="">
|
<a :href="linuxLinks.thirdParty" download="">
|
||||||
<LinkIcon />
|
<LinkIcon />
|
||||||
|
|||||||
@@ -195,7 +195,7 @@ const onAuthorize = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(formatMessage(messages.noRedirectUrlError));
|
throw new Error(formatMessage(messages.noRedirectUrlError));
|
||||||
} catch {
|
} catch (error) {
|
||||||
data.$notify({
|
data.$notify({
|
||||||
group: "main",
|
group: "main",
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
@@ -222,7 +222,7 @@ const onReject = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(formatMessage(messages.noRedirectUrlError));
|
throw new Error(formatMessage(messages.noRedirectUrlError));
|
||||||
} catch {
|
} catch (error) {
|
||||||
data.$notify({
|
data.$notify({
|
||||||
group: "main",
|
group: "main",
|
||||||
title: formatMessage(commonMessages.errorNotificationTitle),
|
title: formatMessage(commonMessages.errorNotificationTitle),
|
||||||
|
|||||||
@@ -145,7 +145,7 @@ if (route.query.flow) {
|
|||||||
await useAuth(auth.value.token);
|
await useAuth(auth.value.token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch {
|
} catch (err) {
|
||||||
success.value = false;
|
success.value = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ const visibility = ref(collection.value.status);
|
|||||||
const removeProjects = ref([]);
|
const removeProjects = ref([]);
|
||||||
|
|
||||||
async function unfollowProject(project) {
|
async function unfollowProject(project) {
|
||||||
await userFollowProject(project);
|
await userUnfollowProject(project);
|
||||||
projects.value = projects.value.filter((x) => x.id !== project.id);
|
projects.value = projects.value.filter((x) => x.id !== project.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -88,6 +88,20 @@
|
|||||||
></span
|
></span
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid-display__item">
|
||||||
|
<div class="label">Current balance</div>
|
||||||
|
<div class="value">
|
||||||
|
{{ $formatMoney(auth.user.payout_data.balance, true) }}
|
||||||
|
</div>
|
||||||
|
<NuxtLink
|
||||||
|
v-if="auth.user.payout_data.balance > 0"
|
||||||
|
class="goto-link"
|
||||||
|
to="/dashboard/revenue"
|
||||||
|
>
|
||||||
|
Withdraw earnings
|
||||||
|
<ChevronRightIcon class="featured-header-chevron" aria-hidden="true" />
|
||||||
|
</NuxtLink>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,24 +2,21 @@
|
|||||||
<div>
|
<div>
|
||||||
<section class="universal-card">
|
<section class="universal-card">
|
||||||
<h2 class="text-2xl">Revenue</h2>
|
<h2 class="text-2xl">Revenue</h2>
|
||||||
<div v-if="userBalance.available >= minWithdraw">
|
<div v-if="auth.user.payout_data.balance >= minWithdraw">
|
||||||
<p>
|
<p>
|
||||||
You have
|
You have
|
||||||
<strong>{{ $formatMoney(userBalance.available) }}</strong>
|
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong>
|
||||||
available to withdraw. <strong>{{ $formatMoney(userBalance.pending) }}</strong> of your
|
available to withdraw.
|
||||||
balance is <nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p v-else>
|
<p v-else>
|
||||||
You have made
|
You have made
|
||||||
<strong>{{ $formatMoney(userBalance.available) }}</strong
|
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong
|
||||||
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
|
>, which is under the minimum of ${{ minWithdraw }} to withdraw.
|
||||||
<strong>{{ $formatMoney(userBalance.pending) }}</strong> of your balance is
|
|
||||||
<nuxt-link class="text-link" to="/legal/cmp-info#pending">pending</nuxt-link>.
|
|
||||||
</p>
|
</p>
|
||||||
<div class="input-group mt-4">
|
<div class="input-group mt-4">
|
||||||
<nuxt-link
|
<nuxt-link
|
||||||
v-if="userBalance.available >= minWithdraw"
|
v-if="auth.user.payout_data.balance >= minWithdraw"
|
||||||
class="iconified-button brand-button"
|
class="iconified-button brand-button"
|
||||||
to="/dashboard/revenue/withdraw"
|
to="/dashboard/revenue/withdraw"
|
||||||
>
|
>
|
||||||
@@ -84,10 +81,6 @@ import { TransferIcon, HistoryIcon, PayPalIcon, SaveIcon, XIcon } from "@modrint
|
|||||||
const auth = await useAuth();
|
const auth = await useAuth();
|
||||||
const minWithdraw = ref(0.01);
|
const minWithdraw = ref(0.01);
|
||||||
|
|
||||||
const { data: userBalance } = await useAsyncData(`payout/balance`, () =>
|
|
||||||
useBaseFetch(`payout/balance`, { apiVersion: 3 }),
|
|
||||||
);
|
|
||||||
|
|
||||||
async function updateVenmo() {
|
async function updateVenmo() {
|
||||||
startLoading();
|
startLoading();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
<p>
|
<p>
|
||||||
You are initiating a transfer of your revenue from Modrinth's Creator Monetization Program.
|
You are initiating a transfer of your revenue from Modrinth's Creator Monetization Program.
|
||||||
How much of your
|
How much of your
|
||||||
<strong>{{ $formatMoney(userBalance.available) }}</strong> balance would you like to transfer
|
<strong>{{ $formatMoney(auth.user.payout_data.balance) }}</strong> balance would you like to
|
||||||
transfer to {{ selectedMethod.name }}?
|
transfer to {{ selectedMethod.name }}?
|
||||||
</p>
|
</p>
|
||||||
<div class="confirmation-input">
|
<div class="confirmation-input">
|
||||||
@@ -212,13 +212,10 @@ const country = ref(
|
|||||||
countries.value.find((x) => x.id === (auth.value.user.payout_data.paypal_region ?? "US")),
|
countries.value.find((x) => x.id === (auth.value.user.payout_data.paypal_region ?? "US")),
|
||||||
);
|
);
|
||||||
|
|
||||||
const [{ data: userBalance }, { data: payoutMethods, refresh: refreshPayoutMethods }] =
|
const { data: payoutMethods, refresh: refreshPayoutMethods } = await useAsyncData(
|
||||||
await Promise.all([
|
`payout/methods?country=${country.value.id}`,
|
||||||
useAsyncData(`payout/balance`, () => useBaseFetch(`payout/balance`, { apiVersion: 3 })),
|
() => useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
|
||||||
useAsyncData(`payout/methods?country=${country.value.id}`, () =>
|
);
|
||||||
useBaseFetch(`payout/methods?country=${country.value.id}`, { apiVersion: 3 }),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const selectedMethodId = ref(payoutMethods.value[0].id);
|
const selectedMethodId = ref(payoutMethods.value[0].id);
|
||||||
const selectedMethod = computed(() =>
|
const selectedMethod = computed(() =>
|
||||||
@@ -298,10 +295,10 @@ const knownErrors = computed(() => {
|
|||||||
if (!parsedAmount.value && amount.value.length > 0) {
|
if (!parsedAmount.value && amount.value.length > 0) {
|
||||||
errors.push(`${amount.value} is not a valid amount`);
|
errors.push(`${amount.value} is not a valid amount`);
|
||||||
} else if (
|
} else if (
|
||||||
parsedAmount.value > userBalance.value.available ||
|
parsedAmount.value > auth.value.user.payout_data.balance ||
|
||||||
parsedAmount.value > maxWithdrawAmount.value
|
parsedAmount.value > maxWithdrawAmount.value
|
||||||
) {
|
) {
|
||||||
const maxAmount = Math.min(userBalance.value.available, maxWithdrawAmount.value);
|
const maxAmount = Math.min(auth.value.user.payout_data.balance, maxWithdrawAmount.value);
|
||||||
errors.push(`The amount must be no more than ${data.$formatMoney(maxAmount)}`);
|
errors.push(`The amount must be no more than ${data.$formatMoney(maxAmount)}`);
|
||||||
} else if (parsedAmount.value <= fees.value || parsedAmount.value < minWithdrawAmount.value) {
|
} else if (parsedAmount.value <= fees.value || parsedAmount.value < minWithdrawAmount.value) {
|
||||||
const minAmount = Math.max(fees.value + 0.01, minWithdrawAmount.value);
|
const minAmount = Math.max(fees.value + 0.01, minWithdrawAmount.value);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="markdown-body">
|
<div class="markdown-body">
|
||||||
<h1>Rewards Program Information</h1>
|
<h1>Rewards Program Information</h1>
|
||||||
<p><em>Last modified: Sep 12, 2024</em></p>
|
<p><em>Last modified: May 13, 2024</em></p>
|
||||||
<p>
|
<p>
|
||||||
This page was created for transparency for how the rewards program works on Modrinth. Feel
|
This page was created for transparency for how the rewards program works on Modrinth. Feel
|
||||||
free to join our Discord or email
|
free to join our Discord or email
|
||||||
@@ -15,11 +15,10 @@
|
|||||||
<h2>Rewards Distribution</h2>
|
<h2>Rewards Distribution</h2>
|
||||||
<p>
|
<p>
|
||||||
We collect ad revenue on our website and app through our ad network
|
We collect ad revenue on our website and app through our ad network
|
||||||
<a href="https://adrinth.com">Adrinth</a>, which is powered by
|
<a href="https://adrinth.com">Adrinth</a>. We then distribute this ad revenue to creators.
|
||||||
<a href="https://aditude.io">Aditude</a>. We then distribute this ad revenue to creators.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
The advertising revenue of the entire website and app is split 75% to creators and 25% to
|
The advertising revenue of the entire website and app is split 90% to creators and 10% to
|
||||||
Modrinth.
|
Modrinth.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
@@ -43,10 +42,10 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<p>In this scenario, the earnings for each creator and Modrinth would be as follows:</p>
|
<p>In this scenario, the earnings for each creator and Modrinth would be as follows:</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Modrinth: $25 (25% of $100, the site's earnings for the day)</li>
|
<li>Modrinth: $10 (10% of $100, the site's earnings for the day)</li>
|
||||||
<li>User A: $48.91 ($75 * (10 + 30 + 100 + 10)/230)</li>
|
<li>User A: $58.69 ($90 * (10 + 30 + 100 + 10)/230)</li>
|
||||||
<li>User B: $10.43 (0.4 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
<li>User B: $12.52 (0.4 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||||
<li>User C: $15.65 (0.6 * $75 * (50 + 20 + 10 + 0)/230)</li>
|
<li>User C: $18.78 (0.6 * $90 * (50 + 20 + 10 + 0)/230)</li>
|
||||||
<li>Note: 230 is the sum of all page views and in-app downloads from above</li>
|
<li>Note: 230 is the sum of all page views and in-app downloads from above</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
@@ -73,62 +72,19 @@
|
|||||||
</p>
|
</p>
|
||||||
<h3>What methods can I use withdraw money from my account? Are there any fees?</h3>
|
<h3>What methods can I use withdraw money from my account? Are there any fees?</h3>
|
||||||
<p>
|
<p>
|
||||||
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. Gift card
|
Right now, you can use PayPal or Venmo to withdraw money from your Modrinth account. We are
|
||||||
withdrawal is also available. We are working on more methods to withdraw money from your
|
working on more methods to withdraw money from your account. There are fees to withdraw money
|
||||||
account. There are fees to withdraw money from your Modrinth account—see the revenue page in
|
from your Modrinth account—see the revenue page in your dashboard for more information.
|
||||||
your dashboard for more information.
|
|
||||||
</p>
|
</p>
|
||||||
<h3 id="pending">What does "pending" revenue mean in my dashboard?</h3>
|
<h3>Modrinth used to give 100% of project page revenue to creators. What changed?</h3>
|
||||||
<p>
|
<p>
|
||||||
Modrinth receives ad revenue from our ad providers on a NET 60 day basis. Due to this, not all
|
While this is true, our new system (as of 08/05/23) gives more of the site's revenue to
|
||||||
revenue is immediately available to withdraw. We pay creators as soon as we receive the money
|
creators, so creators will earn more. In the old system, we would earn revenue through
|
||||||
from our ad providers, which is 60 days after the last day of each month. This table outlines
|
advertisements in search and user profile pages. This amounted on average each month to about
|
||||||
some example dates of how NET 60 payments are made:
|
15-20% of the site's total advertising revenue (so a 80-85% split to creators). The new system
|
||||||
</p>
|
gives creators more revenue and a more favorable split towards creators (90%).
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Date</th>
|
|
||||||
<th>Payment available date</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
|
||||||
<td>January 1st</td>
|
|
||||||
<td>March 31st</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>January 15th</td>
|
|
||||||
<td>March 31st</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>March 3rd</td>
|
|
||||||
<td>May 30th</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>June 30th</td>
|
|
||||||
<td>August 29th</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>July 14th</td>
|
|
||||||
<td>September 29th</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>October 12th</td>
|
|
||||||
<td>December 30th</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<h3>How do I know Modrinth is being transparent about revenue?</h3>
|
|
||||||
<p>
|
|
||||||
We aim to be as transparent as possible with creator revenue. All of our code is open source,
|
|
||||||
including our
|
|
||||||
<a href="https://github.com/modrinth/labrinth/blob/master/src/queue/payouts.rs#L561">
|
|
||||||
revenue distribution system </a
|
|
||||||
>. We also have an
|
|
||||||
<a href="https://api.modrinth.com/v3/payout/platform_revenue">API route</a> that allows users
|
|
||||||
to query exact daily revenue for the site.
|
|
||||||
</p>
|
</p>
|
||||||
|
<h3></h3>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -1,260 +1,240 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div v-if="organization" class="normal-page">
|
||||||
v-if="organization"
|
<div class="normal-page__sidebar">
|
||||||
class="experimental-styles-within new-page sidebar"
|
<div v-if="routeHasSettings" class="universal-card">
|
||||||
:class="{ 'alt-layout': cosmetics.leftContentLayout || routeHasSettings }"
|
<Breadcrumbs
|
||||||
>
|
current-title="Settings"
|
||||||
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
:link-stack="[
|
||||||
<template v-if="routeHasSettings">
|
{ href: `/dashboard/organizations`, label: 'Organizations' },
|
||||||
<div class="normal-page__sidebar">
|
{
|
||||||
|
href: `/organization/${organization.slug}`,
|
||||||
|
label: organization.name,
|
||||||
|
allowTrimming: true,
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="page-header__settings">
|
||||||
|
<Avatar size="sm" :src="organization.icon_url" />
|
||||||
|
<div class="title-section">
|
||||||
|
<h2 class="settings-title">
|
||||||
|
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
||||||
|
{{ organization.name }}
|
||||||
|
</nuxt-link>
|
||||||
|
</h2>
|
||||||
|
<span>
|
||||||
|
{{ $formatNumber(acceptedMembers?.length || 0) }}
|
||||||
|
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h2>Organization settings</h2>
|
||||||
|
|
||||||
|
<NavStack>
|
||||||
|
<NavStackItem :link="`/organization/${organization.slug}/settings`" label="Overview">
|
||||||
|
<SettingsIcon />
|
||||||
|
</NavStackItem>
|
||||||
|
<NavStackItem
|
||||||
|
:link="`/organization/${organization.slug}/settings/members`"
|
||||||
|
label="Members"
|
||||||
|
>
|
||||||
|
<UsersIcon />
|
||||||
|
</NavStackItem>
|
||||||
|
<NavStackItem
|
||||||
|
:link="`/organization/${organization.slug}/settings/projects`"
|
||||||
|
label="Projects"
|
||||||
|
>
|
||||||
|
<BoxIcon />
|
||||||
|
</NavStackItem>
|
||||||
|
<NavStackItem
|
||||||
|
:link="`/organization/${organization.slug}/settings/analytics`"
|
||||||
|
label="Analytics"
|
||||||
|
>
|
||||||
|
<ChartIcon />
|
||||||
|
</NavStackItem>
|
||||||
|
</NavStack>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
<div class="universal-card">
|
<div class="universal-card">
|
||||||
<Breadcrumbs
|
<div class="page-header__icon">
|
||||||
current-title="Settings"
|
<Avatar size="md" :src="organization.icon_url" />
|
||||||
:link-stack="[
|
|
||||||
{ href: `/dashboard/organizations`, label: 'Organizations' },
|
|
||||||
{
|
|
||||||
href: `/organization/${organization.slug}`,
|
|
||||||
label: organization.name,
|
|
||||||
allowTrimming: true,
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
/>
|
|
||||||
<div class="page-header__settings">
|
|
||||||
<Avatar size="sm" :src="organization.icon_url" />
|
|
||||||
<div class="title-section">
|
|
||||||
<h2 class="settings-title">
|
|
||||||
<nuxt-link :to="`/organization/${organization.slug}/settings`">
|
|
||||||
{{ organization.name }}
|
|
||||||
</nuxt-link>
|
|
||||||
</h2>
|
|
||||||
<span>
|
|
||||||
{{ $formatNumber(acceptedMembers?.length || 0) }}
|
|
||||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2>Organization settings</h2>
|
<div class="page-header__text">
|
||||||
|
<h1 class="title">{{ organization.name }}</h1>
|
||||||
|
|
||||||
<NavStack>
|
<div>
|
||||||
<NavStackItem :link="`/organization/${organization.slug}/settings`" label="Overview">
|
<span class="organization-label"><OrganizationIcon /> Organization</span>
|
||||||
<SettingsIcon />
|
</div>
|
||||||
</NavStackItem>
|
|
||||||
<NavStackItem
|
<div class="organization-description">
|
||||||
:link="`/organization/${organization.slug}/settings/members`"
|
<div class="metadata-item markdown-body collection-description">
|
||||||
label="Members"
|
<p>{{ organization.description }}</p>
|
||||||
>
|
</div>
|
||||||
<UsersIcon />
|
|
||||||
</NavStackItem>
|
<hr class="card-divider" />
|
||||||
<NavStackItem
|
|
||||||
:link="`/organization/${organization.slug}/settings/projects`"
|
<div class="primary-stat">
|
||||||
label="Projects"
|
<UserIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
>
|
<div class="primary-stat__text">
|
||||||
<BoxIcon />
|
<span class="primary-stat__counter">
|
||||||
</NavStackItem>
|
{{ $formatNumber(acceptedMembers?.length || 0) }}
|
||||||
<NavStackItem
|
</span>
|
||||||
:link="`/organization/${organization.slug}/settings/analytics`"
|
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||||
label="Analytics"
|
</div>
|
||||||
>
|
</div>
|
||||||
<ChartIcon />
|
|
||||||
</NavStackItem>
|
<div class="primary-stat">
|
||||||
</NavStack>
|
<BoxIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
|
<div class="primary-stat__text">
|
||||||
|
<span class="primary-stat__counter">
|
||||||
|
{{ $formatNumber(projects?.length || 0) }}
|
||||||
|
</span>
|
||||||
|
project<span v-if="projects?.length !== 1">s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="primary-stat no-margin">
|
||||||
|
<DownloadIcon class="primary-stat__icon" aria-hidden="true" />
|
||||||
|
<div class="primary-stat__text">
|
||||||
|
<span class="primary-stat__counter">
|
||||||
|
{{ formatCompactNumber(sumDownloads) }}
|
||||||
|
</span>
|
||||||
|
download<span v-if="sumDownloads !== 1">s</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div class="normal-page__content">
|
|
||||||
<NuxtPage />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="normal-page__header py-4">
|
|
||||||
<ContentPageHeader>
|
|
||||||
<template #icon>
|
|
||||||
<Avatar :src="organization.icon_url" :alt="organization.name" size="96px" />
|
|
||||||
</template>
|
|
||||||
<template #title>
|
|
||||||
{{ organization.name }}
|
|
||||||
</template>
|
|
||||||
<template #title-suffix>
|
|
||||||
<div class="ml-1 flex items-center gap-2 font-semibold">
|
|
||||||
<OrganizationIcon /> Organization
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #summary>
|
|
||||||
{{ organization.description }}
|
|
||||||
</template>
|
|
||||||
<template #stats>
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
|
||||||
>
|
|
||||||
<UsersIcon class="h-6 w-6 text-secondary" />
|
|
||||||
{{ formatCompactNumber(acceptedMembers?.length || 0) }}
|
|
||||||
members
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
|
||||||
>
|
|
||||||
<BoxIcon class="h-6 w-6 text-secondary" />
|
|
||||||
{{ formatCompactNumber(projects?.length || 0) }}
|
|
||||||
projects
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center gap-2 font-semibold">
|
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
|
||||||
{{ formatCompactNumber(sumDownloads) }}
|
|
||||||
downloads
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<ButtonStyled v-if="auth.user && currentMember" size="large">
|
|
||||||
<NuxtLink :to="`/organization/${organization.slug}/settings`">
|
|
||||||
<SettingsIcon aria-hidden="true" />
|
|
||||||
Manage
|
|
||||||
</NuxtLink>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled size="large" circular type="transparent">
|
|
||||||
<OverflowMenu
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
id: 'manage-projects',
|
|
||||||
action: () =>
|
|
||||||
navigateTo('/organization/' + organization.slug + '/settings/projects'),
|
|
||||||
hoverOnly: true,
|
|
||||||
shown: auth.user && currentMember,
|
|
||||||
},
|
|
||||||
{ divider: true, shown: auth.user && currentMember },
|
|
||||||
{ id: 'copy-id', action: () => copyId() },
|
|
||||||
]"
|
|
||||||
aria-label="More options"
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon aria-hidden="true" />
|
|
||||||
<template #manage-projects>
|
|
||||||
<BoxIcon aria-hidden="true" />
|
|
||||||
Manage projects
|
|
||||||
</template>
|
|
||||||
<template #copy-id>
|
|
||||||
<ClipboardCopyIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(commonMessages.copyIdButton) }}
|
|
||||||
</template>
|
|
||||||
</OverflowMenu>
|
|
||||||
</ButtonStyled>
|
|
||||||
</template>
|
|
||||||
</ContentPageHeader>
|
|
||||||
</div>
|
|
||||||
<div class="normal-page__sidebar">
|
|
||||||
<AdPlaceholder
|
<AdPlaceholder
|
||||||
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="card flex-card">
|
<div class="creator-list universal-card">
|
||||||
<h2>Members</h2>
|
<div class="title-and-link">
|
||||||
<div class="details-list">
|
<h3>Members</h3>
|
||||||
<template v-for="member in acceptedMembers" :key="member.user.id">
|
|
||||||
<nuxt-link
|
|
||||||
class="details-list__item details-list__item--type-large"
|
|
||||||
:to="`/user/${member.user.username}`"
|
|
||||||
>
|
|
||||||
<Avatar :src="member.user.avatar_url" circle />
|
|
||||||
<div class="rows">
|
|
||||||
<span class="flex items-center gap-1">
|
|
||||||
{{ member.user.username }}
|
|
||||||
<CrownIcon
|
|
||||||
v-if="member.is_owner"
|
|
||||||
v-tooltip="'Organization owner'"
|
|
||||||
class="text-brand-orange"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span class="details-list__item__text--style-secondary">
|
|
||||||
{{ member.role ? member.role : "Member" }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</nuxt-link>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="normal-page__content">
|
|
||||||
<div v-if="isInvited" class="universal-card information invited">
|
|
||||||
<h2>Invitation to join {{ organization.name }}</h2>
|
|
||||||
<p>You have been invited to join {{ organization.name }}.</p>
|
|
||||||
<div class="input-group">
|
|
||||||
<button class="iconified-button brand-button" @click="onAcceptInvite">
|
|
||||||
<CheckIcon />Accept
|
|
||||||
</button>
|
|
||||||
<button class="iconified-button danger-button" @click="onDeclineInvite">
|
|
||||||
<XIcon />Decline
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
|
||||||
<NavTabs :links="navLinks" />
|
|
||||||
</div>
|
|
||||||
<template v-if="projects?.length > 0">
|
|
||||||
<div class="project-list display-mode--list">
|
|
||||||
<ProjectCard
|
|
||||||
v-for="project in (route.params.projectType !== undefined
|
|
||||||
? projects.filter((x) =>
|
|
||||||
x.project_types.includes(
|
|
||||||
route.params.projectType.substr(0, route.params.projectType.length - 1),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: projects
|
|
||||||
)
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => b.downloads - a.downloads)"
|
|
||||||
:id="project.slug || project.id"
|
|
||||||
:key="project.id"
|
|
||||||
:name="project.name"
|
|
||||||
:display="cosmetics.searchDisplayMode.user"
|
|
||||||
:featured-image="project.gallery.find((element) => element.featured)?.url"
|
|
||||||
project-type-url="project"
|
|
||||||
:description="project.summary"
|
|
||||||
:created-at="project.published"
|
|
||||||
:updated-at="project.updated"
|
|
||||||
:downloads="project.downloads.toString()"
|
|
||||||
:follows="project.followers.toString()"
|
|
||||||
:icon-url="project.icon_url"
|
|
||||||
:categories="project.categories"
|
|
||||||
:client-side="project.client_side"
|
|
||||||
:server-side="project.server_side"
|
|
||||||
:status="
|
|
||||||
auth.user && (auth.user.id === user.id || tags.staffRoles.includes(auth.user.role))
|
|
||||||
? project.status
|
|
||||||
: null
|
|
||||||
"
|
|
||||||
:type="project.project_types[0] ?? 'project'"
|
|
||||||
:color="project.color"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-else-if="true" class="error">
|
<template v-for="member in acceptedMembers" :key="member.user.id">
|
||||||
<UpToDate class="icon" /><br />
|
<nuxt-link class="creator button-base" :to="`/user/${member.user.username}`">
|
||||||
<span class="preserve-lines text">
|
<Avatar :src="member.user.avatar_url" circle />
|
||||||
This organization doesn't have any projects yet.
|
<p class="name">
|
||||||
<template v-if="isPermission(currentMember?.organization_permissions, 1 << 4)">
|
{{ member.user.username }}
|
||||||
Would you like to
|
<CrownIcon v-if="member.is_owner" v-tooltip="'Organization owner'" />
|
||||||
<a class="link" @click="$refs.modal_creation.show()">create one</a>?
|
</p>
|
||||||
</template>
|
<p class="role">{{ member.role }}</p>
|
||||||
</span>
|
</nuxt-link>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
<div v-if="!routeHasSettings" class="normal-page__content">
|
||||||
|
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
||||||
|
<div v-if="isInvited" class="universal-card information invited">
|
||||||
|
<h2>Invitation to join {{ organization.name }}</h2>
|
||||||
|
<p>You have been invited to join {{ organization.name }}.</p>
|
||||||
|
<div class="input-group">
|
||||||
|
<button class="iconified-button brand-button" @click="onAcceptInvite">
|
||||||
|
<CheckIcon />Accept
|
||||||
|
</button>
|
||||||
|
<button class="iconified-button danger-button" @click="onDeclineInvite">
|
||||||
|
<XIcon />Decline
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<nav class="navigation-card">
|
||||||
|
<NavRow
|
||||||
|
:links="[
|
||||||
|
{
|
||||||
|
label: formatMessage(commonMessages.allProjectType),
|
||||||
|
href: `/organization/${organization.slug}`,
|
||||||
|
},
|
||||||
|
...projectTypes.map((x) => {
|
||||||
|
return {
|
||||||
|
label: formatMessage(getProjectTypeMessage(x, true)),
|
||||||
|
href: `/organization/${organization.slug}/${x}s`,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div v-if="auth.user && currentMember" class="input-group">
|
||||||
|
<nuxt-link :to="`/organization/${organization.slug}/settings`" class="iconified-button">
|
||||||
|
<SettingsIcon /> Manage
|
||||||
|
</nuxt-link>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
<template v-if="projects?.length > 0">
|
||||||
|
<div class="project-list display-mode--list">
|
||||||
|
<ProjectCard
|
||||||
|
v-for="project in (route.params.projectType !== undefined
|
||||||
|
? projects.filter((x) =>
|
||||||
|
x.project_types.includes(
|
||||||
|
route.params.projectType.substr(0, route.params.projectType.length - 1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: projects
|
||||||
|
)
|
||||||
|
.slice()
|
||||||
|
.sort((a, b) => b.downloads - a.downloads)"
|
||||||
|
:id="project.slug || project.id"
|
||||||
|
:key="project.id"
|
||||||
|
:name="project.name"
|
||||||
|
:display="cosmetics.searchDisplayMode.user"
|
||||||
|
:featured-image="project.gallery.find((element) => element.featured)?.url"
|
||||||
|
project-type-url="project"
|
||||||
|
:description="project.summary"
|
||||||
|
:created-at="project.published"
|
||||||
|
:updated-at="project.updated"
|
||||||
|
:downloads="project.downloads.toString()"
|
||||||
|
:follows="project.followers.toString()"
|
||||||
|
:icon-url="project.icon_url"
|
||||||
|
:categories="project.categories"
|
||||||
|
:client-side="project.client_side"
|
||||||
|
:server-side="project.server_side"
|
||||||
|
:status="
|
||||||
|
auth.user && (auth.user.id === user.id || tags.staffRoles.includes(auth.user.role))
|
||||||
|
? project.status
|
||||||
|
: null
|
||||||
|
"
|
||||||
|
:type="project.project_types[0] ?? 'project'"
|
||||||
|
:color="project.color"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else-if="true" class="error">
|
||||||
|
<UpToDate class="icon" /><br />
|
||||||
|
<span class="preserve-lines text">
|
||||||
|
This organization doesn't have any projects yet.
|
||||||
|
<template v-if="isPermission(currentMember?.organization_permissions, 1 << 4)">
|
||||||
|
Would you like to
|
||||||
|
<a class="link" @click="$refs.modal_creation.show()">create one</a>?
|
||||||
|
</template>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<NuxtPage />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
BoxIcon,
|
BoxIcon,
|
||||||
MoreVerticalIcon,
|
UserIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
ChartIcon,
|
ChartIcon,
|
||||||
CheckIcon,
|
CheckIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
ClipboardCopyIcon,
|
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { Avatar, ButtonStyled, Breadcrumbs, ContentPageHeader, OverflowMenu } from "@modrinth/ui";
|
import { Avatar, Breadcrumbs } from "@modrinth/ui";
|
||||||
import NavStack from "~/components/ui/NavStack.vue";
|
import NavStack from "~/components/ui/NavStack.vue";
|
||||||
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
||||||
|
import NavRow from "~/components/ui/NavRow.vue";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
||||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||||
@@ -264,7 +244,6 @@ import OrganizationIcon from "~/assets/images/utils/organization.svg?component";
|
|||||||
import DownloadIcon from "~/assets/images/utils/download.svg?component";
|
import DownloadIcon from "~/assets/images/utils/download.svg?component";
|
||||||
import CrownIcon from "~/assets/images/utils/crown.svg?component";
|
import CrownIcon from "~/assets/images/utils/crown.svg?component";
|
||||||
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
||||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
|
||||||
|
|
||||||
const vintl = useVIntl();
|
const vintl = useVIntl();
|
||||||
const { formatMessage } = vintl;
|
const { formatMessage } = vintl;
|
||||||
@@ -472,26 +451,6 @@ useSeoMeta({
|
|||||||
ogDescription: organization.value.description,
|
ogDescription: organization.value.description,
|
||||||
ogImage: organization.value.icon_url ?? "https://cdn.modrinth.com/placeholder.png",
|
ogImage: organization.value.icon_url ?? "https://cdn.modrinth.com/placeholder.png",
|
||||||
});
|
});
|
||||||
|
|
||||||
const navLinks = computed(() => [
|
|
||||||
{
|
|
||||||
label: formatMessage(commonMessages.allProjectType),
|
|
||||||
href: `/organization/${organization.value.slug}`,
|
|
||||||
},
|
|
||||||
...projectTypes.value
|
|
||||||
.map((x) => {
|
|
||||||
return {
|
|
||||||
label: formatMessage(getProjectTypeMessage(x, true)),
|
|
||||||
href: `/organization/${organization.value.slug}/${x}s`,
|
|
||||||
};
|
|
||||||
})
|
|
||||||
.slice()
|
|
||||||
.sort((a, b) => a.label.localeCompare(b.label)),
|
|
||||||
]);
|
|
||||||
|
|
||||||
async function copyId() {
|
|
||||||
await navigator.clipboard.writeText(organization.value.id);
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -371,22 +371,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section id="data-export" class="universal-card">
|
|
||||||
<h2>Data export</h2>
|
|
||||||
<p>
|
|
||||||
Request a copy of all your personal data you have uploaded to Modrinth. This may take
|
|
||||||
several minutes to complete.
|
|
||||||
</p>
|
|
||||||
<a v-if="generated" class="iconified-button" :href="generated" download="export.json">
|
|
||||||
<DownloadIcon />
|
|
||||||
Download export
|
|
||||||
</a>
|
|
||||||
<button v-else class="iconified-button" :disabled="generatingExport" @click="exportData">
|
|
||||||
<template v-if="generatingExport"> <UpdatedIcon /> Generating export... </template>
|
|
||||||
<template v-else> <UpdatedIcon /> Generate export </template>
|
|
||||||
</button>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section id="delete-account" class="universal-card">
|
<section id="delete-account" class="universal-card">
|
||||||
<h2>Delete account</h2>
|
<h2>Delete account</h2>
|
||||||
<p>
|
<p>
|
||||||
@@ -407,18 +391,16 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {
|
import {
|
||||||
CheckIcon,
|
|
||||||
EditIcon,
|
EditIcon,
|
||||||
ExternalIcon,
|
|
||||||
LeftArrowIcon,
|
|
||||||
PlusIcon,
|
|
||||||
RightArrowIcon,
|
|
||||||
SaveIcon,
|
SaveIcon,
|
||||||
SettingsIcon,
|
|
||||||
TrashIcon,
|
TrashIcon,
|
||||||
UpdatedIcon,
|
PlusIcon,
|
||||||
|
SettingsIcon,
|
||||||
XIcon,
|
XIcon,
|
||||||
DownloadIcon,
|
LeftArrowIcon,
|
||||||
|
RightArrowIcon,
|
||||||
|
CheckIcon,
|
||||||
|
ExternalIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import QrcodeVue from "qrcode.vue";
|
import QrcodeVue from "qrcode.vue";
|
||||||
import GitHubIcon from "assets/icons/auth/sso-github.svg";
|
import GitHubIcon from "assets/icons/auth/sso-github.svg";
|
||||||
@@ -556,7 +538,7 @@ async function verifyTwoFactorCode() {
|
|||||||
backupCodes.value = res.backup_codes;
|
backupCodes.value = res.backup_codes;
|
||||||
twoFactorStep.value = 2;
|
twoFactorStep.value = 2;
|
||||||
await useAuth(auth.value.token);
|
await useAuth(auth.value.token);
|
||||||
} catch {
|
} catch (err) {
|
||||||
twoFactorIncorrect.value = true;
|
twoFactorIncorrect.value = true;
|
||||||
}
|
}
|
||||||
stopLoading();
|
stopLoading();
|
||||||
@@ -573,7 +555,7 @@ async function removeTwoFactor() {
|
|||||||
});
|
});
|
||||||
manageTwoFactorModal.value.hide();
|
manageTwoFactorModal.value.hide();
|
||||||
await useAuth(auth.value.token);
|
await useAuth(auth.value.token);
|
||||||
} catch {
|
} catch (err) {
|
||||||
twoFactorIncorrect.value = true;
|
twoFactorIncorrect.value = true;
|
||||||
}
|
}
|
||||||
stopLoading();
|
stopLoading();
|
||||||
@@ -632,34 +614,6 @@ async function deleteAccount() {
|
|||||||
|
|
||||||
stopLoading();
|
stopLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
const generatingExport = ref(false);
|
|
||||||
const generated = ref();
|
|
||||||
async function exportData() {
|
|
||||||
startLoading();
|
|
||||||
generatingExport.value = true;
|
|
||||||
try {
|
|
||||||
const res = await useBaseFetch("gdpr/export", {
|
|
||||||
method: "POST",
|
|
||||||
internal: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const jsonString = JSON.stringify(res, null, 2);
|
|
||||||
|
|
||||||
const blob = new Blob([jsonString], { type: "application/json" });
|
|
||||||
generated.value = URL.createObjectURL(blob);
|
|
||||||
} catch (err) {
|
|
||||||
data.$notify({
|
|
||||||
group: "main",
|
|
||||||
title: "An error occurred",
|
|
||||||
text: err.data.description,
|
|
||||||
type: "error",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
generatingExport.value = false;
|
|
||||||
stopLoading();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
canvas {
|
canvas {
|
||||||
|
|||||||
@@ -301,7 +301,7 @@ const canSubmit = computed(() => {
|
|||||||
const url = new URL(uri);
|
const url = new URL(uri);
|
||||||
return !!url;
|
return !!url;
|
||||||
});
|
});
|
||||||
} catch {
|
} catch (err) {
|
||||||
allValid = false;
|
allValid = false;
|
||||||
}
|
}
|
||||||
return filledIn && (oneValid || allValid);
|
return filledIn && (oneValid || allValid);
|
||||||
|
|||||||
@@ -17,24 +17,33 @@
|
|||||||
<span class="font-bold text-primary">
|
<span class="font-bold text-primary">
|
||||||
<template v-if="charge.product.metadata.type === 'midas'"> Modrinth Plus </template>
|
<template v-if="charge.product.metadata.type === 'midas'"> Modrinth Plus </template>
|
||||||
<template v-else> Unknown product </template>
|
<template v-else> Unknown product </template>
|
||||||
<template v-if="charge.subscription_interval">
|
<template v-if="charge.metadata.modrinth_subscription_interval">
|
||||||
{{ charge.subscription_interval }}
|
{{ charge.metadata.modrinth_subscription_interval }}
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
⋅
|
⋅
|
||||||
<span>{{ formatPrice(charge.amount, charge.currency_code) }}</span>
|
<span>{{ formatPrice(charge.amount, charge.currency) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
<Badge :color="charge.status === 'succeeded' ? 'green' : 'red'" :type="charge.status" />
|
||||||
⋅
|
⋅
|
||||||
{{ $dayjs(charge.due).format("YYYY-MM-DD") }}
|
{{ $dayjs.unix(charge.created).format("YYYY-MM-DD") }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<a
|
||||||
|
v-if="charge.receipt_url"
|
||||||
|
class="iconified-button raised-button"
|
||||||
|
:href="charge.receipt_url"
|
||||||
|
>
|
||||||
|
<ReceiptTextIcon />
|
||||||
|
View receipt
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { ReceiptTextIcon } from "@modrinth/assets";
|
||||||
import { Breadcrumbs, Badge } from "@modrinth/ui";
|
import { Breadcrumbs, Badge } from "@modrinth/ui";
|
||||||
import { products } from "~/generated/state.json";
|
import { products } from "~/generated/state.json";
|
||||||
|
|
||||||
@@ -49,17 +58,15 @@ const { data: charges } = await useAsyncData(
|
|||||||
() => useBaseFetch("billing/payments", { internal: true }),
|
() => useBaseFetch("billing/payments", { internal: true }),
|
||||||
{
|
{
|
||||||
transform: (charges) => {
|
transform: (charges) => {
|
||||||
return charges
|
return charges.map((charge) => {
|
||||||
.filter((charge) => charge.status !== "open" && charge.status !== "cancelled")
|
const product = products.find((product) =>
|
||||||
.map((charge) => {
|
product.prices.some((price) => price.id === charge.metadata.modrinth_price_id),
|
||||||
const product = products.find((product) =>
|
);
|
||||||
product.prices.some((price) => price.id === charge.price_id),
|
|
||||||
);
|
|
||||||
|
|
||||||
charge.product = product;
|
charge.product = product;
|
||||||
|
|
||||||
return charge;
|
return charge;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -8,20 +8,22 @@
|
|||||||
:title="formatMessage(cancelModalMessages.title)"
|
:title="formatMessage(cancelModalMessages.title)"
|
||||||
:description="formatMessage(cancelModalMessages.description)"
|
:description="formatMessage(cancelModalMessages.description)"
|
||||||
:proceed-label="formatMessage(cancelModalMessages.action)"
|
:proceed-label="formatMessage(cancelModalMessages.action)"
|
||||||
@proceed="cancelSubscription(cancelSubscriptionId, true)"
|
@proceed="cancelSubscription(cancelSubscriptionId)"
|
||||||
/>
|
/>
|
||||||
<div class="flex flex-wrap justify-between gap-4">
|
<div class="flex flex-wrap justify-between gap-4">
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasSubscription">
|
||||||
<span v-if="midasCharge.status === 'open'"> You're currently subscribed to: </span>
|
<span v-if="midasSubscription.status === 'active'">
|
||||||
<span v-else-if="midasCharge.status === 'processing'" class="text-orange">
|
You're currently subscribed to:
|
||||||
|
</span>
|
||||||
|
<span v-else-if="midasSubscription.status === 'payment-processing'" class="text-orange">
|
||||||
Your payment is being processed. Perks will activate once payment is complete.
|
Your payment is being processed. Perks will activate once payment is complete.
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'cancelled'">
|
<span v-else-if="midasSubscription.status === 'cancelled'">
|
||||||
You've cancelled your subscription. <br />
|
You've cancelled your subscription. <br />
|
||||||
You will retain your perks until the end of the current billing cycle.
|
You will retain your perks until the end of the current billing cycle.
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'failed'" class="text-red">
|
<span v-else-if="midasSubscription.status === 'payment-failed'" class="text-red">
|
||||||
Your subscription payment failed. Please update your payment method.
|
Your subscription payment failed. Please update your payment method.
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
@@ -47,31 +49,34 @@
|
|||||||
<div class="flex w-full flex-wrap justify-between gap-4 xl:w-auto xl:flex-col">
|
<div class="flex w-full flex-wrap justify-between gap-4 xl:w-auto xl:flex-col">
|
||||||
<div class="flex flex-col gap-1 xl:ml-auto xl:text-right">
|
<div class="flex flex-col gap-1 xl:ml-auto xl:text-right">
|
||||||
<span class="text-2xl font-bold text-dark">
|
<span class="text-2xl font-bold text-dark">
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasSubscription">
|
||||||
{{
|
{{
|
||||||
formatPrice(
|
formatPrice(
|
||||||
vintl.locale,
|
vintl.locale,
|
||||||
midasSubscriptionPrice.prices.intervals[midasCharge.subscription_interval],
|
midasSubscriptionPrice.prices.intervals[midasSubscription.interval],
|
||||||
midasSubscriptionPrice.currency_code,
|
midasSubscriptionPrice.currency_code,
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/
|
/
|
||||||
{{ midasCharge.subscription_interval }}
|
{{ midasSubscription.interval }}
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
{{ formatPrice(vintl.locale, price.prices.intervals.monthly, price.currency_code) }}
|
||||||
/ month
|
/ month
|
||||||
</template>
|
</template>
|
||||||
</span>
|
</span>
|
||||||
<template v-if="midasCharge">
|
<template v-if="midasSubscription">
|
||||||
<span class="text-sm text-secondary">
|
<span class="text-sm text-secondary">
|
||||||
Since {{ $dayjs(midasSubscription.created).format("MMMM D, YYYY") }}
|
Since {{ $dayjs(midasSubscription.created).format("MMMM D, YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
<span v-if="midasCharge.status === 'open'" class="text-sm text-secondary">
|
<span v-if="midasSubscription.status === 'active'" class="text-sm text-secondary">
|
||||||
Renews {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
Renews {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="midasCharge.status === 'cancelled'" class="text-sm text-secondary">
|
<span
|
||||||
Expires {{ $dayjs(midasCharge.due).format("MMMM D, YYYY") }}
|
v-else-if="midasSubscription.status === 'cancelled'"
|
||||||
|
class="text-sm text-secondary"
|
||||||
|
>
|
||||||
|
Expires {{ $dayjs(midasSubscription.expires).format("MMMM D, YYYY") }}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -85,11 +90,11 @@
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="midasCharge && midasCharge.status === 'failed'"
|
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
||||||
class="ml-auto flex flex-row-reverse items-center gap-2"
|
class="ml-auto flex flex-row-reverse items-center gap-2"
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
v-if="midasCharge && midasCharge.status === 'failed'"
|
v-if="midasSubscription && midasSubscription.status === 'payment-failed'"
|
||||||
class="iconified-button raised-button"
|
class="iconified-button raised-button"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -118,7 +123,7 @@
|
|||||||
</OverflowMenu>
|
</OverflowMenu>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
v-else-if="midasCharge && midasCharge.status !== 'cancelled'"
|
v-else-if="midasSubscription && midasSubscription.status !== 'cancelled'"
|
||||||
class="iconified-button raised-button !ml-auto"
|
class="iconified-button raised-button !ml-auto"
|
||||||
@click="
|
@click="
|
||||||
() => {
|
() => {
|
||||||
@@ -129,13 +134,6 @@
|
|||||||
>
|
>
|
||||||
<XIcon /> Cancel
|
<XIcon /> Cancel
|
||||||
</button>
|
</button>
|
||||||
<button
|
|
||||||
v-else-if="midasCharge && midasCharge.status === 'cancelled'"
|
|
||||||
class="btn btn-purple btn-large ml-auto"
|
|
||||||
@click="cancelSubscription(midasSubscription.id, false)"
|
|
||||||
>
|
|
||||||
<RightArrowIcon /> Resubscribe
|
|
||||||
</button>
|
|
||||||
<button
|
<button
|
||||||
v-else
|
v-else
|
||||||
class="btn btn-purple btn-large ml-auto"
|
class="btn btn-purple btn-large ml-auto"
|
||||||
@@ -476,14 +474,12 @@ function loadStripe() {
|
|||||||
|
|
||||||
const [
|
const [
|
||||||
{ data: paymentMethods, refresh: refreshPaymentMethods },
|
{ data: paymentMethods, refresh: refreshPaymentMethods },
|
||||||
{ data: charges, refresh: refreshCharges },
|
|
||||||
{ data: customer, refresh: refreshCustomer },
|
{ data: customer, refresh: refreshCustomer },
|
||||||
{ data: subscriptions, refresh: refreshSubscriptions },
|
{ data: subscriptions, refresh: refreshSubscriptions },
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
useAsyncData("billing/payment_methods", () =>
|
useAsyncData("billing/payment_methods", () =>
|
||||||
useBaseFetch("billing/payment_methods", { internal: true }),
|
useBaseFetch("billing/payment_methods", { internal: true }),
|
||||||
),
|
),
|
||||||
useAsyncData("billing/payments", () => useBaseFetch("billing/payments", { internal: true })),
|
|
||||||
useAsyncData("billing/customer", () => useBaseFetch("billing/customer", { internal: true })),
|
useAsyncData("billing/customer", () => useBaseFetch("billing/customer", { internal: true })),
|
||||||
useAsyncData("billing/subscriptions", () =>
|
useAsyncData("billing/subscriptions", () =>
|
||||||
useBaseFetch("billing/subscriptions", { internal: true }),
|
useBaseFetch("billing/subscriptions", { internal: true }),
|
||||||
@@ -491,30 +487,18 @@ const [
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
await Promise.all([
|
await Promise.all([refreshPaymentMethods(), refreshCustomer(), refreshSubscriptions()]);
|
||||||
refreshPaymentMethods(),
|
|
||||||
refreshCharges(),
|
|
||||||
refreshCustomer(),
|
|
||||||
refreshSubscriptions(),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const midasProduct = ref(products.find((x) => x.metadata.type === "midas"));
|
const midasProduct = ref(products.find((x) => x.metadata.type === "midas"));
|
||||||
const midasSubscription = computed(() =>
|
const midasSubscription = computed(() =>
|
||||||
subscriptions.value.find(
|
subscriptions.value.find((x) => midasProduct.value.prices.find((y) => y.id === x.price_id)),
|
||||||
(x) => x.status === "provisioned" && midasProduct.value.prices.find((y) => y.id === x.price_id),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
const midasSubscriptionPrice = computed(() =>
|
const midasSubscriptionPrice = computed(() =>
|
||||||
midasSubscription.value
|
midasSubscription.value
|
||||||
? midasProduct.value.prices.find((x) => x.id === midasSubscription.value.price_id)
|
? midasProduct.value.prices.find((x) => x.id === midasSubscription.value.price_id)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
const midasCharge = computed(() =>
|
|
||||||
midasSubscription.value
|
|
||||||
? charges.value.find((x) => x.subscription_id === midasSubscription.value.id)
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
|
|
||||||
const purchaseModal = ref();
|
const purchaseModal = ref();
|
||||||
const country = useUserCountry();
|
const country = useUserCountry();
|
||||||
@@ -540,18 +524,10 @@ if (route.query.priceId && route.query.plan && route.query.redirect_status) {
|
|||||||
price_id: route.query.priceId,
|
price_id: route.query.priceId,
|
||||||
interval: route.query.plan,
|
interval: route.query.plan,
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
|
expires: route.query.plan === "yearly" ? Date.now() + 31536000000 : Date.now() + 2629746000,
|
||||||
status,
|
status,
|
||||||
});
|
});
|
||||||
|
|
||||||
charges.value.push({
|
|
||||||
id: "temp",
|
|
||||||
price_id: route.query.priceId,
|
|
||||||
subscription_id: "temp",
|
|
||||||
status: "open",
|
|
||||||
due: Date.now() + (route.query.plan === "yearly" ? 31536000000 : 2629746000),
|
|
||||||
subscription_interval: route.query.plan,
|
|
||||||
});
|
|
||||||
|
|
||||||
await router.replace({ query: {} });
|
await router.replace({ query: {} });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -679,15 +655,12 @@ async function removePaymentMethod(index) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cancelSubscriptionId = ref();
|
const cancelSubscriptionId = ref();
|
||||||
async function cancelSubscription(id, cancelled) {
|
async function cancelSubscription(id) {
|
||||||
startLoading();
|
startLoading();
|
||||||
try {
|
try {
|
||||||
await useBaseFetch(`billing/subscription/${id}`, {
|
await useBaseFetch(`billing/subscription/${id}`, {
|
||||||
internal: true,
|
internal: true,
|
||||||
method: "PATCH",
|
method: "DELETE",
|
||||||
body: {
|
|
||||||
cancelled,
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
await refresh();
|
await refresh();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ async function changeLocale(value: string) {
|
|||||||
try {
|
try {
|
||||||
await vintl.changeLocale(value);
|
await vintl.changeLocale(value);
|
||||||
$failedLocale.value = undefined;
|
$failedLocale.value = undefined;
|
||||||
} catch {
|
} catch (err) {
|
||||||
$failedLocale.value = value;
|
$failedLocale.value = value;
|
||||||
} finally {
|
} finally {
|
||||||
$changingTo.value = undefined;
|
$changingTo.value = undefined;
|
||||||
|
|||||||
@@ -3,88 +3,75 @@
|
|||||||
<ModalCreation ref="modal_creation" />
|
<ModalCreation ref="modal_creation" />
|
||||||
<CollectionCreateModal ref="modal_collection_creation" />
|
<CollectionCreateModal ref="modal_collection_creation" />
|
||||||
<div class="new-page sidebar" :class="{ 'alt-layout': cosmetics.leftContentLayout }">
|
<div class="new-page sidebar" :class="{ 'alt-layout': cosmetics.leftContentLayout }">
|
||||||
<div class="normal-page__header py-4">
|
<div class="normal-page__header pt-4">
|
||||||
<ContentPageHeader>
|
<div
|
||||||
<template #icon>
|
class="mb-4 grid grid-cols-1 gap-x-8 gap-y-6 border-0 border-b border-solid border-button-bg pb-6 lg:grid-cols-[1fr_auto]"
|
||||||
|
>
|
||||||
|
<div class="flex gap-4">
|
||||||
<Avatar :src="user.avatar_url" :alt="user.username" size="96px" circle />
|
<Avatar :src="user.avatar_url" :alt="user.username" size="96px" circle />
|
||||||
</template>
|
<div class="flex flex-col gap-2">
|
||||||
<template #title>
|
<div class="flex flex-wrap items-center gap-2">
|
||||||
{{ user.username }}
|
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||||
</template>
|
{{ user.username }}
|
||||||
<template #summary>
|
</h1>
|
||||||
{{
|
</div>
|
||||||
user.bio
|
<p class="m-0 line-clamp-2 max-w-[40rem]">
|
||||||
? user.bio
|
{{
|
||||||
: projects.length === 0
|
user.bio
|
||||||
? "A Modrinth user."
|
? user.bio
|
||||||
: "A Modrinth creator."
|
: projects.length === 0
|
||||||
}}
|
? "A Modrinth user."
|
||||||
</template>
|
: "A Modrinth creator."
|
||||||
<template #stats>
|
}}
|
||||||
<div
|
</p>
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
|
||||||
>
|
|
||||||
<BoxIcon class="h-6 w-6 text-secondary" />
|
|
||||||
{{ formatCompactNumber(projects?.length || 0) }}
|
|
||||||
projects
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
</div>
|
||||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
<div class="flex flex-col justify-center gap-4">
|
||||||
>
|
<div class="flex flex-wrap gap-2">
|
||||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
<ButtonStyled size="large">
|
||||||
{{ formatCompactNumber(sumDownloads) }}
|
<NuxtLink v-if="auth.user && auth.user.id === user.id" to="/settings/profile">
|
||||||
downloads
|
<EditIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonMessages.editButton) }}
|
||||||
|
</NuxtLink>
|
||||||
|
</ButtonStyled>
|
||||||
|
<ButtonStyled size="large" circular type="transparent">
|
||||||
|
<OverflowMenu
|
||||||
|
:options="[
|
||||||
|
{
|
||||||
|
id: 'manage-projects',
|
||||||
|
action: () => navigateTo('/dashboard/projects'),
|
||||||
|
hoverOnly: true,
|
||||||
|
shown: auth.user && auth.user.id === user.id,
|
||||||
|
},
|
||||||
|
{ divider: true, shown: auth.user && auth.user.id === user.id },
|
||||||
|
{
|
||||||
|
id: 'report',
|
||||||
|
action: () => reportUser(user.id),
|
||||||
|
color: 'red',
|
||||||
|
hoverOnly: true,
|
||||||
|
},
|
||||||
|
{ id: 'copy-id', action: () => copyId() },
|
||||||
|
]"
|
||||||
|
aria-label="More options"
|
||||||
|
>
|
||||||
|
<MoreVerticalIcon aria-hidden="true" />
|
||||||
|
<template #manage-projects>
|
||||||
|
<BoxIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(messages.profileManageProjectsButton) }}
|
||||||
|
</template>
|
||||||
|
<template #report>
|
||||||
|
<ReportIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonMessages.reportButton) }}
|
||||||
|
</template>
|
||||||
|
<template #copy-id>
|
||||||
|
<ClipboardCopyIcon aria-hidden="true" />
|
||||||
|
{{ formatMessage(commonMessages.copyIdButton) }}
|
||||||
|
</template>
|
||||||
|
</OverflowMenu>
|
||||||
|
</ButtonStyled>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 font-semibold">
|
</div>
|
||||||
<CalendarIcon class="h-6 w-6 text-secondary" />
|
</div>
|
||||||
Joined
|
|
||||||
{{ formatRelativeTime(user.created) }}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #actions>
|
|
||||||
<ButtonStyled size="large">
|
|
||||||
<NuxtLink v-if="auth.user && auth.user.id === user.id" to="/settings/profile">
|
|
||||||
<EditIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(commonMessages.editButton) }}
|
|
||||||
</NuxtLink>
|
|
||||||
</ButtonStyled>
|
|
||||||
<ButtonStyled size="large" circular type="transparent">
|
|
||||||
<OverflowMenu
|
|
||||||
:options="[
|
|
||||||
{
|
|
||||||
id: 'manage-projects',
|
|
||||||
action: () => navigateTo('/dashboard/projects'),
|
|
||||||
hoverOnly: true,
|
|
||||||
shown: auth.user && auth.user.id === user.id,
|
|
||||||
},
|
|
||||||
{ divider: true, shown: auth.user && auth.user.id === user.id },
|
|
||||||
{
|
|
||||||
id: 'report',
|
|
||||||
action: () => reportUser(user.id),
|
|
||||||
color: 'red',
|
|
||||||
hoverOnly: true,
|
|
||||||
},
|
|
||||||
{ id: 'copy-id', action: () => copyId() },
|
|
||||||
]"
|
|
||||||
aria-label="More options"
|
|
||||||
>
|
|
||||||
<MoreVerticalIcon aria-hidden="true" />
|
|
||||||
<template #manage-projects>
|
|
||||||
<BoxIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(messages.profileManageProjectsButton) }}
|
|
||||||
</template>
|
|
||||||
<template #report>
|
|
||||||
<ReportIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(commonMessages.reportButton) }}
|
|
||||||
</template>
|
|
||||||
<template #copy-id>
|
|
||||||
<ClipboardCopyIcon aria-hidden="true" />
|
|
||||||
{{ formatMessage(commonMessages.copyIdButton) }}
|
|
||||||
</template>
|
|
||||||
</OverflowMenu>
|
|
||||||
</ButtonStyled>
|
|
||||||
</template>
|
|
||||||
</ContentPageHeader>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-page__content">
|
<div class="normal-page__content">
|
||||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||||
@@ -207,6 +194,72 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="normal-page__sidebar">
|
<div class="normal-page__sidebar">
|
||||||
|
<div class="card flex-card">
|
||||||
|
<h2 class="text-lg text-contrast">{{ formatMessage(messages.profileDetails) }}</h2>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<BoxIcon aria-hidden="true" class="stroke-[3] text-secondary" />
|
||||||
|
<div class="text-secondary">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="messages.profileProjectsStats"
|
||||||
|
:values="{ count: formatCompactNumber(projects.length) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
|
<span class="font-bold text-primary">
|
||||||
|
<component :is="() => normalizeChildren(children)" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<DownloadIcon aria-hidden="true" class="stroke-[3] text-secondary" />
|
||||||
|
<div class="text-secondary">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="messages.profileDownloadsStats"
|
||||||
|
:values="{ count: formatCompactNumber(sumDownloads) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
|
<span class="font-bold text-primary">
|
||||||
|
<component :is="() => normalizeChildren(children)" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<HeartIcon aria-hidden="true" class="text-secondary *:stroke-[3]" />
|
||||||
|
<div class="text-secondary">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="messages.profileProjectsFollowersStats"
|
||||||
|
:values="{ count: formatCompactNumber(sumFollows) }"
|
||||||
|
>
|
||||||
|
<template #stat="{ children }">
|
||||||
|
<span class="font-bold text-primary">
|
||||||
|
<component :is="() => normalizeChildren(children)" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<CalendarIcon aria-hidden="true" class="text-secondary *:stroke-[3]" />
|
||||||
|
<div class="text-secondary">
|
||||||
|
<IntlFormatted
|
||||||
|
:message-id="messages.profileJoinedAt"
|
||||||
|
:values="{ ago: formatRelativeTime(user.created) }"
|
||||||
|
>
|
||||||
|
<template #date="{ children }">
|
||||||
|
<span class="font-bold text-primary">
|
||||||
|
<component :is="() => children" />
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</IntlFormatted>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<AdPlaceholder
|
||||||
|
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
||||||
|
/>
|
||||||
<div v-if="organizations.length > 0" class="card flex-card">
|
<div v-if="organizations.length > 0" class="card flex-card">
|
||||||
<h2 class="text-lg text-contrast">{{ formatMessage(messages.profileOrganizations) }}</h2>
|
<h2 class="text-lg text-contrast">{{ formatMessage(messages.profileOrganizations) }}</h2>
|
||||||
<div class="flex flex-wrap gap-2">
|
<div class="flex flex-wrap gap-2">
|
||||||
@@ -235,9 +288,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<AdPlaceholder
|
|
||||||
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -254,7 +304,7 @@ import {
|
|||||||
ClipboardCopyIcon,
|
ClipboardCopyIcon,
|
||||||
MoreVerticalIcon,
|
MoreVerticalIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { OverflowMenu, ButtonStyled, ContentPageHeader } from "@modrinth/ui";
|
import { OverflowMenu, ButtonStyled } from "@modrinth/ui";
|
||||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
import NavTabs from "~/components/ui/NavTabs.vue";
|
||||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||||
import { reportUser } from "~/utils/report-helpers.ts";
|
import { reportUser } from "~/utils/report-helpers.ts";
|
||||||
@@ -268,6 +318,7 @@ import EarlyAdopterBadge from "~/assets/images/badges/early-adopter.svg?componen
|
|||||||
import ReportIcon from "~/assets/images/utils/report.svg?component";
|
import ReportIcon from "~/assets/images/utils/report.svg?component";
|
||||||
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
||||||
import EditIcon from "~/assets/images/utils/edit.svg?component";
|
import EditIcon from "~/assets/images/utils/edit.svg?component";
|
||||||
|
import HeartIcon from "~/assets/images/utils/heart.svg?component";
|
||||||
import WorldIcon from "~/assets/images/utils/world.svg?component";
|
import WorldIcon from "~/assets/images/utils/world.svg?component";
|
||||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||||
import Avatar from "~/components/ui/Avatar.vue";
|
import Avatar from "~/components/ui/Avatar.vue";
|
||||||
@@ -454,6 +505,15 @@ const sumDownloads = computed(() => {
|
|||||||
|
|
||||||
return sum;
|
return sum;
|
||||||
});
|
});
|
||||||
|
const sumFollows = computed(() => {
|
||||||
|
let sum = 0;
|
||||||
|
|
||||||
|
for (const project of projects.value) {
|
||||||
|
sum += project.followers;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
});
|
||||||
|
|
||||||
const badges = computed(() => {
|
const badges = computed(() => {
|
||||||
const badges = [];
|
const badges = [];
|
||||||
@@ -593,4 +653,8 @@ export default defineNuxtComponent({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.normal-page__header {
|
||||||
|
grid-area: header;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -472,169 +472,3 @@ visiblemeasures.com, 1041, DIRECT #yieldmo
|
|||||||
iqzone.com,IQ190,RESELLER
|
iqzone.com,IQ190,RESELLER
|
||||||
krushmedia.com, AJxF6R537a9M6CaTvK, RESELLER
|
krushmedia.com, AJxF6R537a9M6CaTvK, RESELLER
|
||||||
consumable.com, 2001460, DIRECT, aefcd3d2f45b5070
|
consumable.com, 2001460, DIRECT, aefcd3d2f45b5070
|
||||||
|
|
||||||
google.com, pub-6729046591418183, RESELLER, f08c47fec0942fa0
|
|
||||||
|
|
||||||
pgamssp.com, 642db2eba4eb3f6b34033505, DIRECT
|
|
||||||
freewheel.tv, 1489202, RESELLER
|
|
||||||
freewheel.tv, 1488706, RESELLER
|
|
||||||
pubmatic.com, 162623, RESELLER, 5d62403b186f2ace
|
|
||||||
rubiconproject.com, 24852, RESELLER, 0bfd66d529a55807
|
|
||||||
video.unrulymedia.com, 5921144960123684292, RESELLER
|
|
||||||
minutemedia.com, 01hp4as4p012, RESELLER
|
|
||||||
onetag.com, 87f80e5d9d55274, RESELLER
|
|
||||||
sharethrough.com, FhiWXM0L, RESELLER, d53b998a7bd4ecd2
|
|
||||||
amxrtb.com, 105199776, RESELLER
|
|
||||||
33across.com, 0015a00003ALsDfAAL, DIRECT, bbea06d9c4d2853c
|
|
||||||
risecodes.com, 661fc591c3a3ef0001984071, DIRECT
|
|
||||||
|
|
||||||
optimanetwork.com, 10581, DIRECT
|
|
||||||
rubiconproject.com, 23404, DIRECT, 0bfd66d529a55807
|
|
||||||
pubmatic.com,163370,RESELLER,5d62403b186f2ace
|
|
||||||
criteo.com, B-060927, DIRECT, 9fac4a4a87c2a44f
|
|
||||||
themediagrid.com, TR634Q, DIRECT, 35d5010d7789b49d
|
|
||||||
google.com, pub-7094677798399606, RESELLER, f08c47fec0942fa0
|
|
||||||
openx.com, 544021216, RESELLER, 6a698e2ec38604c6
|
|
||||||
google.com, pub-2403018226404213, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-2730263451308801, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-5200956238394958, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-5761017298734489, RESELLER, f08c47fec0942fa0
|
|
||||||
Pubmatic.com, 161094, DIRECT, 5d62403b186f2ace
|
|
||||||
Pubmatic.com, 162736, DIRECT, 5d62403b186f2ace
|
|
||||||
rubiconproject.com, 23978, RESELLER, 0bfd66d529a55807
|
|
||||||
openx.com, 545708355, DIRECT, 6a698e2ec38604c6
|
|
||||||
openx.com, 558218698, DIRECT, 6a698e2ec38604c6
|
|
||||||
google.com, pub-3479162065560922, DIRECT, f08c47fec0942fa0
|
|
||||||
google.com, pub-6631347089063164, DIRECT, f08c47fec0942fa0
|
|
||||||
google.com, pub-1309378188566785, DIRECT, f08c47fec0942fa0
|
|
||||||
google.com, pub-1305372857617183, DIRECT, f08c47fec0942fa0
|
|
||||||
crads.in, CR-0029, DIRECT
|
|
||||||
google.com, pub-6013343350303951, RESELLER, f08c47fec0942fa0
|
|
||||||
rtbhouse.com, Jz6bvciZ65MalvqnKChX, DIRECT
|
|
||||||
incrementx.com, 60039, DIRECT, 8728b7e97e589da4
|
|
||||||
google.com, pub-3977122154505186, RESELLER, f08c47fec0942fa0
|
|
||||||
pubmatic.com, 163277, RESELLER, 5d62403b186f2ace
|
|
||||||
smartadserver.com, 4417-OB, RESELLER, 060d053dcf45cbf3
|
|
||||||
lijit.com, 381276-eb, RESELLER, fafdf38b16bf6b2b
|
|
||||||
rubiconproject.com, 22884, RESELLER, 0bfd66d529a55807
|
|
||||||
onetag.com, 80faba581f6cfb8, RESELLER
|
|
||||||
onetag.com, 80faba581f6cfb8-OB, RESELLER
|
|
||||||
axonix.com, 59089, RESELLER, bc385f2b4a87b721
|
|
||||||
outbrain.com,002d7f7ba0bd74452f2b155d0dfb5cd6c8,RESELLER
|
|
||||||
opera.com,pub10952251675200,RESELLER,55a0c5fd61378de3
|
|
||||||
pubmatic.com, 161652, RESELLER, 5d62403b186f2ace
|
|
||||||
pubmatic.com, 164418, RESELLER, 5d62403b186f2ace
|
|
||||||
152media.info,152M748,RESELLER
|
|
||||||
appnexus.com, 3153, RESELLER, f5ab79cb980f11d1
|
|
||||||
vidoomy.com, 61162, DIRECT
|
|
||||||
video.unrulymedia.com, 1816262719, RESELLER,
|
|
||||||
pubmatic.com, 156498, RESELLER, 5d62403b186f2ace
|
|
||||||
freewheel.tv, 872257, RESELLER
|
|
||||||
freewheel.tv, 894193, RESELLER
|
|
||||||
openx.com, 540804929, RESELLER, 6a698e2ec38604c6
|
|
||||||
google.com, pub-2831120411392012, RESELLER, f08c47fec0942fa0
|
|
||||||
tremorhub.com, 4cywq-a04wk, RESELLER, 1a4e959a1b50034a
|
|
||||||
rubiconproject.com, 24386, RESELLER, 0bfd66d529a55807
|
|
||||||
pubmatic.com, 165144, RESELLER, 5d62403b186f2ace
|
|
||||||
smartadserver.com, 4984, RESELLER, 060d053dcf45cbf3
|
|
||||||
truvid.com, 2158, DIRECT
|
|
||||||
smartadserver.com, 3356, RESELLER, 060d053dcf45cbf3
|
|
||||||
google.com, pub-6322541192886560, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-8787923930478618, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9507736279911916, RESELLER, f08c47fec0942fa0
|
|
||||||
appnexus.com, 12700, RESELLER, f5ab79cb980f11d1
|
|
||||||
rubiconproject.com, 17412, RESELLER, 0bfd66d529a55807
|
|
||||||
conversantmedia.com, 100712, RESELLER, 03113cd04947736d
|
|
||||||
vidcrunch.com, 63c6b945dee370f45c083ed4, DIRECT
|
|
||||||
vdo.ai, 2213_3552, DIRECT
|
|
||||||
atlas5.co, 2213_3552, DIRECT
|
|
||||||
walletcircle.co, 2213, DIRECT
|
|
||||||
google.com, pub-9417114411593463, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-5717092533913515, RESELLER, f08c47fec0942fa0
|
|
||||||
aps.amazon.com, 24b39613-fd0f-4009-9189-976a7d9bfd3d, DIRECT
|
|
||||||
gannett.com, 22655109307, RESELLER
|
|
||||||
rubiconproject.com, 10968, RESELLER, 0bfd66d529a55807
|
|
||||||
google.com, pub-4836542095728076, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-2930805104418204, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9135355251665930, RESELLER, f08c47fec0942fa0
|
|
||||||
freewheel.tv, 1139281, RESELLER
|
|
||||||
aps.amazon.com, 00ed17ab-4189-4639-9d5e-15acd40affde, DIRECT
|
|
||||||
smartadserver.com, 3686, RESELLER
|
|
||||||
unibots.in, UBC-0165-1, DIRECT
|
|
||||||
google.com, pub-1290995901905588, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-2603664881560000, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-3132893725603935, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-3191289882045155, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-3328898302928686, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-3769010358500643, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-5995202563537249, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-6151720204273327, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-6512936480753445, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-8241049497608997, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-8610050614645263, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-8699255262206653, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9033099948928268, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9467340974789471, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9557089510405422, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-9685734445476814, RESELLER, f08c47fec0942fa0
|
|
||||||
biddo.net, bdc-3969, DIRECT
|
|
||||||
prodooh.com, pdh-3969, DIRECT
|
|
||||||
invamia.com, ivm-3969, DIRECT
|
|
||||||
vidverto.com, vdvt-3969, DIRECT
|
|
||||||
yieldbird.com, 3969, DIRECT
|
|
||||||
bidfuse.com, bf-3969, DIRECT
|
|
||||||
arabella.ag, abl-3969, DIRECT
|
|
||||||
adtech-digital.com, adt3969, DIRECT
|
|
||||||
pixfuture.com, 9048, DIRECT
|
|
||||||
openx.com, 540406323, DIRECT, 6a698e2ec38604c6
|
|
||||||
pubmatic.com, 158127, RESELLER, 5d62403b186f2ace
|
|
||||||
sonobi.com, 0b24fdfc82, DIRECT, d1a215d9eb5aee9e
|
|
||||||
sharethrough.com, 62beb9db, DIRECT, d53b998a7bd4ecd2
|
|
||||||
d-sail.com, ds0453, DIRECT
|
|
||||||
google.com, pub-2205121062140812, RESELLER, f08c47fec0942fa0
|
|
||||||
33across.com, 0010b00002VYZoUAAX, DIRECT, bbea06d9c4d2853c
|
|
||||||
smartadserver.com, 3663, RESELLER
|
|
||||||
smartadserver.com, 4071, RESELLER
|
|
||||||
adtelligent.com, 500592, DIRECT
|
|
||||||
sovrn.com, 331050, DIRECT, fafdf38b16bf6b2b
|
|
||||||
rubiconproject.com, 23564, DIRECT, 0bfd66d529a55807
|
|
||||||
rubiconproject.com, 23566, RESELLER, 0bfd66d529a55807
|
|
||||||
Media.net, 8CUIUMTP7, DIRECT
|
|
||||||
e-planning.net, 29459e615aaa9065, DIRECT, c1ba615865ed87b2
|
|
||||||
themediagrid.com, QDC97I, DIRECT, 35d5010d7789b49d
|
|
||||||
onetag.com, 79c9089f60042c0, DIRECT
|
|
||||||
smilewanted.com, 3520, RESELLER
|
|
||||||
smartadserver.com, 2491, RESELLER
|
|
||||||
minutemedia.com,01hj1rkkswwe, RESELLER
|
|
||||||
appnexus.com, 8381, RESELLER, f5ab79cb980f11d1
|
|
||||||
aniview.com, 60c5dbc56627cd1ce66ff020, RESELLER, 78b21b97965ec3f8
|
|
||||||
google.com, pub-6346866704322274, RESELLER, f08c47fec0942fa0
|
|
||||||
google.com, pub-4586415728471297, RESELLER, f08c47fec0942fa0
|
|
||||||
playstream.media, 911, RESELLER
|
|
||||||
sharethrough.com, UXUWG46h, RESELLER, d53b998a7bd4ecd2
|
|
||||||
sharethrough.com, zLsEa05k, RESELLER, d53b998a7bd4ecd2
|
|
||||||
pubmatic.com, 156557, RESELLER, 5d62403b186f2ace
|
|
||||||
vidgyor.com,20020, DIRECT
|
|
||||||
google.com, pub-8863191426564274, RESELLER, f08c47fec0942fa0
|
|
||||||
pubmatic.com, 162319, RESELLER, 5d62403b186f2ace
|
|
||||||
lijit.com, 230102, DIRECT, fafdf38b16bf6b2b #SOVRN
|
|
||||||
lijit.com, 230102-eb, DIRECT, fafdf38b16bf6b2b #SOVRN
|
|
||||||
video.unrulymedia.com, 2444764291, RESELLER
|
|
||||||
contextweb.com, 558511, RESELLER, 89ff185a4c4e857c
|
|
||||||
krushmedia.com, AJxF6R572a9M6CaTvK, RESELLER
|
|
||||||
inmobi.com, b01aa06531c543d8a5fb9982f60afb00, RESELLER, 83e75a7ae333ca9d
|
|
||||||
motorik.io, 100463, RESELLER
|
|
||||||
smaato.com, 1100056344, RESELLER, 07bcf65f187117b4
|
|
||||||
smartadserver.com, 4926, RESELLER, 060d053dcf45cbf3
|
|
||||||
onetag.com, 5847a7d7e75dee8, DIRECT
|
|
||||||
video.unrulymedia.com, 351983115, RESELLER
|
|
||||||
rubiconproject.com, 15268, RESELLER, 0bfd66d529a55807
|
|
||||||
pubmatic.com, 159277, RESELLER
|
|
||||||
appnexus.com, 6849, RESELLER
|
|
||||||
xad.com, 963, RESELLER, 81cbf0a75a5e0e9a
|
|
||||||
openx.com, 543878511, RESELLER, 6a698e2ec38604c6
|
|
||||||
smaato.com, 1100047589, RESELLER, 07bcf65f187117b4
|
|
||||||
inmobi.com, ba5fd3fb82c5412989b23c3eec71baf7, RESELLER, 83e75a7ae333ca9d
|
|
||||||
|
|
||||||
#Criteo
|
|
||||||
themediagrid.com, 4R7XKQ, DIRECT, 35d5010d7789b49d
|
|
||||||
@@ -1,132 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
<title>Modrinth App Ad</title>
|
|
||||||
<script src="https://cadmus.script.ac/d14pdm1b7fi5kh/script.js"></script>
|
|
||||||
<script
|
|
||||||
src="https://dn0qt3r0xannq.cloudfront.net/modrinth-7JfmkEIXEp/modrinth-longform/prebid-load.js"
|
|
||||||
async
|
|
||||||
></script>
|
|
||||||
<link rel="preload" href="https://www.googletagservices.com/tag/js/gpt.js" as="script" />
|
|
||||||
<style>
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ads-container {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
#plus-link {
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#modrinth-rail-1 {
|
|
||||||
border-radius: 1rem;
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="ads-container">
|
|
||||||
<div id="plus-link"></div>
|
|
||||||
<div id="modrinth-rail-1"></div>
|
|
||||||
</div>
|
|
||||||
<script>
|
|
||||||
function getCookie(name) {
|
|
||||||
function escape(s) {
|
|
||||||
return s.replace(/([.*+?\^$(){}|\[\]\/\\])/g, "\\$1");
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = document.cookie.match(RegExp("(?:^|;\\s*)" + escape(name) + "=([^;]*)"));
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
function initAds(personalized) {
|
|
||||||
window.tude = window.tude || { cmd: [] };
|
|
||||||
|
|
||||||
tude.cmd.push(function () {
|
|
||||||
tude.refreshAdsViaDivMappings([
|
|
||||||
{
|
|
||||||
divId: "modrinth-rail-1",
|
|
||||||
baseDivId: "pb-slot-square-2",
|
|
||||||
targeting: {
|
|
||||||
location: "app",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
|
|
||||||
tude.setPrivacySettings({
|
|
||||||
personalizedAds: personalized ?? true,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (window.__TAURI_INTERNALS__) {
|
|
||||||
window.__TAURI_INTERNALS__
|
|
||||||
.invoke("plugin:ads|get_ads_personalization", {})
|
|
||||||
.then(initAds)
|
|
||||||
.catch(() => initAds(true));
|
|
||||||
} else {
|
|
||||||
initAds(true);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
initAds(true);
|
|
||||||
console.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener(
|
|
||||||
"message",
|
|
||||||
(event) => {
|
|
||||||
if (event.data.modrinthAdClick && window.__TAURI_INTERNALS__) {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__) {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
|
||||||
path: event.data.modrinthOpenUrl,
|
|
||||||
origin: event.origin,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
|
|
||||||
window.addEventListener("mousewheel", (event) => {
|
|
||||||
if (window.__TAURI_INTERNALS__) {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|scroll_ads_window", {
|
|
||||||
scroll: event.deltaY,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
document.addEventListener("contextmenu", (event) => event.preventDefault());
|
|
||||||
|
|
||||||
const plusLink = document.getElementById("plus-link");
|
|
||||||
plusLink.addEventListener("click", function () {
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|record_ads_click", {});
|
|
||||||
window.__TAURI_INTERNALS__.invoke("plugin:ads|open_link", {
|
|
||||||
path: "https://modrinth.com/plus",
|
|
||||||
origin: "https://modrinth.com",
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
+16
-22
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry, personalized_ads,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated\n FROM settings\n ",
|
"query": "\n SELECT\n max_concurrent_writes, max_concurrent_downloads,\n theme, default_page, collapsed_navigation, advanced_rendering, native_decorations,\n discord_rpc, developer_mode, telemetry,\n onboarded,\n json(extra_launch_args) extra_launch_args, json(custom_env_vars) custom_env_vars,\n mc_memory_max, mc_force_fullscreen, mc_game_resolution_x, mc_game_resolution_y, hide_on_process_start,\n hook_pre_launch, hook_wrapper, hook_post_exit,\n custom_dir, prev_custom_dir, migrated\n FROM settings\n ",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
@@ -54,78 +54,73 @@
|
|||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "personalized_ads",
|
"name": "onboarded",
|
||||||
"ordinal": 10,
|
"ordinal": 10,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "onboarded",
|
|
||||||
"ordinal": 11,
|
|
||||||
"type_info": "Integer"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "extra_launch_args",
|
"name": "extra_launch_args",
|
||||||
"ordinal": 12,
|
"ordinal": 11,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "custom_env_vars",
|
"name": "custom_env_vars",
|
||||||
"ordinal": 13,
|
"ordinal": 12,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mc_memory_max",
|
"name": "mc_memory_max",
|
||||||
"ordinal": 14,
|
"ordinal": 13,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mc_force_fullscreen",
|
"name": "mc_force_fullscreen",
|
||||||
"ordinal": 15,
|
"ordinal": 14,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mc_game_resolution_x",
|
"name": "mc_game_resolution_x",
|
||||||
"ordinal": 16,
|
"ordinal": 15,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mc_game_resolution_y",
|
"name": "mc_game_resolution_y",
|
||||||
"ordinal": 17,
|
"ordinal": 16,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hide_on_process_start",
|
"name": "hide_on_process_start",
|
||||||
"ordinal": 18,
|
"ordinal": 17,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hook_pre_launch",
|
"name": "hook_pre_launch",
|
||||||
"ordinal": 19,
|
"ordinal": 18,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hook_wrapper",
|
"name": "hook_wrapper",
|
||||||
"ordinal": 20,
|
"ordinal": 19,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "hook_post_exit",
|
"name": "hook_post_exit",
|
||||||
"ordinal": 21,
|
"ordinal": 20,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "custom_dir",
|
"name": "custom_dir",
|
||||||
"ordinal": 22,
|
"ordinal": 21,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "prev_custom_dir",
|
"name": "prev_custom_dir",
|
||||||
"ordinal": 23,
|
"ordinal": 22,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "migrated",
|
"name": "migrated",
|
||||||
"ordinal": 24,
|
"ordinal": 23,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -144,7 +139,6 @@
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
@@ -160,5 +154,5 @@
|
|||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "8e19c9cdb0aaa48509724e82f6e8f212c9cd2112fdba77cfeee206025af47761"
|
"hash": "03d1aeddf7788320530c447a82342aecdb4099ce183dd9106c4bcc47604cb080"
|
||||||
}
|
}
|
||||||
Generated
-12
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n personalized_ads = $11,\n\n onboarded = $12,\n\n extra_launch_args = jsonb($13),\n custom_env_vars = jsonb($14),\n mc_memory_max = $15,\n mc_force_fullscreen = $16,\n mc_game_resolution_x = $17,\n mc_game_resolution_y = $18,\n hide_on_process_start = $19,\n\n hook_pre_launch = $20,\n hook_wrapper = $21,\n hook_post_exit = $22,\n\n custom_dir = $23,\n prev_custom_dir = $24,\n migrated = $25\n ",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 25
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "26e3ed8680f6c492b03b458aabfb3f94fddc753b343ef705263188945d0e578d"
|
|
||||||
}
|
|
||||||
Generated
+12
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "\n UPDATE settings\n SET\n max_concurrent_writes = $1,\n max_concurrent_downloads = $2,\n\n theme = $3,\n default_page = $4,\n collapsed_navigation = $5,\n advanced_rendering = $6,\n native_decorations = $7,\n\n discord_rpc = $8,\n developer_mode = $9,\n telemetry = $10,\n\n onboarded = $11,\n\n extra_launch_args = jsonb($12),\n custom_env_vars = jsonb($13),\n mc_memory_max = $14,\n mc_force_fullscreen = $15,\n mc_game_resolution_x = $16,\n mc_game_resolution_y = $17,\n hide_on_process_start = $18,\n\n hook_pre_launch = $19,\n hook_wrapper = $20,\n hook_post_exit = $21,\n\n custom_dir = $22,\n prev_custom_dir = $23,\n migrated = $24\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 24
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "d645daf951ff6fead3c86df685d99bacc81cb0a999c0f8d2ff7755b0089a79d8"
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "theseus"
|
name = "theseus"
|
||||||
version = "0.8.9"
|
version = "0.8.3-1"
|
||||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ tracing-error = "0.2.0"
|
|||||||
|
|
||||||
paste = { version = "1.0" }
|
paste = { version = "1.0" }
|
||||||
|
|
||||||
tauri = { version = "2.0.0-rc", optional = true }
|
tauri = { version = "2.0.0-rc.4", optional = true }
|
||||||
indicatif = { version = "0.17.3", optional = true }
|
indicatif = { version = "0.17.3", optional = true }
|
||||||
|
|
||||||
async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
||||||
|
|||||||
Loaded 100 of 131 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user