Compare commits
20 Commits
issue-temp
...
v0.8.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0221034b60 | ||
|
|
9500384100 | ||
|
|
0b31f2eb41 | ||
|
|
b3a6393c91 | ||
|
|
16c5a5a3a6 | ||
|
|
2e7db502a9 | ||
|
|
d29b71ec45 | ||
|
|
9cd0af914a | ||
|
|
4a575393f0 | ||
|
|
76c93c767d | ||
|
|
e69337a1fc | ||
|
|
50734af6cd | ||
|
|
81b0922c93 | ||
|
|
d4f8fff7af | ||
|
|
bd61f5d591 | ||
|
|
016c3d779b | ||
|
|
acf26940d6 | ||
|
|
4bafae881f | ||
|
|
8311451420 | ||
|
|
4b75cb8357 |
41
.github/workflows/app-release.yml
vendored
41
.github/workflows/app-release.yml
vendored
@@ -41,10 +41,34 @@ jobs:
|
||||
with:
|
||||
components: rustfmt, clippy
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
|
||||
- name: Setup rust cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: target/**
|
||||
path: |
|
||||
target/**
|
||||
!target/*/release/bundle/*/*.dmg
|
||||
!target/*/release/bundle/*/*.app.tar.gz
|
||||
!target/*/release/bundle/*/*.app.tar.gz.sig
|
||||
!target/release/bundle/*/*.dmg
|
||||
!target/release/bundle/*/*.app.tar.gz
|
||||
!target/release/bundle/*/*.app.tar.gz.sig
|
||||
|
||||
!target/release/bundle/*/*.AppImage
|
||||
!target/release/bundle/*/*.AppImage.tar.gz
|
||||
!target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
!target/release/bundle/*/*.deb
|
||||
!target/release/bundle/*/*.rpm
|
||||
|
||||
!target/release/bundle/msi/*.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') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-rust-target-
|
||||
@@ -78,7 +102,7 @@ jobs:
|
||||
if: startsWith(matrix.platform, 'ubuntu')
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libssl-dev sqlite3
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev build-essential curl wget file libxdo-dev libssl-dev pkg-config libayatana-appindicator3-dev librsvg2-dev
|
||||
|
||||
- name: Install frontend dependencies
|
||||
run: pnpm install
|
||||
@@ -97,6 +121,7 @@ jobs:
|
||||
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
V1_COMPATIBLE_BIN_NAME: true
|
||||
|
||||
- name: build app
|
||||
run: pnpm --filter=@modrinth/app run tauri build --config "tauri-release.conf.json"
|
||||
@@ -106,6 +131,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_KEY_PASSWORD }}
|
||||
V1_COMPATIBLE_BIN_NAME: true
|
||||
|
||||
- name: upload ${{ matrix.platform }}
|
||||
uses: actions/upload-artifact@v4
|
||||
@@ -124,8 +150,11 @@ jobs:
|
||||
target/release/bundle/*/*.AppImage.tar.gz.sig
|
||||
target/release/bundle/*/*.deb
|
||||
target/release/bundle/*/*.rpm
|
||||
|
||||
target/release/bundle/*/*.msi
|
||||
target/release/bundle/*/*.msi.zip
|
||||
target/release/bundle/*/*.msi.zip.sig
|
||||
|
||||
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
|
||||
|
||||
3
.github/workflows/ci.yml
vendored
3
.github/workflows/ci.yml
vendored
@@ -32,6 +32,9 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
|
||||
|
||||
- name: Install Tauri CLI
|
||||
run: cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
|
||||
- name: Setup Node.JS environment
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
|
||||
754
Cargo.lock
generated
754
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -16,3 +16,7 @@ strip = true # Remove debug symbols
|
||||
|
||||
[profile.dev.package.sqlx-macros]
|
||||
opt-level = 3
|
||||
|
||||
[patch.crates-io]
|
||||
wry = { git = "https://github.com/modrinth/wry", rev = "5840108" }
|
||||
tauri = { git = "https://github.com/modrinth/tauri", rev = "5e29428" }
|
||||
@@ -1,4 +0,0 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['custom/vue'],
|
||||
}
|
||||
22
apps/app-frontend/eslint.config.mjs
Normal file
22
apps/app-frontend/eslint.config.mjs
Normal file
@@ -0,0 +1,22 @@
|
||||
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>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Modrinth App</title>
|
||||
|
||||
<link rel="stylesheet" href="/src/assets/stylesheets/global.scss" />
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "@modrinth/app-frontend",
|
||||
"private": true,
|
||||
"version": "0.8.3-1",
|
||||
"version": "0.8.7",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"build": "vue-tsc --noEmit && vite build",
|
||||
"tsc:check": "vue-tsc --noEmit",
|
||||
"lint": "eslint . && prettier --check .",
|
||||
"fix": "eslint . --fix && prettier --write ."
|
||||
},
|
||||
@@ -13,36 +14,41 @@
|
||||
"@modrinth/assets": "workspace:*",
|
||||
"@modrinth/ui": "workspace:*",
|
||||
"@modrinth/utils": "workspace:*",
|
||||
"@sentry/vue": "^8.27.0",
|
||||
"@tauri-apps/api": "^2.0.0-rc.3",
|
||||
"@tauri-apps/plugin-dialog": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-os": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-updater": "^2.0.0-rc.0",
|
||||
"@tauri-apps/plugin-window-state": "^2.0.0-rc.0",
|
||||
"@vintl/vintl": "^4.4.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"floating-vue": "^5.2.2",
|
||||
"ofetch": "^1.3.4",
|
||||
"pinia": "^2.1.7",
|
||||
"posthog-js": "^1.158.2",
|
||||
"vite-svg-loader": "^5.1.0",
|
||||
"vue": "^3.4.21",
|
||||
"vue-multiselect": "3.0.0",
|
||||
"vue-router": "4.3.0",
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8",
|
||||
"posthog-js": "^1.158.2",
|
||||
"@sentry/vue": "^8.27.0"
|
||||
"vue-virtual-scroller": "v2.0.0-beta.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0-rc",
|
||||
"@eslint/compat": "^1.1.1",
|
||||
"@nuxt/eslint-config": "^0.5.6",
|
||||
"@vitejs/plugin-vue": "^5.0.4",
|
||||
"autoprefixer": "^10.4.19",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-custom": "workspace:*",
|
||||
"eslint-plugin-turbo": "^2.1.1",
|
||||
"postcss": "^8.4.39",
|
||||
"prettier": "^3.2.5",
|
||||
"sass": "^1.74.1",
|
||||
"tailwindcss": "^3.4.4",
|
||||
"tsconfig": "workspace:*",
|
||||
"vite": "^5.2.8"
|
||||
"typescript": "^5.5.4",
|
||||
"vite": "^5.2.8",
|
||||
"vue-tsc": "^2.1.6"
|
||||
},
|
||||
"packageManager": "pnpm@9.4.0"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,15 @@
|
||||
<script setup>
|
||||
import { computed, ref, onMounted } from 'vue'
|
||||
import { RouterView, RouterLink, useRouter, useRoute } from 'vue-router'
|
||||
import { HomeIcon, SearchIcon, LibraryIcon, PlusIcon, SettingsIcon, XIcon } from '@modrinth/assets'
|
||||
import {
|
||||
HomeIcon,
|
||||
SearchIcon,
|
||||
LibraryIcon,
|
||||
PlusIcon,
|
||||
SettingsIcon,
|
||||
XIcon,
|
||||
DownloadIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Button, Notifications } from '@modrinth/ui'
|
||||
import { useLoading, useTheming } from '@/store/state'
|
||||
import AccountsCard from '@/components/ui/AccountsCard.vue'
|
||||
@@ -16,7 +24,7 @@ import { handleError, useNotifications } from '@/store/notifications.js'
|
||||
import { command_listener, warning_listener } from '@/helpers/events.js'
|
||||
import { MinimizeIcon, MaximizeIcon } from '@/assets/icons'
|
||||
import { type } from '@tauri-apps/plugin-os'
|
||||
import { isDev, getOS } from '@/helpers/utils.js'
|
||||
import { isDev, getOS, restartApp } from '@/helpers/utils.js'
|
||||
import { initAnalytics, debugAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
@@ -31,6 +39,10 @@ import { useInstall } from '@/store/install.js'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import { open } from '@tauri-apps/plugin-shell'
|
||||
import { get_opening_command, initialize_state } from '@/helpers/state'
|
||||
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
|
||||
import { renderString } from '@modrinth/utils'
|
||||
import { useFetch } from '@/helpers/fetch.js'
|
||||
import { check } from '@tauri-apps/plugin-updater'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
@@ -51,6 +63,8 @@ const os = ref('')
|
||||
|
||||
const stateInitialized = ref(false)
|
||||
|
||||
const criticalErrorMessage = ref()
|
||||
|
||||
onMounted(async () => {
|
||||
await useCheckDisableMouseover()
|
||||
})
|
||||
@@ -107,7 +121,18 @@ async function setupApp() {
|
||||
}),
|
||||
)
|
||||
|
||||
useFetch(
|
||||
`https://api.modrinth.com/appCriticalAnnouncement.json?version=${version}`,
|
||||
'criticalAnnouncements',
|
||||
true,
|
||||
).then((res) => {
|
||||
if (res && res.header && res.body) {
|
||||
criticalErrorMessage.value = res
|
||||
}
|
||||
})
|
||||
|
||||
get_opening_command().then(handleCommand)
|
||||
checkUpdates()
|
||||
}
|
||||
|
||||
const stateFailed = ref(false)
|
||||
@@ -126,6 +151,7 @@ initialize_state()
|
||||
})
|
||||
|
||||
const handleClose = async () => {
|
||||
await saveWindowState(StateFlags.ALL)
|
||||
await getCurrentWindow().close()
|
||||
}
|
||||
|
||||
@@ -171,7 +197,8 @@ document.querySelector('body').addEventListener('click', function (e) {
|
||||
['http://', 'https://', 'mailto:', 'tel:'].some((v) => target.href.startsWith(v)) &&
|
||||
!target.classList.contains('router-link-active') &&
|
||||
!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)
|
||||
}
|
||||
@@ -215,6 +242,20 @@ async function handleCommand(e) {
|
||||
urlModal.value.show(e)
|
||||
}
|
||||
}
|
||||
|
||||
const updateAvailable = ref(false)
|
||||
async function checkUpdates() {
|
||||
const update = await check()
|
||||
console.log(update)
|
||||
updateAvailable.value = !!update
|
||||
|
||||
setTimeout(
|
||||
() => {
|
||||
checkUpdates()
|
||||
},
|
||||
5 * 1000 * 60,
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -248,6 +289,14 @@ async function handleCommand(e) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="settings pages-list">
|
||||
<button
|
||||
v-if="updateAvailable"
|
||||
v-tooltip="'Install update'"
|
||||
class="btn btn-outline btn-primary icon-only collapsed-button"
|
||||
@click="restartApp()"
|
||||
>
|
||||
<DownloadIcon />
|
||||
</button>
|
||||
<Button
|
||||
v-tooltip="'Create profile'"
|
||||
class="sleek-primary collapsed-button"
|
||||
@@ -263,6 +312,10 @@ async function handleCommand(e) {
|
||||
</div>
|
||||
</div>
|
||||
<div class="view">
|
||||
<div v-if="criticalErrorMessage" class="critical-error-banner" data-tauri-drag-region>
|
||||
<h1>{{ criticalErrorMessage.header }}</h1>
|
||||
<div class="markdown-body" v-html="renderString(criticalErrorMessage.body ?? '')"></div>
|
||||
</div>
|
||||
<div class="appbar-row">
|
||||
<div data-tauri-drag-region class="appbar">
|
||||
<section class="navigation-controls">
|
||||
@@ -375,6 +428,16 @@ async function handleCommand(e) {
|
||||
width: calc(100% - var(--sidebar-width));
|
||||
background-color: var(--color-raised-bg);
|
||||
|
||||
.critical-error-banner {
|
||||
margin-top: -1.25rem;
|
||||
padding: 1rem;
|
||||
background-color: rgba(203, 34, 69, 0.1);
|
||||
border-left: 2px solid var(--color-red);
|
||||
border-bottom: 2px solid var(--color-red);
|
||||
border-right: 2px solid var(--color-red);
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.appbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -12,13 +12,13 @@ import {
|
||||
SearchIcon,
|
||||
XIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { ConfirmModal, Button, Card, DropdownSelect } from '@modrinth/ui'
|
||||
import { Button, Card, DropdownSelect } from '@modrinth/ui'
|
||||
import { formatCategoryHeader } from '@modrinth/utils'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import dayjs from 'dayjs'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { duplicate, remove } from '@/helpers/profile.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
instances: {
|
||||
@@ -35,7 +35,6 @@ const props = defineProps({
|
||||
const instanceOptions = ref(null)
|
||||
const instanceComponents = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
const confirmModal = ref(null)
|
||||
|
||||
@@ -230,13 +229,12 @@ const filteredResults = computed(() => {
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<ConfirmModal
|
||||
<ConfirmModalWrapper
|
||||
ref="confirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="deleteProfile"
|
||||
/>
|
||||
<Card class="header">
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
EyeIcon,
|
||||
ChevronRightIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { ConfirmModal } from '@modrinth/ui'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
import Instance from '@/components/ui/Instance.vue'
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
@@ -22,7 +22,6 @@ import { handleError } from '@/store/notifications.js'
|
||||
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { showProfileInFolder } from '@/helpers/utils.js'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
@@ -53,7 +52,6 @@ const instanceComponents = ref(null)
|
||||
const rows = ref(null)
|
||||
const deleteConfirmModal = ref(null)
|
||||
|
||||
const themeStore = useTheming()
|
||||
const currentDeleteInstance = ref(null)
|
||||
|
||||
async function deleteProfile() {
|
||||
@@ -207,13 +205,12 @@ onUnmounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmModal
|
||||
<ConfirmModalWrapper
|
||||
ref="deleteConfirmModal"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="deleteProfile"
|
||||
/>
|
||||
<div class="content">
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
v-tooltip.right="'Minecraft accounts'"
|
||||
class="button-base avatar-button"
|
||||
:class="{ expanded: mode === 'expanded' }"
|
||||
@click="showCard = !showCard"
|
||||
@click="toggleMenu"
|
||||
>
|
||||
<Avatar
|
||||
:size="mode === 'expanded' ? 'xs' : 'sm'"
|
||||
@@ -73,6 +73,7 @@ import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { process_listener } from '@/helpers/events'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
|
||||
defineProps({
|
||||
mode: {
|
||||
@@ -133,9 +134,9 @@ const logout = async (id) => {
|
||||
trackEvent('AccountLogOut')
|
||||
}
|
||||
|
||||
let showCard = ref(false)
|
||||
let card = ref(null)
|
||||
let button = ref(null)
|
||||
const showCard = ref(false)
|
||||
const card = ref(null)
|
||||
const button = ref(null)
|
||||
const handleClickOutside = (event) => {
|
||||
const elements = document.elementsFromPoint(event.clientX, event.clientY)
|
||||
if (
|
||||
@@ -144,7 +145,20 @@ const handleClickOutside = (event) => {
|
||||
!elements.includes(card.value.$el) &&
|
||||
!button.value.contains(event.target)
|
||||
) {
|
||||
toggleMenu(false)
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMenu(override = true) {
|
||||
if (showCard.value || !override) {
|
||||
if (showCard.value) {
|
||||
show_ads_window()
|
||||
}
|
||||
|
||||
showCard.value = false
|
||||
} else {
|
||||
hide_ads_window()
|
||||
showCard.value = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const handleAddContentFromFile = async () => {
|
||||
if (!newProject) return
|
||||
|
||||
for (const project of newProject) {
|
||||
await add_project_from_path(props.instance.path, project).catch(handleError)
|
||||
await add_project_from_path(props.instance.path, project.path ?? project).catch(handleError)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
<script setup>
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
|
||||
const emit = defineEmits(['menu-closed', 'option-clicked'])
|
||||
|
||||
@@ -37,6 +38,7 @@ const shown = ref(false)
|
||||
|
||||
defineExpose({
|
||||
showMenu: (event, passedItem, passedOptions) => {
|
||||
hide_ads_window()
|
||||
item.value = passedItem
|
||||
options.value = passedOptions
|
||||
|
||||
@@ -69,6 +71,9 @@ const isLinkedData = (item) => {
|
||||
}
|
||||
|
||||
const hideContextMenu = () => {
|
||||
if (shown.value) {
|
||||
show_ads_window()
|
||||
}
|
||||
shown.value = false
|
||||
emit('menu-closed')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup>
|
||||
import { XIcon, HammerIcon, LogInIcon, UpdatedIcon } from '@modrinth/assets'
|
||||
import { Modal } from '@modrinth/ui'
|
||||
import { ChatIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
|
||||
@@ -9,6 +8,7 @@ import { handleSevereError } from '@/store/error.js'
|
||||
import { cancel_directory_change } from '@/helpers/settings.js'
|
||||
import { install } from '@/helpers/profile.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const errorModal = ref()
|
||||
const error = ref()
|
||||
@@ -121,7 +121,7 @@ async function repairInstance() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="errorModal" :header="title" :closable="closable">
|
||||
<ModalWrapper ref="errorModal" :header="title" :closable="closable">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<template v-if="errorType === 'minecraft_auth'">
|
||||
@@ -272,7 +272,7 @@ async function repairInstance() {
|
||||
<button v-if="closable" class="btn" @click="errorModal.hide()"><XIcon /> Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<script setup>
|
||||
import { XIcon, PlusIcon } from '@modrinth/assets'
|
||||
import { Button, Checkbox, Modal } from '@modrinth/ui'
|
||||
import { Button, Checkbox } from '@modrinth/ui'
|
||||
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
||||
import { ref } from 'vue'
|
||||
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/theme'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -30,8 +30,6 @@ const files = ref([])
|
||||
const folders = ref([])
|
||||
const showingFiles = ref(false)
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const initFiles = async () => {
|
||||
const newFolders = new Map()
|
||||
const sep = '/'
|
||||
@@ -106,7 +104,7 @@ const exportPack = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="exportModal" header="Export modpack" :noblur="!themeStore.advancedRendering">
|
||||
<ModalWrapper ref="exportModal" header="Export modpack">
|
||||
<div class="modal-body">
|
||||
<div class="labeled_input">
|
||||
<p>Modpack Name</p>
|
||||
@@ -208,7 +206,7 @@ const exportPack = async () => {
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal ref="modal" header="Create instance" :noblur="!themeStore.advancedRendering">
|
||||
<ModalWrapper ref="modal" header="Create instance">
|
||||
<div class="modal-header">
|
||||
<Chips v-model="creationType" :items="['custom', 'from file', 'import from launcher']" />
|
||||
</div>
|
||||
@@ -193,10 +193,11 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import {
|
||||
PlusIcon,
|
||||
UploadIcon,
|
||||
@@ -207,7 +208,7 @@ import {
|
||||
FolderSearchIcon,
|
||||
UpdatedIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Button, Chips, Modal, Checkbox } from '@modrinth/ui'
|
||||
import { Avatar, Button, Chips, Checkbox } from '@modrinth/ui'
|
||||
import { computed, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { get_loaders } from '@/helpers/tags'
|
||||
import { create } from '@/helpers/profile'
|
||||
@@ -217,7 +218,6 @@ import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import Multiselect from 'vue-multiselect'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { useTheming } from '@/store/state.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
import { install_from_file } from '@/helpers/pack.js'
|
||||
import {
|
||||
@@ -227,8 +227,6 @@ import {
|
||||
} from '@/helpers/import.js'
|
||||
import ProgressBar from '@/components/ui/ProgressBar.vue'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const profile_name = ref('')
|
||||
const game_version = ref('')
|
||||
const loader = ref('vanilla')
|
||||
@@ -371,7 +369,7 @@ const create_instance = async () => {
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
icon.value = await open({
|
||||
const res = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
@@ -381,6 +379,8 @@ const upload_icon = async () => {
|
||||
],
|
||||
})
|
||||
|
||||
icon.value = res.path ?? res
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
}
|
||||
@@ -417,7 +417,7 @@ const openFile = async () => {
|
||||
const newProject = await open({ multiple: false })
|
||||
if (!newProject) return
|
||||
hide()
|
||||
await install_from_file(newProject).catch(handleError)
|
||||
await install_from_file(newProject.path ?? newProject).catch(handleError)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModalFileOpen',
|
||||
@@ -462,7 +462,7 @@ const promises = profileOptions.value.map(async (option) => {
|
||||
option.name,
|
||||
instances.map((name) => ({ name, selected: false })),
|
||||
)
|
||||
} catch (error) {
|
||||
} catch {
|
||||
// Allow failure silently
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<Modal ref="detectJavaModal" header="Select java version" :noblur="!themeStore.advancedRendering">
|
||||
<ModalWrapper ref="detectJavaModal" header="Select java version">
|
||||
<div class="auto-detect-modal">
|
||||
<div class="table">
|
||||
<div class="table-row table-head">
|
||||
@@ -32,18 +32,16 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
<script setup>
|
||||
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets'
|
||||
import { Modal, Button } from '@modrinth/ui'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { find_filtered_jres } from '@/helpers/jre.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const chosenInstallOptions = ref([])
|
||||
const detectJavaModal = ref(null)
|
||||
|
||||
@@ -124,20 +124,19 @@ async function testJava() {
|
||||
}
|
||||
|
||||
async function handleJavaFileInput() {
|
||||
let filePath = await open()
|
||||
const filePath = await open()
|
||||
|
||||
if (filePath) {
|
||||
let result = await get_jre(filePath)
|
||||
let result = await get_jre(filePath.path ?? filePath)
|
||||
if (!result) {
|
||||
result = {
|
||||
path: filePath,
|
||||
path: filePath.path ?? filePath,
|
||||
version: props.version.toString(),
|
||||
architecture: 'x86',
|
||||
}
|
||||
}
|
||||
|
||||
trackEvent('JavaManualSelect', {
|
||||
path: filePath,
|
||||
version: props.version,
|
||||
})
|
||||
|
||||
@@ -150,7 +149,7 @@ async function autoDetect() {
|
||||
if (!props.compact) {
|
||||
detectJavaModal.value.show(props.version, props.modelValue)
|
||||
} else {
|
||||
let versions = await find_filtered_jres(props.version).catch(handleError)
|
||||
const versions = await find_filtered_jres(props.version).catch(handleError)
|
||||
if (versions.length > 0) {
|
||||
emit('update:modelValue', versions[0])
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup>
|
||||
import { CheckIcon } from '@modrinth/assets'
|
||||
import { Button, Modal, Badge } from '@modrinth/ui'
|
||||
import { Button, Badge } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import { useTheming } from '@/store/theme'
|
||||
import { update_managed_modrinth_version } from '@/helpers/profile'
|
||||
import { releaseColor } from '@/helpers/utils'
|
||||
import { SwapIcon } from '@/assets/icons/index.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
versions: {
|
||||
@@ -33,8 +33,6 @@ const installedVersion = computed(() => props.instance?.linked_data?.version_id)
|
||||
const installing = computed(() => props.instance.install_stage !== 'installed')
|
||||
const inProgress = ref(false)
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const switchVersion = async (versionId) => {
|
||||
inProgress.value = true
|
||||
await update_managed_modrinth_version(props.instance.path, versionId)
|
||||
@@ -43,11 +41,10 @@ const switchVersion = async (versionId) => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
<ModalWrapper
|
||||
ref="modpackVersionModal"
|
||||
class="modpack-version-modal"
|
||||
header="Change modpack version"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<div class="modal-body">
|
||||
<Card v-if="instance.linked_data" class="mod-card">
|
||||
@@ -111,7 +108,7 @@ const switchVersion = async (versionId) => {
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,12 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Promotion } from '@modrinth/ui'
|
||||
<script setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { get as getCreds } from '@/helpers/mr_auth.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_user } from '@/helpers/cache.js'
|
||||
import { ChevronRightIcon } from '@modrinth/assets'
|
||||
import { init_ads_window } from '@/helpers/ads.js'
|
||||
import { listen } from '@tauri-apps/api/event'
|
||||
|
||||
const showAd = ref(true)
|
||||
|
||||
defineExpose({
|
||||
scroll() {
|
||||
updateAdPosition()
|
||||
},
|
||||
})
|
||||
|
||||
const creds = await getCreds().catch(handleError)
|
||||
if (creds && creds.user_id) {
|
||||
const user = await get_user(creds.user_id).catch(handleError)
|
||||
@@ -16,8 +24,98 @@ if (creds && creds.user_id) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<Promotion v-if="showAd" :external="false" query-param="?r=launcher" />
|
||||
<div
|
||||
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">90% of ad revenue goes to creators</p>
|
||||
<a
|
||||
href="https://modrinth.com/plus"
|
||||
class="mt-auto items-center gap-1 text-purple hover:underline"
|
||||
>
|
||||
<span>
|
||||
Support creators and Modrinth ad-free with
|
||||
<span class="font-bold">Modrinth+</span>
|
||||
</span>
|
||||
<ChevronRightIcon class="relative top-[3px] h-5 w-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -88,8 +88,6 @@ import { loading_listener } from '@/helpers/events.js'
|
||||
import { getCurrentWindow } from '@tauri-apps/api/window'
|
||||
import { XIcon } from '@modrinth/assets'
|
||||
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 { useLoading } from '@/store/loading.js'
|
||||
|
||||
@@ -130,9 +128,16 @@ const os = ref('')
|
||||
getOS().then((x) => (os.value = x))
|
||||
|
||||
loading_listener(async (e) => {
|
||||
console.log(e)
|
||||
if (e.event.type === 'directory_move') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Updating app directory...'
|
||||
} else if (e.event.type === 'launcher_update') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Updating Modrinth App...'
|
||||
} else if (e.event.type === 'checking_for_updates') {
|
||||
loadingProgress.value = 100 * (e.fraction ?? 1)
|
||||
message.value = 'Checking for updates...'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup>
|
||||
import { Modal, Button } from '@modrinth/ui'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import SearchCard from '@/components/ui/SearchCard.vue'
|
||||
import { get_categories } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { get_version, get_project } from '@/helpers/cache.js'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const confirmModal = ref(null)
|
||||
const project = ref(null)
|
||||
@@ -41,7 +42,7 @@ async function install() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="confirmModal" :header="`Install ${project?.title}`">
|
||||
<ModalWrapper ref="confirmModal" :header="`Install ${project?.title}`">
|
||||
<div class="modal-body">
|
||||
<SearchCard
|
||||
:project="project"
|
||||
@@ -60,7 +61,7 @@ async function install() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
<template>
|
||||
<Modal
|
||||
ref="incompatibleModal"
|
||||
header="Incompatibility warning"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<ModalWrapper ref="incompatibleModal" header="Incompatibility warning" :on-hide="onInstall">
|
||||
<div class="modal-body">
|
||||
<p>
|
||||
This {{ versions?.length > 0 ? 'project' : 'version' }} is not compatible with the instance
|
||||
@@ -51,20 +46,19 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button, Modal, DropdownSelect } from '@modrinth/ui'
|
||||
import { Button, DropdownSelect } from '@modrinth/ui'
|
||||
import { formatCategory } from '@modrinth/utils'
|
||||
import { add_project_from_version as installMod } from '@/helpers/profile'
|
||||
import { ref } from 'vue'
|
||||
import { handleError, useTheming } from '@/store/state.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const instance = ref(null)
|
||||
const project = ref(null)
|
||||
const versions = ref(null)
|
||||
@@ -72,7 +66,7 @@ const selectedVersion = ref(null)
|
||||
const incompatibleModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
const onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (instanceVal, projectVal, projectVersions, callback) => {
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
<script setup>
|
||||
import { XIcon, DownloadIcon } from '@modrinth/assets'
|
||||
import { Button, Modal } from '@modrinth/ui'
|
||||
import { Button } from '@modrinth/ui'
|
||||
import { install as pack_install } from '@/helpers/pack'
|
||||
import { ref } from 'vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { handleError } from '@/store/state.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const versionId = ref()
|
||||
const project = ref()
|
||||
const confirmModal = ref(null)
|
||||
const installing = ref(false)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
const onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: (projectVal, versionIdVal, callback) => {
|
||||
@@ -52,12 +50,7 @@ async function install() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
ref="confirmModal"
|
||||
header="Are you sure?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<ModalWrapper ref="confirmModal" header="Are you sure?" :on-hide="onInstall">
|
||||
<div class="modal-body">
|
||||
<p>You already have this modpack installed. Are you sure you want to install it again?</p>
|
||||
<div class="input-group push-right">
|
||||
@@ -67,7 +60,7 @@ async function install() {
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
RightArrowIcon,
|
||||
CheckIcon,
|
||||
} from '@modrinth/assets'
|
||||
import { Avatar, Modal, Button, Card } from '@modrinth/ui'
|
||||
import { Avatar, Button, Card } from '@modrinth/ui'
|
||||
import { computed, ref } from 'vue'
|
||||
import {
|
||||
add_project_from_version as installMod,
|
||||
@@ -19,12 +19,11 @@ import {
|
||||
import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { installVersionDependencies } from '@/store/install.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const themeStore = useTheming()
|
||||
const router = useRouter()
|
||||
|
||||
const versions = ref()
|
||||
@@ -49,7 +48,7 @@ const shownProfiles = computed(() =>
|
||||
return profile.name.toLowerCase().includes(searchFilter.value.toLowerCase())
|
||||
})
|
||||
.filter((profile) => {
|
||||
let loaders = versions.value.flatMap((v) => v.loaders)
|
||||
const loaders = versions.value.flatMap((v) => v.loaders)
|
||||
|
||||
return (
|
||||
versions.value.flatMap((v) => v.game_versions).includes(profile.game_version) &&
|
||||
@@ -60,7 +59,7 @@ const shownProfiles = computed(() =>
|
||||
}),
|
||||
)
|
||||
|
||||
let onInstall = ref(() => {})
|
||||
const onInstall = ref(() => {})
|
||||
|
||||
defineExpose({
|
||||
show: async (projectVal, versionsVal, callback) => {
|
||||
@@ -78,7 +77,7 @@ defineExpose({
|
||||
onInstall.value = callback
|
||||
|
||||
const profilesVal = await list().catch(handleError)
|
||||
for (let profile of profilesVal) {
|
||||
for (const profile of profilesVal) {
|
||||
profile.installing = false
|
||||
profile.installedMod = await check_installed(profile.path, project.value.id).catch(
|
||||
handleError,
|
||||
@@ -142,7 +141,7 @@ const toggleCreation = () => {
|
||||
}
|
||||
|
||||
const upload_icon = async () => {
|
||||
icon.value = await open({
|
||||
const res = await open({
|
||||
multiple: false,
|
||||
filters: [
|
||||
{
|
||||
@@ -151,6 +150,7 @@ const upload_icon = async () => {
|
||||
},
|
||||
],
|
||||
})
|
||||
icon.value = res.path ?? res
|
||||
|
||||
if (!icon.value) return
|
||||
display_icon.value = convertFileSrc(icon.value)
|
||||
@@ -213,12 +213,7 @@ const createInstance = async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal
|
||||
ref="installModal"
|
||||
header="Install project to instance"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
:on-hide="onInstall"
|
||||
>
|
||||
<ModalWrapper ref="installModal" header="Install project to instance" :on-hide="onInstall">
|
||||
<div class="modal-body">
|
||||
<input
|
||||
v-model="searchFilter"
|
||||
@@ -304,7 +299,7 @@ const createInstance = async () => {
|
||||
<Button @click="installModal.hide()">Cancel</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ConfirmModal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
defineProps({
|
||||
confirmationText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hasToType: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: 'No title defined',
|
||||
required: true,
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: 'No description defined',
|
||||
required: true,
|
||||
},
|
||||
proceedLabel: {
|
||||
type: String,
|
||||
default: 'Proceed',
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['proceed'])
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
hide_ads_window()
|
||||
modal.value.show()
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
}
|
||||
|
||||
function proceed() {
|
||||
emit('proceed')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmModal
|
||||
ref="modal"
|
||||
:confirmation-text="confirmationText"
|
||||
:has-to-type="hasToType"
|
||||
:title="title"
|
||||
:description="description"
|
||||
:proceed-label="proceedLabel"
|
||||
:on-hide="onModalHide"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="proceed"
|
||||
/>
|
||||
</template>
|
||||
49
apps/app-frontend/src/components/ui/modal/ModalWrapper.vue
Normal file
49
apps/app-frontend/src/components/ui/modal/ModalWrapper.vue
Normal file
@@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { Modal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const props = defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
closable: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: () => {
|
||||
hide_ads_window()
|
||||
modal.value.show()
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
props.onHide()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="modal" :header="header" :noblur="!themeStore.advancedRendering" @hide="onModalHide">
|
||||
<slot />
|
||||
</Modal>
|
||||
</template>
|
||||
@@ -0,0 +1,61 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { ShareModal } from '@modrinth/ui'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
defineProps({
|
||||
header: {
|
||||
type: String,
|
||||
default: 'Share',
|
||||
},
|
||||
shareTitle: {
|
||||
type: String,
|
||||
default: 'Modrinth',
|
||||
},
|
||||
shareText: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
link: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
openInNewTab: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const modal = ref(null)
|
||||
|
||||
defineExpose({
|
||||
show: (passedContent) => {
|
||||
hide_ads_window()
|
||||
modal.value.show(passedContent)
|
||||
},
|
||||
hide: () => {
|
||||
onModalHide()
|
||||
modal.value.hide()
|
||||
},
|
||||
})
|
||||
|
||||
function onModalHide() {
|
||||
show_ads_window()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ShareModal
|
||||
ref="modal"
|
||||
:header="header"
|
||||
:share-title="shareTitle"
|
||||
:share-text="shareText"
|
||||
:link="link"
|
||||
:open-in-new-tab="openInNewTab"
|
||||
:on-hide="onModalHide"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { UserIcon, LockIcon, MailIcon } from '@modrinth/assets'
|
||||
import { Button, Card, Checkbox, Modal } from '@modrinth/ui'
|
||||
import { Button, Card, Checkbox } from '@modrinth/ui'
|
||||
import {
|
||||
DiscordIcon,
|
||||
GithubIcon,
|
||||
@@ -13,6 +13,7 @@ import { login, login_2fa, create_account, login_pass } from '@/helpers/mr_auth.
|
||||
import { handleError, useNotifications } from '@/store/state.js'
|
||||
import { ref } from 'vue'
|
||||
import { handleSevereError } from '@/store/error.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
callback: {
|
||||
@@ -132,7 +133,7 @@ async function createAccount() {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="modal" :on-hide="removeWidget">
|
||||
<ModalWrapper ref="modal" :on-hide="removeWidget">
|
||||
<Card>
|
||||
<template v-if="twoFactorFlow">
|
||||
<h1>Enter two-factor code</h1>
|
||||
@@ -217,17 +218,17 @@ async function createAccount() {
|
||||
v-else-if="loggingIn"
|
||||
color="primary"
|
||||
large
|
||||
@click="signIn"
|
||||
:disabled="!turnstileToken"
|
||||
@click="signIn"
|
||||
>
|
||||
Login
|
||||
</Button>
|
||||
<Button v-else color="primary" large @click="createAccount" :disabled="!turnstileToken">
|
||||
<Button v-else color="primary" large :disabled="!turnstileToken" @click="createAccount">
|
||||
Create account
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
13
apps/app-frontend/src/helpers/ads.js
Normal file
13
apps/app-frontend/src/helpers/ads.js
Normal file
@@ -0,0 +1,13 @@
|
||||
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 })
|
||||
}
|
||||
@@ -33,6 +33,10 @@ export async function highlightModInProfile(profilePath, projectPath) {
|
||||
return await highlightInFolder(fullPath)
|
||||
}
|
||||
|
||||
export async function restartApp() {
|
||||
return await invoke('restart_app')
|
||||
}
|
||||
|
||||
export const releaseColor = (releaseType) => {
|
||||
switch (releaseType) {
|
||||
case 'release':
|
||||
@@ -52,7 +56,7 @@ export function debounce(fn, wait) {
|
||||
if (timer) {
|
||||
clearTimeout(timer) // clear any pre-existing timer
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
|
||||
const context = this // get the current context
|
||||
timer = setTimeout(() => {
|
||||
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
|
||||
async function sortByNameOrNumber(sortable, identifiers) {
|
||||
sortable.sort((a, b) => {
|
||||
for (let identifier of identifiers) {
|
||||
let aNum = parseFloat(a[identifier])
|
||||
let bNum = parseFloat(b[identifier])
|
||||
for (const identifier of identifiers) {
|
||||
const aNum = parseFloat(a[identifier])
|
||||
const bNum = parseFloat(b[identifier])
|
||||
if (isNaN(aNum) && isNaN(bNum)) {
|
||||
// Both are strings, sort alphabetically
|
||||
let stringComp = a[identifier].localeCompare(b[identifier])
|
||||
const stringComp = a[identifier].localeCompare(b[identifier])
|
||||
if (stringComp != 0) return stringComp
|
||||
} else if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
// Both are numbers, sort numerically
|
||||
let numComp = aNum - bNum
|
||||
const numComp = aNum - bNum
|
||||
if (numComp != 0) return numComp
|
||||
} else {
|
||||
// One is a number and one is a string, numbers go first
|
||||
let numStringComp = isNaN(aNum) ? 1 : -1
|
||||
const numStringComp = isNaN(aNum) ? 1 : -1
|
||||
if (numStringComp != 0) return numStringComp
|
||||
}
|
||||
}
|
||||
@@ -528,7 +528,8 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
||||
|
||||
<template>
|
||||
<div ref="searchWrapper" class="search-container">
|
||||
<aside class="filter-panel">
|
||||
<aside class="filter-panel" @scroll="$refs.promo.scroll()">
|
||||
<PromotionWrapper ref="promo" />
|
||||
<Card v-if="instanceContext" class="small-instance">
|
||||
<router-link :to="`/instance/${encodeURIComponent(instanceContext.path)}`" class="instance">
|
||||
<Avatar
|
||||
@@ -675,8 +676,7 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
||||
</Card>
|
||||
</aside>
|
||||
<div class="search">
|
||||
<PromotionWrapper class="mt-4" />
|
||||
<Card class="project-type-container">
|
||||
<Card class="project-type-container mt-4">
|
||||
<NavRow :links="selectableProjectTypes" />
|
||||
</Card>
|
||||
<Card class="search-panel-container">
|
||||
@@ -878,13 +878,13 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
||||
|
||||
.filter-panel {
|
||||
position: fixed;
|
||||
width: 20rem;
|
||||
padding: 1rem 0.5rem 1rem 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: fit-content;
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
width: 20rem;
|
||||
overflow-y: auto;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -903,8 +903,8 @@ const isModProject = computed(() => ['modpack', 'mod'].includes(projectType.valu
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 0 1rem 0.5rem 20.5rem;
|
||||
width: calc(100% - 20.5rem);
|
||||
margin: 0 1rem 0.5rem calc(20rem + 1rem);
|
||||
width: calc(100% - calc(20rem + 1rem));
|
||||
|
||||
.offline {
|
||||
margin: 1rem;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { ref, onUnmounted, computed } from 'vue'
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import RowDisplay from '@/components/RowDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
@@ -8,6 +8,11 @@ import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import dayjs from 'dayjs'
|
||||
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 featuredMods = ref({})
|
||||
@@ -42,7 +47,7 @@ const getInstances = async () => {
|
||||
return dateB - dateA
|
||||
})
|
||||
|
||||
let filters = []
|
||||
const filters = []
|
||||
for (const instance of recentInstances.value) {
|
||||
if (instance.linked_data && instance.linked_data.project_id) {
|
||||
filters.push(`NOT"project_id"="${instance.linked_data.project_id}"`)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup>
|
||||
import { onUnmounted, ref, shallowRef } from 'vue'
|
||||
import { onMounted, onUnmounted, ref, shallowRef } from 'vue'
|
||||
import GridDisplay from '@/components/GridDisplay.vue'
|
||||
import { list } from '@/helpers/profile.js'
|
||||
import { useRoute } from 'vue-router'
|
||||
@@ -10,6 +10,11 @@ import { Button } from '@modrinth/ui'
|
||||
import { PlusIcon } from '@modrinth/assets'
|
||||
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
|
||||
import { NewInstanceImage } from '@/assets/icons'
|
||||
import { hide_ads_window } from '@/helpers/ads.js'
|
||||
|
||||
onMounted(() => {
|
||||
hide_ads_window(true)
|
||||
})
|
||||
|
||||
const route = useRoute()
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup>
|
||||
import { ref, watch } from 'vue'
|
||||
import { ref, watch, onMounted } from 'vue'
|
||||
import { LogOutIcon, LogInIcon, BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { Card, Slider, DropdownSelect, Toggle, ConfirmModal, Button } from '@modrinth/ui'
|
||||
import { Card, Slider, DropdownSelect, Toggle, Button } from '@modrinth/ui'
|
||||
import { handleError, useTheming } from '@/store/state'
|
||||
import { get, set } from '@/helpers/settings'
|
||||
import { get_java_versions, get_max_memory, set_java_version } from '@/helpers/jre'
|
||||
@@ -13,6 +13,12 @@ import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { getOS } from '@/helpers/utils.js'
|
||||
import { getVersion } from '@tauri-apps/api/app'
|
||||
import { get_user, purge_cache_types } from '@/helpers/cache.js'
|
||||
import { hide_ads_window } from '@/helpers/ads.js'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
|
||||
onMounted(() => {
|
||||
hide_ads_window(true)
|
||||
})
|
||||
|
||||
const pageOptions = ['Home', 'Library']
|
||||
|
||||
@@ -169,13 +175,12 @@ async function purgeCache() {
|
||||
Sign in
|
||||
</button>
|
||||
</div>
|
||||
<ConfirmModal
|
||||
<ConfirmModalWrapper
|
||||
ref="purgeCacheConfirmModal"
|
||||
title="Are you sure you want to purge the cache?"
|
||||
description="If you proceed, your entire cache will be purged. This may slow down the app temporarily."
|
||||
:has-to-type="false"
|
||||
proceed-label="Purge cache"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="purgeCache"
|
||||
/>
|
||||
<div class="adjacent-input">
|
||||
@@ -401,14 +406,14 @@ async function purgeCache() {
|
||||
<span class="label__title size-card-header">Java settings</span>
|
||||
</h3>
|
||||
</div>
|
||||
<template v-for="version in [21, 17, 8]">
|
||||
<label :for="'java-' + version">
|
||||
<span class="label__title">Java {{ version }} location</span>
|
||||
<template v-for="javaVersion in [21, 17, 8]" :key="`java-${javaVersion}`">
|
||||
<label :for="'java-' + javaVersion">
|
||||
<span class="label__title">Java {{ javaVersion }} location</span>
|
||||
</label>
|
||||
<JavaSelector
|
||||
:id="'java-selector-' + version"
|
||||
v-model="javaVersions[version]"
|
||||
:version="version"
|
||||
:id="'java-selector-' + javaVersion"
|
||||
v-model="javaVersions[javaVersion]"
|
||||
:version="javaVersion"
|
||||
@update:model-value="updateJavaVersion"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div class="instance-container">
|
||||
<div class="side-cards">
|
||||
<div class="side-cards pb-4" @scroll="$refs.promo.scroll()">
|
||||
<Card class="instance-card" @contextmenu.prevent.stop="handleRightClick">
|
||||
<Avatar size="lg" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
|
||||
<Avatar size="md" :src="instance.icon_path ? convertFileSrc(instance.icon_path) : null" />
|
||||
<div class="instance-info">
|
||||
<h2 class="name">{{ instance.name }}</h2>
|
||||
<span class="metadata"> {{ instance.loader }} {{ instance.game_version }} </span>
|
||||
@@ -61,9 +61,9 @@
|
||||
</RouterLink>
|
||||
</div>
|
||||
</Card>
|
||||
<PromotionWrapper ref="promo" class="mt-4" />
|
||||
</div>
|
||||
<div class="content">
|
||||
<PromotionWrapper />
|
||||
<RouterView v-slot="{ Component }">
|
||||
<template v-if="Component">
|
||||
<Suspense @pending="loadingBar.startLoading()" @resolve="loadingBar.stopLoading()">
|
||||
@@ -311,7 +311,6 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
width: 17rem;
|
||||
}
|
||||
|
||||
Button {
|
||||
@@ -325,12 +324,13 @@ Button {
|
||||
}
|
||||
|
||||
.side-cards {
|
||||
position: absolute;
|
||||
position: fixed;
|
||||
width: 300px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
min-height: calc(100% - 3.25rem);
|
||||
max-height: calc(100% - 3.25rem);
|
||||
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
overflow-y: auto;
|
||||
-ms-overflow-style: none;
|
||||
scrollbar-width: none;
|
||||
@@ -374,10 +374,7 @@ Button {
|
||||
overflow: auto;
|
||||
gap: 1rem;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 19rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.instance-info {
|
||||
@@ -451,10 +448,10 @@ Button {
|
||||
}
|
||||
|
||||
.content {
|
||||
width: 100%;
|
||||
margin: 0 1rem 0.5rem 20rem;
|
||||
width: calc(100% - 20rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem 1rem 0 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
</div>
|
||||
</RecycleScroller>
|
||||
</div>
|
||||
<ShareModal
|
||||
<ShareModalWrapper
|
||||
ref="shareModal"
|
||||
header="Share Log"
|
||||
share-title="Instance Log"
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<script setup>
|
||||
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
|
||||
import { Button, Card, ShareModal, Checkbox, DropdownSelect } from '@modrinth/ui'
|
||||
import { Button, Card, Checkbox, DropdownSelect } from '@modrinth/ui'
|
||||
import {
|
||||
delete_logs_by_filename,
|
||||
get_logs,
|
||||
@@ -107,6 +107,7 @@ import { handleError } from '@/store/notifications.js'
|
||||
import { ofetch } from 'ofetch'
|
||||
import { RecycleScroller } from 'vue-virtual-scroller'
|
||||
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
|
||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
|
||||
dayjs.extend(isToday)
|
||||
dayjs.extend(isYesterday)
|
||||
@@ -295,7 +296,7 @@ if (logs.value.length > 1 && !props.playing) {
|
||||
|
||||
const deleteLog = async () => {
|
||||
if (logs.value[selectedLogIndex.value] && selectedLogIndex.value !== 0) {
|
||||
let deleteIndex = selectedLogIndex.value
|
||||
const deleteIndex = selectedLogIndex.value
|
||||
selectedLogIndex.value = deleteIndex - 1
|
||||
await delete_logs_by_filename(
|
||||
props.instance.path,
|
||||
|
||||
@@ -284,7 +284,7 @@
|
||||
:link-function="(page) => `?page=${page}`"
|
||||
@switch-page="switchPage"
|
||||
/>
|
||||
<Modal ref="deleteWarning" header="Are you sure?">
|
||||
<ModalWrapper ref="deleteWarning" header="Are you sure?">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<p>
|
||||
@@ -302,8 +302,8 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal ref="deleteDisabledWarning" header="Are you sure?">
|
||||
</ModalWrapper>
|
||||
<ModalWrapper ref="deleteDisabledWarning" header="Are you sure?">
|
||||
<div class="modal-body">
|
||||
<div class="markdown-body">
|
||||
<p>
|
||||
@@ -325,8 +325,8 @@
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<ShareModal
|
||||
</ModalWrapper>
|
||||
<ShareModalWrapper
|
||||
ref="shareModal"
|
||||
share-title="Sharing modpack content"
|
||||
share-text="Check out the projects I'm using in my modpack!"
|
||||
@@ -360,8 +360,6 @@ import {
|
||||
import {
|
||||
Pagination,
|
||||
DropdownSelect,
|
||||
ShareModal,
|
||||
Modal,
|
||||
Checkbox,
|
||||
AnimatedLogo,
|
||||
Avatar,
|
||||
@@ -393,6 +391,8 @@ import {
|
||||
get_version_many,
|
||||
} from '@/helpers/cache.js'
|
||||
import { profile_listener } from '@/helpers/events.js'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
|
||||
|
||||
const props = defineProps({
|
||||
instance: {
|
||||
@@ -717,7 +717,7 @@ const updateProject = async (mod) => {
|
||||
})
|
||||
}
|
||||
|
||||
let locks = {}
|
||||
const locks = {}
|
||||
|
||||
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.
|
||||
@@ -725,7 +725,7 @@ const toggleDisableMod = async (mod) => {
|
||||
locks[mod.id] = ref(null)
|
||||
}
|
||||
|
||||
let lock = locks[mod.id]
|
||||
const lock = locks[mod.id]
|
||||
|
||||
while (lock.value) {
|
||||
await lock.value
|
||||
@@ -784,6 +784,7 @@ const deleteDisabled = async () => {
|
||||
}
|
||||
|
||||
const shareNames = async () => {
|
||||
console.log(functionValues.value)
|
||||
await shareModal.value.show(functionValues.value.map((x) => x.name).join('\n'))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,13 @@
|
||||
<template>
|
||||
<ConfirmModal
|
||||
<ConfirmModalWrapper
|
||||
ref="modal_confirm"
|
||||
title="Are you sure you want to delete this instance?"
|
||||
description="If you proceed, all data for your instance will be removed. You will not be able to recover it."
|
||||
:has-to-type="false"
|
||||
proceed-label="Delete"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
@proceed="removeProfile"
|
||||
/>
|
||||
<Modal
|
||||
ref="modalConfirmUnlock"
|
||||
header="Are you sure you want to unlock this instance?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<ModalWrapper ref="modalConfirmUnlock" header="Are you sure you want to unlock this instance?">
|
||||
<div class="modal-delete">
|
||||
<div
|
||||
class="markdown-body"
|
||||
@@ -31,13 +26,9 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
|
||||
<Modal
|
||||
ref="modalConfirmUnpair"
|
||||
header="Are you sure you want to unpair this instance?"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<ModalWrapper ref="modalConfirmUnpair" header="Are you sure you want to unpair this instance?">
|
||||
<div class="modal-delete">
|
||||
<div
|
||||
class="markdown-body"
|
||||
@@ -56,13 +47,9 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
|
||||
<Modal
|
||||
ref="changeVersionsModal"
|
||||
header="Change instance versions"
|
||||
:noblur="!themeStore.advancedRendering"
|
||||
>
|
||||
<ModalWrapper ref="changeVersionsModal" header="Change instance versions">
|
||||
<div class="change-versions-modal universal-body">
|
||||
<div class="input-row">
|
||||
<p class="input-label">Loader</p>
|
||||
@@ -106,7 +93,7 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalWrapper>
|
||||
<section class="card">
|
||||
<div class="label">
|
||||
<h3>
|
||||
@@ -511,18 +498,7 @@ import {
|
||||
DownloadIcon,
|
||||
ClipboardCopyIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Button,
|
||||
Toggle,
|
||||
ConfirmModal,
|
||||
Card,
|
||||
Slider,
|
||||
Checkbox,
|
||||
Avatar,
|
||||
Modal,
|
||||
Chips,
|
||||
DropdownSelect,
|
||||
} from '@modrinth/ui'
|
||||
import { Button, Toggle, Card, Slider, Checkbox, Avatar, Chips, DropdownSelect } from '@modrinth/ui'
|
||||
import { SwapIcon } from '@/assets/icons'
|
||||
|
||||
import { Multiselect } from 'vue-multiselect'
|
||||
@@ -546,10 +522,11 @@ import { open } from '@tauri-apps/plugin-dialog'
|
||||
import { get_loader_versions } from '@/helpers/metadata.js'
|
||||
import { get_game_versions, get_loaders } from '@/helpers/tags.js'
|
||||
import { handleError } from '@/store/notifications.js'
|
||||
import { useTheming } from '@/store/theme.js'
|
||||
import { useBreadcrumbs } from '@/store/breadcrumbs'
|
||||
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
||||
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
|
||||
|
||||
const breadcrumbs = useBreadcrumbs()
|
||||
|
||||
@@ -570,8 +547,6 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
const themeStore = useTheming()
|
||||
|
||||
const title = ref(props.instance.name)
|
||||
const icon = ref(props.instance.icon_path)
|
||||
const groups = ref(props.instance.groups)
|
||||
@@ -606,7 +581,7 @@ async function setIcon() {
|
||||
|
||||
if (!value) return
|
||||
|
||||
icon.value = value
|
||||
icon.value = value.path ?? value
|
||||
await edit_icon(props.instance.path, icon.value).catch(handleError)
|
||||
|
||||
trackEvent('InstanceSetIcon')
|
||||
@@ -621,12 +596,12 @@ const overrideJavaInstall = ref(!!props.instance.java_path)
|
||||
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
|
||||
const javaInstall = ref({ path: optimalJava.path ?? props.instance.java_path })
|
||||
|
||||
const overrideJavaArgs = ref(!!props.instance.extra_launch_args)
|
||||
const overrideJavaArgs = ref(props.instance.extra_launch_args?.length !== undefined)
|
||||
const javaArgs = ref(
|
||||
(props.instance.extra_launch_args ?? globalSettings.extra_launch_args).join(' '),
|
||||
)
|
||||
|
||||
const overrideEnvVars = ref(!!props.instance.custom_env_vars)
|
||||
const overrideEnvVars = ref(props.instance.custom_env_vars?.length !== undefined)
|
||||
const envVars = ref(
|
||||
(props.instance.custom_env_vars ?? globalSettings.custom_env_vars)
|
||||
.map((x) => x.join('='))
|
||||
@@ -710,19 +685,15 @@ const editProfileObject = computed(() => {
|
||||
}
|
||||
|
||||
if (overrideJavaArgs.value) {
|
||||
if (javaArgs.value !== '') {
|
||||
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
}
|
||||
editProfile.extra_launch_args = javaArgs.value.trim().split(/\s+/).filter(Boolean)
|
||||
}
|
||||
|
||||
if (overrideEnvVars.value) {
|
||||
if (envVars.value !== '') {
|
||||
editProfile.custom_env_vars = envVars.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
}
|
||||
editProfile.custom_env_vars = envVars.value
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean)
|
||||
.map((x) => x.split('=').filter(Boolean))
|
||||
}
|
||||
|
||||
if (overrideMemorySettings.value) {
|
||||
@@ -905,7 +876,7 @@ const editing = ref(false)
|
||||
async function saveGvLoaderEdits() {
|
||||
editing.value = true
|
||||
|
||||
let editProfile = editProfileObject.value
|
||||
const editProfile = editProfileObject.value
|
||||
editProfile.loader = loader.value
|
||||
editProfile.game_version = gameVersion.value
|
||||
|
||||
|
||||
@@ -20,14 +20,14 @@
|
||||
</span>
|
||||
</Card>
|
||||
</div>
|
||||
<div v-if="expandedGalleryItem" class="expanded-image-modal" @click="expandedGalleryItem = null">
|
||||
<div v-if="expandedGalleryItem" class="expanded-image-modal" @click="hideImage">
|
||||
<div class="content">
|
||||
<img
|
||||
class="image"
|
||||
:class="{ 'zoomed-in': zoomedIn }"
|
||||
:src="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
||||
@@ -45,15 +45,15 @@
|
||||
</div>
|
||||
<div class="controls">
|
||||
<div class="buttons">
|
||||
<Button class="close" icon-only @click="expandedGalleryItem = null">
|
||||
<Button class="close" icon-only @click="hideImage">
|
||||
<XIcon aria-hidden="true" />
|
||||
</Button>
|
||||
<a
|
||||
class="open btn icon-only"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
@@ -94,6 +94,7 @@ import {
|
||||
import { Button, Card } from '@modrinth/ui'
|
||||
import { ref } from 'vue'
|
||||
import { trackEvent } from '@/helpers/analytics'
|
||||
import { show_ads_window, hide_ads_window } from '@/helpers/ads.js'
|
||||
|
||||
const props = defineProps({
|
||||
project: {
|
||||
@@ -102,9 +103,14 @@ const props = defineProps({
|
||||
},
|
||||
})
|
||||
|
||||
let expandedGalleryItem = ref(null)
|
||||
let expandedGalleryIndex = ref(0)
|
||||
let zoomedIn = ref(false)
|
||||
const expandedGalleryItem = ref(null)
|
||||
const expandedGalleryIndex = ref(0)
|
||||
const zoomedIn = ref(false)
|
||||
|
||||
const hideImage = () => {
|
||||
expandedGalleryItem.value = null
|
||||
show_ads_window()
|
||||
}
|
||||
|
||||
const nextImage = () => {
|
||||
expandedGalleryIndex.value++
|
||||
@@ -131,6 +137,7 @@ const previousImage = () => {
|
||||
}
|
||||
|
||||
const expandImage = (item, index) => {
|
||||
hide_ads_window()
|
||||
expandedGalleryItem.value = item
|
||||
expandedGalleryIndex.value = index
|
||||
zoomedIn.value = false
|
||||
@@ -140,6 +147,20 @@ const expandImage = (item, index) => {
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="root-container">
|
||||
<div v-if="data" class="project-sidebar">
|
||||
<div v-if="data" class="project-sidebar" @scroll="$refs.promo.scroll()">
|
||||
<Card v-if="instance" class="small-instance">
|
||||
<router-link class="instance" :to="`/instance/${encodeURIComponent(instance.path)}`">
|
||||
<Avatar
|
||||
@@ -20,7 +20,7 @@
|
||||
</router-link>
|
||||
</Card>
|
||||
<Card class="sidebar-card" @contextmenu.prevent.stop="handleRightClick">
|
||||
<Avatar size="lg" :src="data.icon_url" />
|
||||
<Avatar size="md" :src="data.icon_url" />
|
||||
<div class="instance-info">
|
||||
<h2 class="name">{{ data.title }}</h2>
|
||||
{{ data.description }}
|
||||
@@ -61,7 +61,9 @@
|
||||
Site
|
||||
</a>
|
||||
</div>
|
||||
<hr class="card-divider" />
|
||||
</Card>
|
||||
<PromotionWrapper ref="promo" />
|
||||
<Card class="sidebar-card">
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
@@ -163,7 +165,6 @@
|
||||
</Card>
|
||||
</div>
|
||||
<div v-if="data" class="content-container">
|
||||
<PromotionWrapper />
|
||||
<Card class="tabs">
|
||||
<NavRow
|
||||
v-if="data.gallery.length > 0"
|
||||
@@ -231,15 +232,7 @@ import {
|
||||
GlobeIcon,
|
||||
ClipboardCopyIcon,
|
||||
} from '@modrinth/assets'
|
||||
import {
|
||||
Categories,
|
||||
EnvironmentIndicator,
|
||||
Card,
|
||||
Avatar,
|
||||
Button,
|
||||
Promotion,
|
||||
NavRow,
|
||||
} from '@modrinth/ui'
|
||||
import { Categories, EnvironmentIndicator, Card, Avatar, Button, NavRow } from '@modrinth/ui'
|
||||
import { formatNumber } from '@modrinth/utils'
|
||||
import {
|
||||
BuyMeACoffeeIcon,
|
||||
@@ -260,7 +253,7 @@ import { handleError } from '@/store/notifications.js'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import ContextMenu from '@/components/ui/ContextMenu.vue'
|
||||
import { install as installVersion } from '@/store/install.js'
|
||||
import { get_project, get_project_many, get_team, get_version_many } from '@/helpers/cache.js'
|
||||
import { get_project, get_team, get_version_many } from '@/helpers/cache.js'
|
||||
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
@@ -309,11 +302,13 @@ async function fetchProjectData() {
|
||||
|
||||
await fetchProjectData()
|
||||
|
||||
const promo = ref(null)
|
||||
watch(
|
||||
() => route.params.id,
|
||||
async () => {
|
||||
if (route.params.id && route.path.startsWith('/project')) {
|
||||
await fetchProjectData()
|
||||
promo.value.scroll()
|
||||
}
|
||||
},
|
||||
)
|
||||
@@ -377,7 +372,7 @@ const handleOptionsClick = (args) => {
|
||||
|
||||
.project-sidebar {
|
||||
position: fixed;
|
||||
width: 20rem;
|
||||
width: calc(300px + 1.5rem);
|
||||
min-height: calc(100vh - 3.25rem);
|
||||
height: fit-content;
|
||||
max-height: calc(100vh - 3.25rem);
|
||||
@@ -403,7 +398,7 @@ const handleOptionsClick = (args) => {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
margin-left: 19.5rem;
|
||||
margin-left: calc(300px + 1rem);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
|
||||
24
apps/app-frontend/tsconfig.app.json
Normal file
24
apps/app-frontend/tsconfig.app.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"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,12 +1,4 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["src/*"]
|
||||
},
|
||||
"target": "ESNext"
|
||||
},
|
||||
"exclude": ["node_modules", "dist"]
|
||||
"files": [],
|
||||
"references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }]
|
||||
}
|
||||
|
||||
17
apps/app-frontend/tsconfig.node.json
Normal file
17
apps/app-frontend/tsconfig.node.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"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,
|
||||
strictPort: true,
|
||||
},
|
||||
// to make use of `TAURI_DEBUG` and other env variables
|
||||
// https://tauri.studio/v1/api/config#buildconfig.beforedevcommand
|
||||
// to make use of `TAURI_ENV_DEBUG` and other env variables
|
||||
// https://v2.tauri.app/reference/environment-variables/#tauri-cli-hook-commands
|
||||
envPrefix: ['VITE_', 'TAURI_'],
|
||||
build: {
|
||||
// Tauri supports es2021
|
||||
|
||||
@@ -10,7 +10,6 @@ theseus = { path = "../../packages/app-lib", features = ["cli"] }
|
||||
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = "2.0.0-rc.4"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
url = "2.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus_gui"
|
||||
version = "0.8.3-1"
|
||||
version = "0.8.7"
|
||||
description = "The Modrinth App is a desktop application for managing your Minecraft mods"
|
||||
license = "GPL-3.0-only"
|
||||
repository = "https://github.com/modrinth/code/apps/app/"
|
||||
@@ -8,7 +8,7 @@ edition = "2021"
|
||||
build = "build.rs"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2.0.0-rc", features = ["codegen"] }
|
||||
tauri-build = { git = "https://github.com/modrinth/tauri", features = ["codegen"], rev = "5e29428" }
|
||||
|
||||
[dependencies]
|
||||
theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
@@ -16,13 +16,14 @@ theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
||||
serde_json = "1.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
tauri = { version = "2.0.0-rc.6", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
||||
tauri = { git = "https://github.com/modrinth/tauri", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"], rev = "5e29428" }
|
||||
tauri-plugin-window-state = "2.0.0-rc"
|
||||
tauri-plugin-deep-link = "2.0.0-rc"
|
||||
tauri-plugin-os = "2.0.0-rc"
|
||||
tauri-plugin-shell = "2.0.0-rc"
|
||||
tauri-plugin-dialog = "2.0.0-rc"
|
||||
tauri-plugin-updater = { version = "2.0.0-rc.1", optional = true }
|
||||
tauri-plugin-updater = { version = "2.0.0-rc" }
|
||||
tauri-plugin-single-instance = { version = "2.0.0-rc" }
|
||||
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
thiserror = "1.0"
|
||||
@@ -57,6 +58,9 @@ cocoa = "0.25.0"
|
||||
objc = "0.2.7"
|
||||
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]
|
||||
# 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
|
||||
@@ -64,4 +68,4 @@ default = ["custom-protocol"]
|
||||
# this feature is used for production builds where `devPath` points to the filesystem
|
||||
# DO NOT remove this
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
updater = ["dep:tauri-plugin-updater"]
|
||||
updater = []
|
||||
|
||||
@@ -15,13 +15,14 @@ Before you begin, ensure you have the following installed on your machine:
|
||||
- [Node.js](https://nodejs.org/en/)
|
||||
- [pnpm](https://pnpm.io/)
|
||||
- [Rust](https://www.rust-lang.org/tools/install)
|
||||
- [Tauri](https://tauri.app/v1/guides/getting-started/prerequisites/#installing)
|
||||
- [Tauri](https://v2.tauri.app/start/prerequisites/)
|
||||
|
||||
### Setup
|
||||
|
||||
Follow these steps to set up your development environment:
|
||||
|
||||
```bash
|
||||
cargo install tauri-cli --git https://github.com/modrinth/tauri.git --rev 5e2942876c2266594ed1db516c1d9975c873c36a
|
||||
pnpm install
|
||||
pnpm app:dev
|
||||
```
|
||||
|
||||
@@ -62,7 +62,7 @@ fn main() {
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"get_java_versions",
|
||||
"set_java_versions",
|
||||
"set_java_version",
|
||||
"jre_find_filtered_jres",
|
||||
"jre_get_jre",
|
||||
"jre_test_jre",
|
||||
@@ -217,6 +217,19 @@ fn main() {
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
)
|
||||
.plugin(
|
||||
"ads",
|
||||
InlinedPlugin::new()
|
||||
.commands(&[
|
||||
"init_ads_window",
|
||||
"hide_ads_window",
|
||||
"scroll_ads_window",
|
||||
"show_ads_window",
|
||||
])
|
||||
.default_permission(
|
||||
DefaultPermissionRule::AllowAllCommands,
|
||||
),
|
||||
),
|
||||
)
|
||||
.expect("Failed to run tauri-build");
|
||||
|
||||
15
apps/app/capabilities/ads.json
Normal file
15
apps/app/capabilities/ads.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"identifier": "ads",
|
||||
"description": "",
|
||||
"local": false,
|
||||
"remote": {
|
||||
"urls": ["https://modrinth.com/*", "http://localhost:3000/*"]
|
||||
},
|
||||
"webviews": [
|
||||
"ads-window"
|
||||
],
|
||||
"permissions": [
|
||||
"shell:allow-open",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
@@ -35,6 +35,7 @@
|
||||
"cache:default",
|
||||
"settings:default",
|
||||
"tags:default",
|
||||
"utils:default"
|
||||
"utils:default",
|
||||
"ads:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
11
apps/app/capabilities/updater.json
Normal file
11
apps/app/capabilities/updater.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"identifier": "updater",
|
||||
"description": "",
|
||||
"local": true,
|
||||
"windows": [
|
||||
"main"
|
||||
],
|
||||
"permissions": [
|
||||
"updater:default"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"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":{"identifier":"ads","description":"","remote":{"urls":["https://modrinth.com/*","http://localhost:3000/*"]},"local":false,"webviews":["ads-window"],"permissions":["shell:allow-open","ads:default"]},"core":{"identifier":"core","description":"","local":true,"windows":["main"],"permissions":["core:default","core:path:default","core:event:default","core:window:default","core:app:default","core:resources:default","core:menu:default","core:tray:default","core:window:allow-create","core:window:allow-maximize","core:window:allow-toggle-maximize","core:window:allow-unmaximize","core:window:allow-minimize","core:window:allow-unminimize","core:window:allow-show","core:window:allow-hide","core:window:allow-close","core:window:allow-set-decorations","core:window:allow-start-dragging","core:webview:allow-set-webview-zoom"]},"plugins":{"identifier":"plugins","description":"","local":true,"windows":["main"],"permissions":["dialog:allow-open","dialog:allow-confirm","shell:allow-open","os:allow-platform","os:allow-version","os:allow-os-type","os:allow-family","os:allow-arch","os:allow-exe-extension","os:allow-locale","os:allow-hostname","deep-link:default","window-state:default","window-state:allow-restore-state","window-state:allow-save-window-state","auth:default","import:default","jre:default","logs:default","metadata:default","mr-auth:default","profile-create:default","pack:default","process:default","profile:default","cache:default","settings:default","tags:default","utils:default","ads:default"]},"updater":{"identifier":"updater","description":"","local":true,"windows":["main"],"permissions":["updater:default"]}}
|
||||
@@ -299,6 +299,69 @@
|
||||
},
|
||||
"Identifier": {
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -2785,10 +2848,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:allow-set-java-versions"
|
||||
"jre:allow-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2834,10 +2897,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:deny-set-java-versions"
|
||||
"jre:deny-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3855,6 +3918,69 @@
|
||||
"tags:deny-tags-get-report-types"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-check -> Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-check"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-download"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-download-and-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-check"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-download"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-download-and-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "utils:default -> Default plugin permissions.",
|
||||
"type": "string",
|
||||
|
||||
@@ -299,6 +299,69 @@
|
||||
},
|
||||
"Identifier": {
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -2785,10 +2848,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:allow-set-java-versions"
|
||||
"jre:allow-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2834,10 +2897,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:deny-set-java-versions"
|
||||
"jre:deny-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -299,6 +299,69 @@
|
||||
},
|
||||
"Identifier": {
|
||||
"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.",
|
||||
"type": "string",
|
||||
@@ -2785,10 +2848,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:allow-set-java-versions -> Enables the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:allow-set-java-version -> Enables the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:allow-set-java-versions"
|
||||
"jre:allow-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -2834,10 +2897,10 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "jre:deny-set-java-versions -> Denies the set_java_versions command without any pre-configured scope.",
|
||||
"description": "jre:deny-set-java-version -> Denies the set_java_version command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"jre:deny-set-java-versions"
|
||||
"jre:deny-set-java-version"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -3855,6 +3918,69 @@
|
||||
"tags:deny-tags-get-report-types"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:default -> This permission set configures which kind of\nupdater functions are exposed to the frontend.\n\n#### Granted Permissions\n\nThe full workflow from checking for updates to installing them\nis enabled.\n\n",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:default"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-check -> Enables the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-check"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-download -> Enables the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-download"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-download-and-install -> Enables the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-download-and-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:allow-install -> Enables the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:allow-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-check -> Denies the check command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-check"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-download -> Denies the download command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-download"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-download-and-install -> Denies the download_and_install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-download-and-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "updater:deny-install -> Denies the install command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"updater:deny-install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "utils:default -> Default plugin permissions.",
|
||||
"type": "string",
|
||||
|
||||
41
apps/app/nsis/hooks.nsi
Normal file
41
apps/app/nsis/hooks.nsi
Normal file
@@ -0,0 +1,41 @@
|
||||
!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
|
||||
@@ -1,16 +1,13 @@
|
||||
{
|
||||
"name": "@modrinth/app",
|
||||
"scripts": {
|
||||
"build": "tauri build",
|
||||
"tauri": "tauri",
|
||||
"dev": "tauri dev",
|
||||
"build": "cargo tauri build",
|
||||
"tauri": "cargo tauri",
|
||||
"dev": "cargo tauri dev",
|
||||
"test": "cargo test",
|
||||
"lint": "cargo fmt --check && cargo clippy -- -D warnings",
|
||||
"fix": "cargo fmt && cargo clippy --fix"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "2.0.0-rc.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@modrinth/app-frontend": "workspace:*",
|
||||
"@modrinth/app-lib": "workspace:*"
|
||||
|
||||
20
apps/app/src/api/ads-init.js
Normal file
20
apps/app/src/api/ads-init.js
Normal file
@@ -0,0 +1,20 @@
|
||||
if (!window.modrinthClickListener) {
|
||||
window.modrinthClickListener = true
|
||||
document.addEventListener('click', function (e) {
|
||||
let target = e.target
|
||||
while (target != null) {
|
||||
if (target.matches('a')) {
|
||||
e.preventDefault()
|
||||
if (target.href) {
|
||||
window.top.postMessage({ modrinthOpenUrl: target.href }, 'https://modrinth.com')
|
||||
}
|
||||
break
|
||||
}
|
||||
target = target.parentElement
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
window.open = (url, target, features) => {
|
||||
window.top.postMessage({ modrinthOpenUrl: url }, 'https://modrinth.com')
|
||||
}
|
||||
161
apps/app/src/api/ads.rs
Normal file
161
apps/app/src/api/ads.rs
Normal file
@@ -0,0 +1,161 @@
|
||||
use serde::Serialize;
|
||||
use tauri::plugin::TauriPlugin;
|
||||
use tauri::{Emitter, LogicalPosition, LogicalSize, Manager, Runtime};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct AdsState {
|
||||
pub shown: bool,
|
||||
pub size: Option<LogicalSize<f32>>,
|
||||
pub position: Option<LogicalPosition<f32>>,
|
||||
}
|
||||
|
||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
||||
tauri::plugin::Builder::<R>::new("ads")
|
||||
.setup(|app, _api| {
|
||||
app.manage(RwLock::new(AdsState {
|
||||
shown: true,
|
||||
size: None,
|
||||
position: None,
|
||||
}));
|
||||
|
||||
// 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(
|
||||
"https://modrinth.com/wrapper/app-ads-cookie"
|
||||
.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,
|
||||
])
|
||||
.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(
|
||||
"https://modrinth.com/wrapper/app-ads-cookie".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(())
|
||||
}
|
||||
@@ -16,6 +16,7 @@ pub mod settings;
|
||||
pub mod tags;
|
||||
pub mod utils;
|
||||
|
||||
pub mod ads;
|
||||
pub mod cache;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, TheseusSerializableError>;
|
||||
@@ -39,6 +40,10 @@ pub enum TheseusSerializableError {
|
||||
|
||||
#[error("Tauri error: {0}")]
|
||||
Tauri(#[from] tauri::Error),
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
#[error("Tauri updater error: {0}")]
|
||||
TauriUpdater(#[from] tauri_plugin_updater::Error),
|
||||
}
|
||||
|
||||
// Generic implementation of From<T> for ErrorTypeA
|
||||
@@ -86,7 +91,15 @@ macro_rules! impl_serialize {
|
||||
}
|
||||
|
||||
// Use the macro to implement Serialize for TheseusSerializableError
|
||||
#[cfg(not(feature = "updater"))]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
}
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
impl_serialize! {
|
||||
IO,
|
||||
Tauri,
|
||||
TauriUpdater,
|
||||
}
|
||||
|
||||
@@ -26,11 +26,73 @@ extern crate objc;
|
||||
#[tauri::command]
|
||||
async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
theseus::EventState::init(app.clone()).await?;
|
||||
State::init().await?;
|
||||
|
||||
#[cfg(feature = "updater")]
|
||||
{
|
||||
use tauri_plugin_updater::UpdaterExt;
|
||||
|
||||
let updater = app.updater_builder().build()?;
|
||||
|
||||
let update_fut = updater.check();
|
||||
|
||||
State::init().await?;
|
||||
|
||||
let check_bar = theseus::init_loading(
|
||||
theseus::LoadingBarType::CheckingForUpdates,
|
||||
1.0,
|
||||
"Checking for updates...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
let update = update_fut.await;
|
||||
|
||||
drop(check_bar);
|
||||
|
||||
if let Some(update) = update.ok().flatten() {
|
||||
tracing::info!("Update found: {:?}", update.download_url);
|
||||
let loader_bar_id = theseus::init_loading(
|
||||
theseus::LoadingBarType::LauncherUpdate {
|
||||
version: update.version.clone(),
|
||||
current_version: update.current_version.clone(),
|
||||
},
|
||||
1.0,
|
||||
"Updating Modrinth App...",
|
||||
)
|
||||
.await?;
|
||||
|
||||
// 100 MiB
|
||||
const DEFAULT_CONTENT_LENGTH: u64 = 1024 * 1024 * 100;
|
||||
|
||||
update
|
||||
.download_and_install(
|
||||
|chunk_length, content_length| {
|
||||
let _ = theseus::emit_loading(
|
||||
&loader_bar_id,
|
||||
(chunk_length as f64)
|
||||
/ (content_length
|
||||
.unwrap_or(DEFAULT_CONTENT_LENGTH)
|
||||
as f64),
|
||||
None,
|
||||
);
|
||||
},
|
||||
|| {},
|
||||
)
|
||||
.await?;
|
||||
|
||||
app.restart();
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "updater"))]
|
||||
{
|
||||
State::init().await?;
|
||||
}
|
||||
|
||||
let state = State::get().await?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir(), true)?;
|
||||
app.asset_protocol_scope()
|
||||
.allow_directory(state.directories.caches_dir().join("icons"), true)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -39,7 +101,7 @@ async fn initialize_state(app: tauri::AppHandle) -> api::Result<()> {
|
||||
#[tracing::instrument(skip_all)]
|
||||
#[tauri::command]
|
||||
fn show_window(app: tauri::AppHandle) {
|
||||
let win = app.get_webview_window("main").unwrap();
|
||||
let win = app.get_window("main").unwrap();
|
||||
if let Err(e) = win.show() {
|
||||
MessageDialog::new()
|
||||
.set_type(MessageType::Error)
|
||||
@@ -73,6 +135,11 @@ async fn toggle_decorations(b: bool, window: tauri::Window) -> api::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
fn restart_app(app: tauri::AppHandle) {
|
||||
app.restart();
|
||||
}
|
||||
|
||||
// if Tauri app is called with arguments, then those arguments will be treated as commands
|
||||
// ie: deep links or filepaths for .mrpacks
|
||||
fn main() {
|
||||
@@ -102,6 +169,19 @@ fn main() {
|
||||
}
|
||||
|
||||
builder = builder
|
||||
.plugin(tauri_plugin_single_instance::init(|app, args, _cwd| {
|
||||
if let Some(payload) = args.get(1) {
|
||||
tracing::info!("Handling deep link from arg {payload}");
|
||||
let payload = payload.clone();
|
||||
tauri::async_runtime::spawn(api::utils::handle_command(
|
||||
payload,
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(win) = app.get_window("main") {
|
||||
let _ = win.set_focus();
|
||||
}
|
||||
}))
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_deep_link::init())
|
||||
@@ -179,12 +259,14 @@ fn main() {
|
||||
.plugin(api::tags::init())
|
||||
.plugin(api::utils::init())
|
||||
.plugin(api::cache::init())
|
||||
.plugin(api::ads::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
initialize_state,
|
||||
is_dev,
|
||||
toggle_decorations,
|
||||
api::mr_auth::modrinth_auth_login,
|
||||
show_window,
|
||||
restart_app,
|
||||
]);
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
|
||||
@@ -5,10 +5,15 @@
|
||||
"build": {
|
||||
"features": ["updater"]
|
||||
},
|
||||
"app": {
|
||||
"security": {
|
||||
"capabilities": ["ads", "core", "plugins", "updater"]
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDIwMzM5QkE0M0FCOERBMzkKUldRNTJyZzZwSnN6SUdPRGdZREtUUGxMblZqeG9OVHYxRUlRTzJBc2U3MUNJaDMvZDQ1UytZZmYK",
|
||||
"endpoints": ["https://launcher-files.modrinth.com/updates.json"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
"timestampUrl": "http://timestamp.digicert.com",
|
||||
"wix": {
|
||||
"template": "./msi/main.wxs"
|
||||
},
|
||||
"nsis": {
|
||||
"installMode": "perMachine",
|
||||
"installerHooks": "./nsis/hooks.nsi"
|
||||
}
|
||||
},
|
||||
"longDescription": "",
|
||||
@@ -48,7 +52,7 @@
|
||||
]
|
||||
},
|
||||
"productName": "Modrinth App",
|
||||
"version": "0.8.3-1",
|
||||
"version": "0.8.7",
|
||||
"identifier": "ModrinthApp",
|
||||
"plugins": {
|
||||
"deep-link": {
|
||||
@@ -70,10 +74,10 @@
|
||||
"resizable": true,
|
||||
"title": "Modrinth App",
|
||||
"width": 1280,
|
||||
"minHeight": 700,
|
||||
"minHeight": 750,
|
||||
"minWidth": 1100,
|
||||
"visible": false,
|
||||
"zoomHotkeysEnabled": true,
|
||||
"zoomHotkeysEnabled": false,
|
||||
"decorations": false
|
||||
}
|
||||
],
|
||||
@@ -86,7 +90,18 @@
|
||||
],
|
||||
"enable": true
|
||||
},
|
||||
"csp": "default-src 'self'; connect-src ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://*.cloudflare.com https://api.mclo.gs; font-src https://cdn-raw.modrinth.com/fonts/inter/; img-src tauri: https: data: blob: 'unsafe-inline' asset: https://asset.localhost; script-src https://*.cloudflare.com 'self'; frame-src https://*.cloudflare.com https://www.youtube.com https://www.youtube-nocookie.com https://discord.com 'self'; style-src unsafe-inline 'self'"
|
||||
"capabilities": ["ads", "core", "plugins"],
|
||||
"csp": {
|
||||
"default-src": "'self' customprotocol: asset:",
|
||||
"connect-src": "ipc: http://ipc.localhost https://modrinth.com https://*.modrinth.com https://*.posthog.com https://*.sentry.io https://*.cloudflare.com https://api.mclo.gs https://cmp.inmobi.com",
|
||||
"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'"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
"minHeight": 700,
|
||||
"minWidth": 1100,
|
||||
"visible": false,
|
||||
"zoomHotkeysEnabled": true,
|
||||
"zoomHotkeysEnabled": false,
|
||||
"decorations": true
|
||||
}
|
||||
]
|
||||
|
||||
@@ -62,6 +62,10 @@
|
||||
.normal-page__content {
|
||||
grid-area: content;
|
||||
}
|
||||
|
||||
.normal-page__header {
|
||||
grid-area: header;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
@@ -161,4 +165,8 @@
|
||||
max-width: calc(80rem - 18.75rem - 0.75rem);
|
||||
//overflow-x: hidden;
|
||||
}
|
||||
|
||||
.normal-page__header {
|
||||
grid-area: header;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -430,220 +430,208 @@
|
||||
}"
|
||||
>
|
||||
<div class="normal-page__header relative my-4">
|
||||
<div
|
||||
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">
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
<Avatar :src="project.icon_url" :alt="project.title" size="96px" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||
{{ project.title }}
|
||||
</h1>
|
||||
<Badge
|
||||
v-if="auth.user && currentMember"
|
||||
:type="project.status"
|
||||
class="status-badge"
|
||||
/>
|
||||
</div>
|
||||
<p class="m-0 line-clamp-2 max-w-[40rem]">
|
||||
{{ project.description }}
|
||||
</p>
|
||||
<div class="mt-auto flex flex-wrap gap-4">
|
||||
</template>
|
||||
<template #title>
|
||||
{{ project.title }}
|
||||
</template>
|
||||
<template #title-suffix>
|
||||
<Badge v-if="auth.user && currentMember" :type="project.status" class="status-badge" />
|
||||
</template>
|
||||
<template #summary>
|
||||
{{ project.description }}
|
||||
</template>
|
||||
<template #stats>
|
||||
<div
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
{{ $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
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4"
|
||||
v-for="(category, index) in project.categories"
|
||||
:key="index"
|
||||
class="tag-list__item"
|
||||
>
|
||||
<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>
|
||||
{{ formatCategory(category) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-col justify-center gap-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<div class="hidden sm:contents">
|
||||
<ButtonStyled
|
||||
size="large"
|
||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||
>
|
||||
<button @click="(event) => downloadModal.show(event)">
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</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>
|
||||
</template>
|
||||
<template #actions>
|
||||
<div class="hidden sm:contents">
|
||||
<ButtonStyled
|
||||
size="large"
|
||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||
>
|
||||
<button @click="(event) => downloadModal.show(event)">
|
||||
<DownloadIcon aria-hidden="true" />
|
||||
Download
|
||||
</button>
|
||||
</ButtonStyled>
|
||||
</div>
|
||||
<div class="contents sm:hidden">
|
||||
<ButtonStyled
|
||||
size="large"
|
||||
circular
|
||||
:color="following ? 'red' : 'standard'"
|
||||
color-fill="none"
|
||||
hover-color-fill="background"
|
||||
:color="route.name === 'type-id-version-version' ? `standard` : `brand`"
|
||||
>
|
||||
<button
|
||||
v-if="auth.user"
|
||||
v-tooltip="following ? `Unfollow` : `Follow`"
|
||||
:aria-label="following ? `Unfollow` : `Follow`"
|
||||
@click="userFollowProject(project)"
|
||||
aria-label="Download"
|
||||
class="flex sm:hidden"
|
||||
@click="(event) => downloadModal.show(event)"
|
||||
>
|
||||
<HeartIcon :fill="following ? 'currentColor' : 'none'" aria-hidden="true" />
|
||||
<DownloadIcon 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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ButtonStyled
|
||||
size="large"
|
||||
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
|
||||
v-if="currentMember"
|
||||
:project="project"
|
||||
@@ -701,13 +689,13 @@
|
||||
"
|
||||
>
|
||||
<h3>{{ formatMessage(compatibilityMessages.environments) }}</h3>
|
||||
<div class="status-list">
|
||||
<div class="tag-list">
|
||||
<div
|
||||
v-if="
|
||||
(project.client_side === 'required' && project.server_side !== 'required') ||
|
||||
(project.client_side === 'optional' && project.server_side === 'optional')
|
||||
"
|
||||
class="status-list__item"
|
||||
class="tag-list__item"
|
||||
>
|
||||
<ClientIcon aria-hidden="true" />
|
||||
Client-side
|
||||
@@ -717,34 +705,29 @@
|
||||
(project.server_side === 'required' && project.client_side !== 'required') ||
|
||||
(project.client_side === 'optional' && project.server_side === 'optional')
|
||||
"
|
||||
class="status-list__item"
|
||||
class="tag-list__item"
|
||||
>
|
||||
<ServerIcon aria-hidden="true" />
|
||||
Server-side
|
||||
</div>
|
||||
<div v-if="false" class="status-list__item">
|
||||
<div v-if="false" class="tag-list__item">
|
||||
<UserIcon aria-hidden="true" />
|
||||
Singleplayer
|
||||
</div>
|
||||
<div
|
||||
v-if="project.client_side === 'required' && project.server_side === 'required'"
|
||||
class="status-list__item"
|
||||
v-if="
|
||||
project.project_type !== 'datapack' &&
|
||||
((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" />
|
||||
Client and server
|
||||
</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>
|
||||
</section>
|
||||
</div>
|
||||
@@ -1044,6 +1027,7 @@ import {
|
||||
OverflowMenu,
|
||||
PopoutMenu,
|
||||
ScrollablePanel,
|
||||
ContentPageHeader,
|
||||
} from "@modrinth/ui";
|
||||
import { formatCategory, isRejected, isStaff, isUnderReview, renderString } from "@modrinth/utils";
|
||||
import dayjs from "dayjs";
|
||||
@@ -1734,10 +1718,6 @@ const navLinks = computed(() => {
|
||||
});
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.normal-page__header {
|
||||
grid-area: header;
|
||||
}
|
||||
|
||||
.settings-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -139,8 +139,8 @@
|
||||
class="image"
|
||||
:class="{ 'zoomed-in': zoomedIn }"
|
||||
:src="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
:alt="expandedGalleryItem.title ? expandedGalleryItem.title : 'gallery-image'"
|
||||
@@ -165,8 +165,8 @@
|
||||
class="open circle-button"
|
||||
target="_blank"
|
||||
:href="
|
||||
expandedGalleryItem.url
|
||||
? expandedGalleryItem.url
|
||||
expandedGalleryItem.raw_url
|
||||
? expandedGalleryItem.raw_url
|
||||
: 'https://cdn.modrinth.com/placeholder-banner.svg'
|
||||
"
|
||||
>
|
||||
|
||||
@@ -567,6 +567,7 @@
|
||||
:show-labels="false"
|
||||
:limit="6"
|
||||
:hide-selected="true"
|
||||
:custom-label="(version) => version"
|
||||
placeholder="Choose versions..."
|
||||
/>
|
||||
<Checkbox
|
||||
|
||||
@@ -1,240 +1,260 @@
|
||||
<template>
|
||||
<div v-if="organization" class="normal-page">
|
||||
<div class="normal-page__sidebar">
|
||||
<div v-if="routeHasSettings" class="universal-card">
|
||||
<Breadcrumbs
|
||||
current-title="Settings"
|
||||
: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>
|
||||
|
||||
<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
|
||||
v-if="organization"
|
||||
class="experimental-styles-within new-page sidebar"
|
||||
:class="{ 'alt-layout': cosmetics.leftContentLayout || routeHasSettings }"
|
||||
>
|
||||
<ModalCreation ref="modal_creation" :organization-id="organization.id" />
|
||||
<template v-if="routeHasSettings">
|
||||
<div class="normal-page__sidebar">
|
||||
<div class="universal-card">
|
||||
<div class="page-header__icon">
|
||||
<Avatar size="md" :src="organization.icon_url" />
|
||||
</div>
|
||||
|
||||
<div class="page-header__text">
|
||||
<h1 class="title">{{ organization.name }}</h1>
|
||||
|
||||
<div>
|
||||
<span class="organization-label"><OrganizationIcon /> Organization</span>
|
||||
</div>
|
||||
|
||||
<div class="organization-description">
|
||||
<div class="metadata-item markdown-body collection-description">
|
||||
<p>{{ organization.description }}</p>
|
||||
</div>
|
||||
|
||||
<hr class="card-divider" />
|
||||
|
||||
<div class="primary-stat">
|
||||
<UserIcon class="primary-stat__icon" aria-hidden="true" />
|
||||
<div class="primary-stat__text">
|
||||
<span class="primary-stat__counter">
|
||||
{{ $formatNumber(acceptedMembers?.length || 0) }}
|
||||
</span>
|
||||
member<template v-if="acceptedMembers?.length !== 1">s</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="primary-stat">
|
||||
<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>
|
||||
<Breadcrumbs
|
||||
current-title="Settings"
|
||||
: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>
|
||||
|
||||
<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>
|
||||
|
||||
</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
|
||||
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
||||
/>
|
||||
|
||||
<div class="creator-list universal-card">
|
||||
<div class="title-and-link">
|
||||
<h3>Members</h3>
|
||||
<div class="card flex-card">
|
||||
<h2>Members</h2>
|
||||
<div class="details-list">
|
||||
<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>
|
||||
|
||||
<template v-for="member in acceptedMembers" :key="member.user.id">
|
||||
<nuxt-link class="creator button-base" :to="`/user/${member.user.username}`">
|
||||
<Avatar :src="member.user.avatar_url" circle />
|
||||
<p class="name">
|
||||
{{ member.user.username }}
|
||||
<CrownIcon v-if="member.is_owner" v-tooltip="'Organization owner'" />
|
||||
</p>
|
||||
<p class="role">{{ member.role }}</p>
|
||||
</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>
|
||||
<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 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>
|
||||
</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 v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
<NavTabs :links="navLinks" />
|
||||
</div>
|
||||
</template>
|
||||
<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 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>
|
||||
</div>
|
||||
<NuxtPage />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
BoxIcon,
|
||||
UserIcon,
|
||||
MoreVerticalIcon,
|
||||
UsersIcon,
|
||||
SettingsIcon,
|
||||
ChartIcon,
|
||||
CheckIcon,
|
||||
XIcon,
|
||||
ClipboardCopyIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { Avatar, Breadcrumbs } from "@modrinth/ui";
|
||||
import { Avatar, ButtonStyled, Breadcrumbs, ContentPageHeader, OverflowMenu } from "@modrinth/ui";
|
||||
import NavStack from "~/components/ui/NavStack.vue";
|
||||
import NavStackItem from "~/components/ui/NavStackItem.vue";
|
||||
import NavRow from "~/components/ui/NavRow.vue";
|
||||
import ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||
import UpToDate from "~/assets/images/illustrations/up_to_date.svg?component";
|
||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||
@@ -244,6 +264,7 @@ import OrganizationIcon from "~/assets/images/utils/organization.svg?component";
|
||||
import DownloadIcon from "~/assets/images/utils/download.svg?component";
|
||||
import CrownIcon from "~/assets/images/utils/crown.svg?component";
|
||||
import { acceptTeamInvite, removeTeamMember } from "~/helpers/teams.js";
|
||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
||||
|
||||
const vintl = useVIntl();
|
||||
const { formatMessage } = vintl;
|
||||
@@ -451,6 +472,26 @@ useSeoMeta({
|
||||
ogDescription: organization.value.description,
|
||||
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>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -3,75 +3,88 @@
|
||||
<ModalCreation ref="modal_creation" />
|
||||
<CollectionCreateModal ref="modal_collection_creation" />
|
||||
<div class="new-page sidebar" :class="{ 'alt-layout': cosmetics.leftContentLayout }">
|
||||
<div class="normal-page__header pt-4">
|
||||
<div
|
||||
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">
|
||||
<div class="normal-page__header py-4">
|
||||
<ContentPageHeader>
|
||||
<template #icon>
|
||||
<Avatar :src="user.avatar_url" :alt="user.username" size="96px" circle />
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||
{{ user.username }}
|
||||
</h1>
|
||||
</div>
|
||||
<p class="m-0 line-clamp-2 max-w-[40rem]">
|
||||
{{
|
||||
user.bio
|
||||
? user.bio
|
||||
: projects.length === 0
|
||||
? "A Modrinth user."
|
||||
: "A Modrinth creator."
|
||||
}}
|
||||
</p>
|
||||
</template>
|
||||
<template #title>
|
||||
{{ user.username }}
|
||||
</template>
|
||||
<template #summary>
|
||||
{{
|
||||
user.bio
|
||||
? user.bio
|
||||
: projects.length === 0
|
||||
? "A Modrinth user."
|
||||
: "A Modrinth creator."
|
||||
}}
|
||||
</template>
|
||||
<template #stats>
|
||||
<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>
|
||||
<div class="flex flex-col justify-center gap-4">
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<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>
|
||||
<div
|
||||
class="flex items-center gap-2 border-0 border-r border-solid border-button-bg pr-4 font-semibold"
|
||||
>
|
||||
<DownloadIcon class="h-6 w-6 text-secondary" />
|
||||
{{ formatCompactNumber(sumDownloads) }}
|
||||
downloads
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 font-semibold">
|
||||
<CalendarIcon class="h-6 w-6 text-secondary" />
|
||||
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 class="normal-page__content">
|
||||
<div v-if="navLinks.length > 2" class="mb-4 max-w-full overflow-x-auto">
|
||||
@@ -194,72 +207,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<h2 class="text-lg text-contrast">{{ formatMessage(messages.profileOrganizations) }}</h2>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
@@ -288,6 +235,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<AdPlaceholder
|
||||
v-if="!auth.user || !isPermission(auth.user.badges, 1 << 0) || flags.showAdsWithPlus"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -304,7 +254,7 @@ import {
|
||||
ClipboardCopyIcon,
|
||||
MoreVerticalIcon,
|
||||
} from "@modrinth/assets";
|
||||
import { OverflowMenu, ButtonStyled } from "@modrinth/ui";
|
||||
import { OverflowMenu, ButtonStyled, ContentPageHeader } from "@modrinth/ui";
|
||||
import NavTabs from "~/components/ui/NavTabs.vue";
|
||||
import ProjectCard from "~/components/ui/ProjectCard.vue";
|
||||
import { reportUser } from "~/utils/report-helpers.ts";
|
||||
@@ -318,7 +268,6 @@ import EarlyAdopterBadge from "~/assets/images/badges/early-adopter.svg?componen
|
||||
import ReportIcon from "~/assets/images/utils/report.svg?component";
|
||||
import UpToDate from "~/assets/images/illustrations/up_to_date.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 ModalCreation from "~/components/ui/ModalCreation.vue";
|
||||
import Avatar from "~/components/ui/Avatar.vue";
|
||||
@@ -505,15 +454,6 @@ const sumDownloads = computed(() => {
|
||||
|
||||
return sum;
|
||||
});
|
||||
const sumFollows = computed(() => {
|
||||
let sum = 0;
|
||||
|
||||
for (const project of projects.value) {
|
||||
sum += project.followers;
|
||||
}
|
||||
|
||||
return sum;
|
||||
});
|
||||
|
||||
const badges = computed(() => {
|
||||
const badges = [];
|
||||
@@ -653,8 +593,4 @@ export default defineNuxtComponent({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.normal-page__header {
|
||||
grid-area: header;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -472,3 +472,5 @@ visiblemeasures.com, 1041, DIRECT #yieldmo
|
||||
iqzone.com,IQ190,RESELLER
|
||||
krushmedia.com, AJxF6R537a9M6CaTvK, RESELLER
|
||||
consumable.com, 2001460, DIRECT, aefcd3d2f45b5070
|
||||
|
||||
google.com, pub-6729046591418183, RESELLER, f08c47fec0942fa0
|
||||
|
||||
90
apps/frontend/src/public/promo-frame.html
Normal file
90
apps/frontend/src/public/promo-frame.html
Normal file
@@ -0,0 +1,90 @@
|
||||
<!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://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;
|
||||
bottom: 0;
|
||||
z-index: 2;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="ads-container">
|
||||
<a id="plus-link" href="https://modrinth.com/plus" target="_blank"></a>
|
||||
<div id="modrinth-rail-1" />
|
||||
</div>
|
||||
<script>
|
||||
window.tude = window.tude || { cmd: [] };
|
||||
tude.cmd.push(function () {
|
||||
tude.refreshAdsViaDivMappings([
|
||||
{
|
||||
divId: "modrinth-rail-1",
|
||||
baseDivId: "pb-slot-square-2",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
let lastUrl = null
|
||||
window.addEventListener(
|
||||
"message",
|
||||
(event) => {
|
||||
if (event.data.modrinthOpenUrl && window.__TAURI_INTERNALS__ && lastUrl !== event.data.modrinthOpenUrl) {
|
||||
lastUrl = event.data.modrinthOpenUrl
|
||||
window.__TAURI_INTERNALS__.invoke("plugin:shell|open", {
|
||||
path: event.data.modrinthOpenUrl,
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
lastUrl = null
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
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());
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "theseus"
|
||||
version = "0.8.3-1"
|
||||
version = "0.8.7"
|
||||
authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
||||
edition = "2021"
|
||||
|
||||
@@ -36,7 +36,7 @@ tracing-error = "0.2.0"
|
||||
|
||||
paste = { version = "1.0" }
|
||||
|
||||
tauri = { version = "2.0.0-rc.4", optional = true }
|
||||
tauri = { git = "https://github.com/modrinth/tauri", rev = "5e29428", optional = true }
|
||||
indicatif = { version = "0.17.3", optional = true }
|
||||
|
||||
async-tungstenite = { version = "0.27.0", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
||||
|
||||
@@ -68,7 +68,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
pub name: PathBuf,
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching java version")).await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching java version"))?;
|
||||
let packages = fetch_json::<Vec<Package>>(
|
||||
Method::GET,
|
||||
&format!(
|
||||
@@ -80,7 +80,7 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
&state.fetch_semaphore,
|
||||
&state.pool,
|
||||
).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Downloading java version")).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Downloading java version"))?;
|
||||
|
||||
if let Some(download) = packages.first() {
|
||||
let file = fetch_advanced(
|
||||
@@ -115,13 +115,13 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
||||
}
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting java")).await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting java"))?;
|
||||
archive.extract(&path).map_err(|_| {
|
||||
crate::Error::from(crate::ErrorKind::InputError(
|
||||
"Failed to extract java zip".to_string(),
|
||||
))
|
||||
})?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Done extracting java")).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Done extracting java"))?;
|
||||
let mut base_path = path.join(
|
||||
download
|
||||
.name
|
||||
|
||||
@@ -287,7 +287,7 @@ pub async fn copy_dotminecraft(
|
||||
|
||||
fetch::copy(&src_child, &dst_child, io_semaphore).await?;
|
||||
|
||||
emit_loading(&loading_bar, 1.0, None).await?;
|
||||
emit_loading(&loading_bar, 1.0, None)?;
|
||||
}
|
||||
Ok(loading_bar)
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ pub async fn generate_pack_from_version_id(
|
||||
let state = State::get().await?;
|
||||
|
||||
let loading_bar = if let Some(bar) = initialized_loading_bar {
|
||||
emit_loading(&bar, 0.0, Some("Downloading pack file")).await?;
|
||||
emit_loading(&bar, 0.0, Some("Downloading pack file"))?;
|
||||
bar
|
||||
} else {
|
||||
init_loading(
|
||||
@@ -207,7 +207,7 @@ pub async fn generate_pack_from_version_id(
|
||||
.await?
|
||||
};
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching version")).await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching version"))?;
|
||||
let version = CachedEntry::get_version(
|
||||
&version_id,
|
||||
None,
|
||||
@@ -220,7 +220,7 @@ pub async fn generate_pack_from_version_id(
|
||||
"Invalid version ID specified!".to_string(),
|
||||
)
|
||||
})?;
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
emit_loading(&loading_bar, 10.0, None)?;
|
||||
|
||||
let (url, hash) =
|
||||
if let Some(file) = version.files.iter().find(|x| x.primary) {
|
||||
@@ -248,7 +248,7 @@ pub async fn generate_pack_from_version_id(
|
||||
&state.pool,
|
||||
)
|
||||
.await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata")).await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Fetching project metadata"))?;
|
||||
|
||||
let project = CachedEntry::get_project(
|
||||
&version.project_id,
|
||||
@@ -263,7 +263,7 @@ pub async fn generate_pack_from_version_id(
|
||||
)
|
||||
})?;
|
||||
|
||||
emit_loading(&loading_bar, 10.0, Some("Retrieving icon")).await?;
|
||||
emit_loading(&loading_bar, 10.0, Some("Retrieving icon"))?;
|
||||
let icon = if let Some(icon_url) = project.icon_url {
|
||||
let state = State::get().await?;
|
||||
let icon_bytes =
|
||||
@@ -287,7 +287,7 @@ pub async fn generate_pack_from_version_id(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
emit_loading(&loading_bar, 10.0, None).await?;
|
||||
emit_loading(&loading_bar, 10.0, None)?;
|
||||
|
||||
Ok(CreatePack {
|
||||
file,
|
||||
|
||||
@@ -203,7 +203,7 @@ pub async fn install_zipped_mrpack_files(
|
||||
)
|
||||
.await?;
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides")).await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Extracting overrides"))?;
|
||||
|
||||
let mut total_len = 0;
|
||||
|
||||
@@ -270,8 +270,7 @@ pub async fn install_zipped_mrpack_files(
|
||||
"Extracting override {}/{}",
|
||||
index, total_len
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,15 +40,10 @@ pub mod update;
|
||||
#[tracing::instrument]
|
||||
pub async fn remove(path: &str) -> crate::Result<()> {
|
||||
let state = State::get().await?;
|
||||
|
||||
let mut transaction = state.pool.begin().await?;
|
||||
|
||||
Profile::remove(path, &mut transaction).await?;
|
||||
Profile::remove(path, &state.pool).await?;
|
||||
|
||||
emit_profile(path, ProfilePayloadType::Removed).await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -510,7 +505,7 @@ pub async fn export_mrpack(
|
||||
// Iterate over every file in the folder
|
||||
// Every file that is NOT in the config file is added to the zip, in overrides
|
||||
for path in path_list {
|
||||
emit_loading(&loading_bar, 1.0, None).await?;
|
||||
emit_loading(&loading_bar, 1.0, None)?;
|
||||
|
||||
let relative_path = pack_get_relative_path(&profile_base_path, &path)?;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::event::{
|
||||
};
|
||||
use futures::prelude::*;
|
||||
#[cfg(feature = "tauri")]
|
||||
use tauri::Emitter;
|
||||
use tauri::{Emitter, Manager};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
@@ -33,7 +33,7 @@ const CLI_PROGRESS_BAR_TOTAL: u64 = 1000;
|
||||
pub async fn loading_function() -> crate::Result<()> {
|
||||
let loading_bar = init_loading(LoadingBarType::StateInit, 100.0, "Loading something long...").await;
|
||||
for i in 0..100 {
|
||||
emit_loading(&loading_bar, 1.0, None).await?;
|
||||
emit_loading(&loading_bar, 1.0, None)?;
|
||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,7 @@ pub async fn init_loading_unsafe(
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<LoadingBarId> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
let key = LoadingBarId(Uuid::new_v4());
|
||||
|
||||
event_state.loading_bars.insert(
|
||||
@@ -91,7 +91,7 @@ pub async fn init_loading_unsafe(
|
||||
},
|
||||
);
|
||||
// attempt an initial loading_emit event to the frontend
|
||||
emit_loading(&key, 0.0, None).await?;
|
||||
emit_loading(&key, 0.0, None)?;
|
||||
Ok(key)
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ pub async fn edit_loading(
|
||||
total: f64,
|
||||
title: &str,
|
||||
) -> crate::Result<()> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
|
||||
if let Some(mut bar) = event_state.loading_bars.get_mut(&id.0) {
|
||||
bar.bar_type = bar_type;
|
||||
@@ -132,7 +132,7 @@ pub async fn edit_loading(
|
||||
}
|
||||
};
|
||||
|
||||
emit_loading(id, 0.0, None).await?;
|
||||
emit_loading(id, 0.0, None)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -144,12 +144,12 @@ pub async fn edit_loading(
|
||||
#[allow(unused_variables)]
|
||||
#[tracing::instrument(level = "debug")]
|
||||
|
||||
pub async fn emit_loading(
|
||||
pub fn emit_loading(
|
||||
key: &LoadingBarId,
|
||||
increment_frac: f64,
|
||||
message: Option<&str>,
|
||||
) -> crate::Result<()> {
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
|
||||
let mut loading_bar = match event_state.loading_bars.get_mut(&key.0) {
|
||||
Some(f) => f,
|
||||
@@ -211,7 +211,7 @@ pub async fn emit_loading(
|
||||
pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
event_state
|
||||
.app
|
||||
.emit(
|
||||
@@ -228,18 +228,22 @@ pub async fn emit_warning(message: &str) -> crate::Result<()> {
|
||||
|
||||
// emit_command(CommandPayload::Something { something })
|
||||
// ie: installing a pack, opening an .mrpack, etc
|
||||
// Generally used for url deep links and file opens that we we want to handle in the frontend
|
||||
// Generally used for url deep links and file opens that we want to handle in the frontend
|
||||
#[allow(dead_code)]
|
||||
#[allow(unused_variables)]
|
||||
pub async fn emit_command(command: CommandPayload) -> crate::Result<()> {
|
||||
tracing::debug!("Command: {}", serde_json::to_string(&command)?);
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
event_state
|
||||
.app
|
||||
.emit("command", command)
|
||||
.map_err(EventError::from)?;
|
||||
|
||||
if let Some(window) = event_state.app.get_window("main") {
|
||||
let _ = window.set_focus();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -254,7 +258,7 @@ pub async fn emit_process(
|
||||
) -> crate::Result<()> {
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
event_state
|
||||
.app
|
||||
.emit(
|
||||
@@ -279,7 +283,7 @@ pub async fn emit_profile(
|
||||
) -> crate::Result<()> {
|
||||
#[cfg(feature = "tauri")]
|
||||
{
|
||||
let event_state = crate::EventState::get().await?;
|
||||
let event_state = crate::EventState::get()?;
|
||||
event_state
|
||||
.app
|
||||
.emit(
|
||||
@@ -329,7 +333,7 @@ macro_rules! loading_join {
|
||||
async move {
|
||||
let res = $task.await;
|
||||
if let Some(key) = key {
|
||||
$crate::event::emit::emit_loading(key, increment, message).await?;
|
||||
$crate::event::emit::emit_loading(key, increment, message)?;
|
||||
}
|
||||
res
|
||||
}
|
||||
@@ -376,8 +380,7 @@ where
|
||||
async move {
|
||||
f.await?;
|
||||
if let Some(key) = key {
|
||||
emit_loading(key, total / (num_futs as f64), message)
|
||||
.await?;
|
||||
emit_loading(key, total / (num_futs as f64), message)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -45,21 +45,14 @@ impl EventState {
|
||||
.cloned()
|
||||
}
|
||||
|
||||
#[cfg(feature = "tauri")]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
pub fn get() -> crate::Result<Arc<Self>> {
|
||||
Ok(EVENT_STATE.get().ok_or(EventError::NotInitialized)?.clone())
|
||||
}
|
||||
|
||||
// Initialization requires no app handle in non-tauri mode, so we can just use the same function
|
||||
#[cfg(not(feature = "tauri"))]
|
||||
pub async fn get() -> crate::Result<Arc<Self>> {
|
||||
Self::init().await
|
||||
}
|
||||
|
||||
// Values provided should not be used directly, as they are clones and are not guaranteed to be up-to-date
|
||||
pub async fn list_progress_bars() -> crate::Result<DashMap<Uuid, LoadingBar>>
|
||||
{
|
||||
let value = Self::get().await?;
|
||||
let value = Self::get()?;
|
||||
Ok(value.loading_bars.clone())
|
||||
}
|
||||
|
||||
@@ -67,7 +60,7 @@ impl EventState {
|
||||
pub async fn get_main_window() -> crate::Result<Option<tauri::WebviewWindow>>
|
||||
{
|
||||
use tauri::Manager;
|
||||
let value = Self::get().await?;
|
||||
let value = Self::get()?;
|
||||
Ok(value.app.get_webview_window("main"))
|
||||
}
|
||||
}
|
||||
@@ -95,7 +88,7 @@ impl Drop for LoadingBarId {
|
||||
fn drop(&mut self) {
|
||||
let loader_uuid = self.0;
|
||||
tokio::spawn(async move {
|
||||
if let Ok(event_state) = EventState::get().await {
|
||||
if let Ok(event_state) = EventState::get() {
|
||||
#[cfg(any(feature = "tauri", feature = "cli"))]
|
||||
if let Some((_, bar)) =
|
||||
event_state.loading_bars.remove(&loader_uuid)
|
||||
@@ -180,6 +173,11 @@ pub enum LoadingBarType {
|
||||
import_location: PathBuf,
|
||||
profile_name: String,
|
||||
},
|
||||
CheckingForUpdates,
|
||||
LauncherUpdate {
|
||||
version: String,
|
||||
current_version: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Clone)]
|
||||
|
||||
@@ -111,7 +111,7 @@ pub async fn download_version_info(
|
||||
}?;
|
||||
|
||||
if let Some(loading_bar) = loading_bar {
|
||||
emit_loading(loading_bar, 5.0, None).await?;
|
||||
emit_loading(loading_bar, 5.0, None)?;
|
||||
}
|
||||
|
||||
tracing::debug!("Loaded version info for Minecraft {version_id}");
|
||||
@@ -154,7 +154,7 @@ pub async fn download_client(
|
||||
tracing::trace!("Fetched client version {version}");
|
||||
}
|
||||
if let Some(loading_bar) = loading_bar {
|
||||
emit_loading(loading_bar, 9.0, None).await?;
|
||||
emit_loading(loading_bar, 9.0, None)?;
|
||||
}
|
||||
|
||||
tracing::debug!("Client loaded for version {version}!");
|
||||
@@ -196,7 +196,7 @@ pub async fn download_assets_index(
|
||||
}?;
|
||||
|
||||
if let Some(loading_bar) = loading_bar {
|
||||
emit_loading(loading_bar, 5.0, None).await?;
|
||||
emit_loading(loading_bar, 5.0, None)?;
|
||||
}
|
||||
tracing::debug!("Assets index successfully loaded!");
|
||||
Ok(res)
|
||||
|
||||
@@ -336,8 +336,7 @@ pub async fn install_minecraft(
|
||||
server => "";
|
||||
}
|
||||
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))
|
||||
.await?;
|
||||
emit_loading(&loading_bar, 0.0, Some("Running forge processors"))?;
|
||||
let total_length = processors.len();
|
||||
|
||||
// Forge processors (90-100)
|
||||
@@ -402,8 +401,7 @@ pub async fn install_minecraft(
|
||||
"Running forge processor {}/{}",
|
||||
index, total_length
|
||||
)),
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -414,7 +412,7 @@ pub async fn install_minecraft(
|
||||
async { Ok(()) }
|
||||
})
|
||||
.await?;
|
||||
emit_loading(&loading_bar, 1.0, Some("Finished installing")).await?;
|
||||
emit_loading(&loading_bar, 1.0, Some("Finished installing"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ mod state;
|
||||
|
||||
pub use api::*;
|
||||
pub use error::*;
|
||||
pub use event::{EventState, LoadingBar, LoadingBarType};
|
||||
pub use event::{
|
||||
emit::emit_loading, emit::init_loading, EventState, LoadingBar,
|
||||
LoadingBarType,
|
||||
};
|
||||
pub use logger::start_logger;
|
||||
pub use state::State;
|
||||
|
||||
@@ -271,6 +271,7 @@ pub struct License {
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct GalleryItem {
|
||||
pub url: String,
|
||||
pub raw_url: String,
|
||||
pub featured: bool,
|
||||
pub title: Option<String>,
|
||||
pub description: Option<String>,
|
||||
@@ -703,6 +704,16 @@ impl CachedEntry {
|
||||
.await?;
|
||||
|
||||
for row in query {
|
||||
let row_exists = row.data.is_some();
|
||||
let parsed_data = row
|
||||
.data
|
||||
.and_then(|x| serde_json::from_value::<CacheValue>(x).ok());
|
||||
|
||||
// If data is corrupted/failed to parse ignore it
|
||||
if row_exists && parsed_data.is_none() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if row.expires <= Utc::now().timestamp() {
|
||||
if cache_behaviour == CacheBehaviour::MustRevalidate {
|
||||
continue;
|
||||
@@ -727,10 +738,7 @@ impl CachedEntry {
|
||||
.unwrap_or(false)
|
||||
});
|
||||
|
||||
if let Some(data) = row
|
||||
.data
|
||||
.and_then(|x| serde_json::from_value::<CacheValue>(x).ok())
|
||||
{
|
||||
if let Some(data) = parsed_data {
|
||||
return_vals.push(Self {
|
||||
id: row.id,
|
||||
alias: row.alias,
|
||||
|
||||
@@ -330,8 +330,7 @@ impl DirectoryInfo {
|
||||
&loader_bar_id,
|
||||
10.0 / (MOVE_DIRS.len() as f64),
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
}
|
||||
|
||||
let paths_len = paths.len();
|
||||
@@ -380,8 +379,7 @@ impl DirectoryInfo {
|
||||
&loader_bar_id,
|
||||
90.0 / paths_len as f64,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
success_idxs.insert(idx);
|
||||
|
||||
@@ -433,8 +431,7 @@ impl DirectoryInfo {
|
||||
&loader_bar_id,
|
||||
((x.size as f64) / (total_size as f64)) * 60.0,
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
);
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
}
|
||||
@@ -453,8 +450,7 @@ impl DirectoryInfo {
|
||||
&loader_bar_id,
|
||||
30.0 / paths_len as f64,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
|
||||
Ok::<(), crate::Error>(())
|
||||
};
|
||||
|
||||
@@ -119,7 +119,7 @@ pub(crate) async fn watch_profile(
|
||||
{
|
||||
let path = profile_path.join(folder);
|
||||
|
||||
if !path.exists() {
|
||||
if !path.exists() && !path.is_symlink() {
|
||||
crate::util::io::create_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
|
||||
@@ -501,13 +501,8 @@ impl Profile {
|
||||
|
||||
pub async fn remove(
|
||||
profile_path: &str,
|
||||
transaction: &mut sqlx::Transaction<'_, sqlx::Sqlite>,
|
||||
pool: &SqlitePool,
|
||||
) -> crate::Result<()> {
|
||||
if let Ok(path) = crate::api::profile::get_full_path(profile_path).await
|
||||
{
|
||||
io::remove_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
sqlx::query!(
|
||||
"
|
||||
DELETE FROM profiles
|
||||
@@ -515,9 +510,14 @@ impl Profile {
|
||||
",
|
||||
profile_path
|
||||
)
|
||||
.execute(&mut **transaction)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
if let Ok(path) = crate::api::profile::get_full_path(profile_path).await
|
||||
{
|
||||
io::remove_dir_all(&path).await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -130,8 +130,7 @@ pub async fn fetch_advanced(
|
||||
(chunk.len() as f64 / total_size as f64)
|
||||
* total,
|
||||
None,
|
||||
)
|
||||
.await?;
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(bytes::Bytes::from(bytes))
|
||||
|
||||
@@ -9,9 +9,21 @@ pub trait OsExt {
|
||||
|
||||
/// Gets the OS + Arch of the current system
|
||||
fn native_arch(java_arch: &str) -> Self;
|
||||
|
||||
/// Gets the OS from an OS + Arch
|
||||
fn get_os(&self) -> Self;
|
||||
}
|
||||
|
||||
impl OsExt for Os {
|
||||
fn native() -> Self {
|
||||
match std::env::consts::OS {
|
||||
"windows" => Self::Windows,
|
||||
"macos" => Self::Osx,
|
||||
"linux" => Self::Linux,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
|
||||
fn native_arch(java_arch: &str) -> Self {
|
||||
if std::env::consts::OS == "windows" {
|
||||
if java_arch == "aarch64" {
|
||||
@@ -38,12 +50,13 @@ impl OsExt for Os {
|
||||
}
|
||||
}
|
||||
|
||||
fn native() -> Self {
|
||||
match std::env::consts::OS {
|
||||
"windows" => Self::Windows,
|
||||
"macos" => Self::Osx,
|
||||
"linux" => Self::Linux,
|
||||
_ => Self::Unknown,
|
||||
fn get_os(&self) -> Self {
|
||||
match self {
|
||||
Os::OsxArm64 => Os::Osx,
|
||||
Os::LinuxArm32 => Os::Linux,
|
||||
Os::LinuxArm64 => Os::Linux,
|
||||
Os::WindowsArm64 => Os::Windows,
|
||||
_ => self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,8 +85,8 @@ pub fn os_rule(
|
||||
if minecraft_updated
|
||||
&& (name != &Os::LinuxArm64 || name != &Os::LinuxArm32)
|
||||
{
|
||||
rule_match &=
|
||||
&Os::native() == name || &Os::native_arch(java_arch) == name;
|
||||
rule_match &= Os::native() == name.get_os()
|
||||
|| &Os::native_arch(java_arch) == name;
|
||||
} else {
|
||||
rule_match &= &Os::native_arch(java_arch) == name;
|
||||
}
|
||||
|
||||
@@ -833,7 +833,6 @@ a,
|
||||
// MARKDOWN
|
||||
|
||||
.markdown-body {
|
||||
overflow-y: auto;
|
||||
h1:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
@@ -887,7 +886,7 @@ a,
|
||||
|
||||
a {
|
||||
cursor: pointer;
|
||||
color: var(--color-link);
|
||||
color: var(--color-blue);
|
||||
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
|
||||
26
packages/ui/src/components/base/ContentPageHeader.vue
Normal file
26
packages/ui/src/components/base/ContentPageHeader.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<template>
|
||||
<div
|
||||
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">
|
||||
<slot name="icon" />
|
||||
<div class="flex flex-col gap-1">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<h1 class="m-0 text-2xl font-extrabold leading-none text-contrast">
|
||||
<slot name="title" />
|
||||
</h1>
|
||||
<slot name="title-suffix" />
|
||||
</div>
|
||||
<p class="m-0 line-clamp-2 max-w-[40rem]">
|
||||
<slot name="summary" />
|
||||
</p>
|
||||
<div class="mt-auto flex flex-wrap gap-4">
|
||||
<slot name="stats" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2 items-center">
|
||||
<slot name="actions" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -797,7 +797,7 @@ function openVideoModal() {
|
||||
|
||||
.markdown-resource-link {
|
||||
cursor: pointer;
|
||||
color: var(--color-link);
|
||||
color: var(--color-blue);
|
||||
|
||||
&:focus-visible,
|
||||
&:hover {
|
||||
|
||||
@@ -7,6 +7,7 @@ export { default as Card } from './base/Card.vue'
|
||||
export { default as Checkbox } from './base/Checkbox.vue'
|
||||
export { default as Chips } from './base/Chips.vue'
|
||||
export { default as ConditionalNuxtLink } from './base/ConditionalNuxtLink.vue'
|
||||
export { default as ContentPageHeader } from './base/ContentPageHeader.vue'
|
||||
export { default as CopyCode } from './base/CopyCode.vue'
|
||||
export { default as DoubleIcon } from './base/DoubleIcon.vue'
|
||||
export { default as DropArea } from './base/DropArea.vue'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<NewModal ref="modal" :noblur="noblur" danger>
|
||||
<NewModal ref="modal" :noblur="noblur" danger :on-hide="onHide">
|
||||
<template #title>
|
||||
<slot name="title">
|
||||
<span class="font-extrabold text-contrast text-lg">{{ title }}</span>
|
||||
@@ -76,6 +76,12 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['proceed'])
|
||||
|
||||
@@ -58,6 +58,7 @@ const props = withDefaults(
|
||||
closeOnEsc?: boolean
|
||||
warnOnClose?: boolean
|
||||
header?: string
|
||||
onHide?: () => void
|
||||
}>(),
|
||||
{
|
||||
type: true,
|
||||
@@ -65,6 +66,7 @@ const props = withDefaults(
|
||||
danger: false,
|
||||
closeOnEsc: true,
|
||||
warnOnClose: false,
|
||||
onHide: () => {},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -87,6 +89,7 @@ function show(event?: MouseEvent) {
|
||||
}
|
||||
|
||||
function hide() {
|
||||
props.onHide()
|
||||
visible.value = false
|
||||
window.removeEventListener('mousedown', updateMousePosition)
|
||||
window.removeEventListener('keydown', handleKeyDown)
|
||||
|
||||
@@ -34,6 +34,16 @@ const props = defineProps({
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
noblur: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onHide: {
|
||||
type: Function,
|
||||
default() {
|
||||
return () => {}
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const shareModal = ref(null)
|
||||
@@ -115,7 +125,7 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal ref="shareModal" :header="header">
|
||||
<Modal ref="shareModal" :header="header" :noblur="noblur" :on-hide="onHide">
|
||||
<div class="share-body">
|
||||
<div v-if="link" class="qr-wrapper">
|
||||
<div ref="qrCode">
|
||||
|
||||
1359
pnpm-lock.yaml
generated
1359
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user