Merge remote-tracking branch 'upstream/main' into beta

This commit is contained in:
2025-08-16 22:38:14 +03:00
844 changed files with 105393 additions and 102744 deletions

View File

@@ -3,8 +3,7 @@ root = true
[*] [*]
charset = utf-8 charset = utf-8
indent_style = space indent_style = "tab"
indent_size = 4
end_of_line = lf end_of_line = lf
insert_final_newline = true insert_final_newline = true
trim_trailing_whitespace = true trim_trailing_whitespace = true
@@ -15,5 +14,5 @@ indent_size = 2
max_line_length = off max_line_length = off
trim_trailing_whitespace = false trim_trailing_whitespace = false
[*.{json,yml,yaml,ts,vue,scss,css,html,js,cjs,mjs,gltf,prettierrc}] [*.{json,yml,yaml}]
indent_size = 2 indent_size = 2

12
.vscode/settings.json vendored
View File

@@ -2,8 +2,14 @@
"prettier.endOfLine": "lf", "prettier.endOfLine": "lf",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"], "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
"editor.detectIndentation": true, "editor.detectIndentation": false,
"editor.insertSpaces": false,
"files.eol": "\n",
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit" "source.fixAll.eslint": "explicit",
} "source.organizeImports": "always"
},
"editor.defaultFormatter": "esbenp.prettier-vscode"
} }

View File

@@ -2,7 +2,7 @@
All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package. All packages in this repository are licensed under their respective licenses. For more information, refer to the LICENSE file in each package.
For detailed information, consult each package's COPYING.md file, if available. For detailed information, consult each package's COPYING.md, LICENSE.txt, or LICENSE file, if available.
## Modrinth Branding ## Modrinth Branding

578
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,3 @@
**/dist **/dist
*.gltf *.gltf
src/locales/

View File

@@ -1,22 +1,2 @@
import { createConfigForNuxt } from '@nuxt/eslint-config/flat' import config from '@modrinth/tooling-config/eslint/nuxt.mjs'
import { fixupPluginRules } from '@eslint/compat' export default config
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',
},
},
])

View File

@@ -11,6 +11,7 @@
<body> <body>
<div id="app"></div> <div id="app"></div>
<script src="https://tally.so/widgets/embed.js" async></script>
<script type="module" src="/src/main.js"></script> <script type="module" src="/src/main.js"></script>
</body> </body>
</html> </html>

View File

@@ -41,6 +41,7 @@
"vue-virtual-scroller": "v2.0.0-beta.8" "vue-virtual-scroller": "v2.0.0-beta.8"
}, },
"devDependencies": { "devDependencies": {
"@modrinth/tooling-config": "workspace:*",
"@eslint/compat": "^1.1.1", "@eslint/compat": "^1.1.1",
"@formatjs/cli": "^6.2.12", "@formatjs/cli": "^6.2.12",
"@nuxt/eslint-config": "^0.5.6", "@nuxt/eslint-config": "^0.5.6",
@@ -48,13 +49,11 @@
"@vitejs/plugin-vue": "^5.0.4", "@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19", "autoprefixer": "^10.4.19",
"eslint": "^9.9.1", "eslint": "^9.9.1",
"eslint-config-custom": "workspace:*",
"eslint-plugin-turbo": "^2.5.4", "eslint-plugin-turbo": "^2.5.4",
"postcss": "^8.4.39", "postcss": "^8.4.39",
"prettier": "^3.2.5", "prettier": "^3.2.5",
"sass": "^1.74.1", "sass": "^1.74.1",
"tailwindcss": "^3.4.4", "tailwindcss": "^3.4.4",
"tsconfig": "workspace:*",
"typescript": "^5.5.4", "typescript": "^5.5.4",
"vite": "^5.4.6", "vite": "^5.4.6",
"vue-tsc": "^2.1.6" "vue-tsc": "^2.1.6"

View File

@@ -1,6 +1,4 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref, watch, provide } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import { import {
ArrowBigUpDashIcon, ArrowBigUpDashIcon,
ChangeSkinIcon, ChangeSkinIcon,
@@ -13,21 +11,23 @@ import {
LogOutIcon, LogOutIcon,
MaximizeIcon, MaximizeIcon,
MinimizeIcon, MinimizeIcon,
NewspaperIcon,
NotepadTextIcon,
PlusIcon, PlusIcon,
RestoreIcon, RestoreIcon,
RightArrowIcon, RightArrowIcon,
SettingsIcon, SettingsIcon,
WorldIcon, WorldIcon,
XIcon, XIcon,
NewspaperIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
Avatar, Avatar,
Button, Button,
ButtonStyled, ButtonStyled,
Notifications,
OverflowMenu,
NewsArticleCard, NewsArticleCard,
NotificationPanel,
OverflowMenu,
provideNotificationManager,
} from '@modrinth/ui' } from '@modrinth/ui'
import { useLoading, useTheming } from '@/store/state' import { useLoading, useTheming } from '@/store/state'
// import ModrinthAppLogo from '@/assets/modrinth_app.svg?component' // import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
@@ -39,46 +39,54 @@ import RunningAppBar from '@/components/ui/RunningAppBar.vue'
import SplashScreen from '@/components/ui/SplashScreen.vue' import SplashScreen from '@/components/ui/SplashScreen.vue'
import ErrorModal from '@/components/ui/ErrorModal.vue' import ErrorModal from '@/components/ui/ErrorModal.vue'
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue' import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
import { handleError, useNotifications } from '@/store/notifications.js'
import { command_listener, warning_listener } from '@/helpers/events.js' import { command_listener, warning_listener } from '@/helpers/events.js'
import { type } from '@tauri-apps/plugin-os' import { type } from '@tauri-apps/plugin-os'
import { getOS, isDev } from '@/helpers/utils.js'
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics' import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
import { getCurrentWindow } from '@tauri-apps/api/window' import { getCurrentWindow } from '@tauri-apps/api/window'
import { renderString } from '@modrinth/utils'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue' import { invoke } from '@tauri-apps/api/core'
import { create_profile_and_install_from_file } from './helpers/pack' import { openUrl } from '@tauri-apps/plugin-opener'
import { useError } from '@/store/error.js' import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js' import { $fetch } from 'ofetch'
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue' import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue' import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue' import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
import { useInstall } from '@/store/install.js' import { useInstall } from '@/store/install.js'
import { invoke } from '@tauri-apps/api/core'
import { get_opening_command, initialize_state } from '@/helpers/state' import { get_opening_command, initialize_state } from '@/helpers/state'
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
import { renderString } from '@modrinth/utils'
import { useFetch } from '@/helpers/fetch.js' import { useFetch } from '@/helpers/fetch.js'
// import { check } from '@tauri-apps/plugin-updater'
import NavButton from '@/components/ui/NavButton.vue' import NavButton from '@/components/ui/NavButton.vue'
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js' import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js'
import { get_user } from '@/helpers/cache.js' import { get_user } from '@/helpers/cache.js'
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue' import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue' import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
// import PromotionWrapper from '@/components/ui/PromotionWrapper.vue' import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
// import { hide_ads_window, init_ads_window } from '@/helpers/ads.js'
import FriendsList from '@/components/ui/friends/FriendsList.vue'
import { openUrl } from '@tauri-apps/plugin-opener'
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue' import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
import { get_available_capes, get_available_skins } from './helpers/skins' import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { list } from '@/helpers/profile.js'
import { getOS, isDev } from '@/helpers/utils.js'
import { useError } from '@/store/error.js'
import { create_profile_and_install_from_file } from './helpers/pack'
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer' import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
import { get_available_capes, get_available_skins } from './helpers/skins'
import { AppNotificationManager } from './providers/app-notifications'
// [AR] Feature // [AR] Feature
import { getRemote, updateState } from '@/helpers/update.js' import { getRemote, updateState } from '@/helpers/update.js'
const themeStore = useTheming() const themeStore = useTheming()
const notificationManager = new AppNotificationManager()
provideNotificationManager(notificationManager)
const { handleError, addNotification } = notificationManager
const news = ref([]) const news = ref([])
const availableSurvey = ref(false)
const urlModal = ref(null) const urlModal = ref(null)
@@ -115,15 +123,14 @@ onUnmounted(() => {
}) })
async function setupApp() { async function setupApp() {
stateInitialized.value = true
const settings = await get() const settings = await get()
// Patched // [AR] Patched
settings.personalized_ads = false settings.personalized_ads = false
settings.telemetry = false settings.telemetry = false
await set(settings) await set(settings)
stateInitialized.value = true
const { const {
native_decorations, native_decorations,
theme, theme,
@@ -136,8 +143,7 @@ async function setupApp() {
toggle_sidebar, toggle_sidebar,
developer_mode, developer_mode,
feature_flags, feature_flags,
} = settings } = await get()
if (default_page === 'Library') { if (default_page === 'Library') {
await router.push('/library') await router.push('/library')
@@ -164,7 +170,7 @@ async function setupApp() {
isMaximized.value = await getCurrentWindow().isMaximized() isMaximized.value = await getCurrentWindow().isMaximized()
}) })
initAnalytics() // initAnalytics()
if (!telemetry) { if (!telemetry) {
console.info("[AR] • Telemetry disabled by default (Hard patched).") console.info("[AR] • Telemetry disabled by default (Hard patched).")
optOutAnalytics() optOutAnalytics()
@@ -186,7 +192,7 @@ async function setupApp() {
} }
await warning_listener((e) => await warning_listener((e) =>
notificationsWrapper.value.addNotification({ addNotification({
title: 'Warning', title: 'Warning',
text: e.message, text: e.message,
type: 'warn', type: 'warn',
@@ -240,6 +246,12 @@ async function setupApp() {
} catch (error) { } catch (error) {
console.warn('Failed to generate skin previews in app setup.', error) console.warn('Failed to generate skin previews in app setup.', error)
} }
if (osType === 'windows') {
await processPendingSurveys()
} else {
console.info('Skipping user surveys on non-Windows platforms')
}
} }
const stateFailed = ref(false) const stateFailed = ref(false)
@@ -271,9 +283,6 @@ const route = useRoute()
const loading = useLoading() const loading = useLoading()
loading.setEnabled(false) loading.setEnabled(false)
const notifications = useNotifications()
const notificationsWrapper = ref()
const error = useError() const error = useError()
const errorModal = ref() const errorModal = ref()
@@ -355,8 +364,6 @@ const sidebarVisible = computed(() => sidebarToggled.value || forceSidebar.value
onMounted(() => { onMounted(() => {
invoke('show_window') invoke('show_window')
notifications.setNotifs(notificationsWrapper.value)
error.setErrorModal(errorModal.value) error.setErrorModal(errorModal.value)
install.setIncompatibilityWarningModal(incompatibilityWarningModal) install.setIncompatibilityWarningModal(incompatibilityWarningModal)
@@ -432,6 +439,116 @@ function handleAuxClick(e) {
e.target.dispatchEvent(event) e.target.dispatchEvent(event)
} }
} }
function cleanupOldSurveyDisplayData() {
const threeWeeksAgo = new Date()
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21)
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i)
if (key.startsWith('survey-') && key.endsWith('-display')) {
const dateValue = new Date(localStorage.getItem(key))
if (dateValue < threeWeeksAgo) {
localStorage.removeItem(key)
}
}
}
}
async function openSurvey() {
if (!availableSurvey.value) {
console.error('No survey to open')
return
}
const creds = await getCreds().catch(handleError)
const userId = creds?.user_id
const formId = availableSurvey.value.tally_id
const popupOptions = {
layout: 'modal',
width: 700,
autoClose: 2000,
hideTitle: true,
hiddenFields: {
user_id: userId,
},
onOpen: () => console.info('Opened user survey'),
onClose: () => {
console.info('Closed user survey')
// show_ads_window()
},
onSubmit: () => console.info('Active user survey submitted'),
}
try {
// hide_ads_window()
if (window.Tally?.openPopup) {
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
dismissSurvey()
window.Tally.openPopup(formId, popupOptions)
} else {
console.warn('Tally script not yet loaded')
// show_ads_window()
}
} catch (e) {
console.error('Error opening Tally popup:', e)
// show_ads_window()
}
console.info(`Found user survey to show with tally_id: ${formId}`)
window.Tally.openPopup(formId, popupOptions)
}
function dismissSurvey() {
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, new Date())
availableSurvey.value = undefined
}
async function processPendingSurveys() {
function isWithinLastTwoWeeks(date) {
const twoWeeksAgo = new Date()
twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14)
return date >= twoWeeksAgo
}
cleanupOldSurveyDisplayData()
const creds = await getCreds().catch(handleError)
const userId = creds?.user_id
const instances = await list().catch(handleError)
const isActivePlayer =
instances.findIndex(
(instance) =>
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
) >= 0
let surveys = []
try {
surveys = await $fetch('https://api.modrinth.com/v2/surveys')
} catch (e) {
console.error('Error fetching surveys:', e)
}
const surveyToShow = surveys.find(
(survey) =>
!!(
localStorage.getItem(`survey-${survey.id}-display`) === null &&
survey.type === 'tally_app' &&
((survey.condition === 'active_player' && isActivePlayer) ||
(survey.assigned_users?.includes(userId) && !survey.dismissed_users?.includes(userId)))
),
)
if (surveyToShow) {
availableSurvey.value = surveyToShow
} else {
console.info('No user survey to show')
}
}
</script> </script>
<template> <template>
@@ -491,7 +608,6 @@ function handleAuxClick(e) {
<PlusIcon /> <PlusIcon />
</NavButton> </NavButton>
<div class="flex flex-grow"></div> <div class="flex flex-grow"></div>
<!-- [AR] TODO -->
<!-- <NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()"> <!-- <NavButton v-if="updateAvailable" v-tooltip.right="'Install update'" :to="() => restartApp()">
<DownloadIcon /> <DownloadIcon />
</NavButton> --> </NavButton> -->
@@ -593,6 +709,28 @@ function handleAuxClick(e) {
:class="{ 'sidebar-enabled': sidebarVisible }" :class="{ 'sidebar-enabled': sidebarVisible }"
> >
<div class="app-viewport flex-grow router-view"> <div class="app-viewport flex-grow router-view">
<transition name="popup-survey">
<div
v-if="availableSurvey"
class="w-[400px] z-20 fixed -bottom-12 pb-16 right-[--right-bar-width] mr-4 rounded-t-2xl card-shadow bg-bg-raised border-divider border-[1px] border-solid border-b-0 p-4"
>
<h2 class="text-lg font-extrabold mt-0 mb-2">Hey there Modrinth user!</h2>
<p class="m-0 leading-tight">
Would you mind answering a few questions about your experience with Modrinth App?
</p>
<p class="mt-3 mb-4 leading-tight">
This feedback will go directly to the Modrinth team and help guide future updates!
</p>
<div class="flex gap-2">
<ButtonStyled color="brand">
<button @click="openSurvey"><NotepadTextIcon /> Take survey</button>
</ButtonStyled>
<ButtonStyled>
<button @click="dismissSurvey"><XIcon /> No thanks</button>
</ButtonStyled>
</div>
</div>
</transition>
<div <div
class="loading-indicator-container h-8 fixed z-50" class="loading-indicator-container h-8 fixed z-50"
:style="{ :style="{
@@ -672,7 +810,7 @@ function handleAuxClick(e) {
</div> </div>
</div> </div>
</div> </div>
<!-- <template v-if="showAd"> <template v-if="showAd">
<a <a
href="https://modrinth.plus?app" href="https://modrinth.plus?app"
class="absolute bottom-[250px] w-full flex justify-center items-center gap-1 px-4 py-3 text-purple font-medium hover:underline z-10" class="absolute bottom-[250px] w-full flex justify-center items-center gap-1 px-4 py-3 text-purple font-medium hover:underline z-10"
@@ -680,12 +818,12 @@ function handleAuxClick(e) {
> >
<ArrowBigUpDashIcon class="text-2xl" /> Upgrade to Modrinth+ <ArrowBigUpDashIcon class="text-2xl" /> Upgrade to Modrinth+
</a> </a>
<PromotionWrapper /> <!-- <PromotionWrapper /> -->
</template> --> </template>
</div> </div>
</div> </div>
<URLConfirmModal ref="urlModal" /> <URLConfirmModal ref="urlModal" />
<Notifications ref="notificationsWrapper" sidebar /> <NotificationPanel has-sidebar />
<ErrorModal ref="errorModal" /> <ErrorModal ref="errorModal" />
<ModInstallModal ref="modInstallModal" /> <ModInstallModal ref="modInstallModal" />
<IncompatibilityWarningModal ref="incompatibilityWarningModal" /> <IncompatibilityWarningModal ref="incompatibilityWarningModal" />
@@ -893,6 +1031,26 @@ function handleAuxClick(e) {
.sidebar-teleport-content:empty + .sidebar-default-content.sidebar-enabled { .sidebar-teleport-content:empty + .sidebar-default-content.sidebar-enabled {
display: contents; display: contents;
} }
.popup-survey-enter-active {
transition:
opacity 0.25s ease,
transform 0.25s cubic-bezier(0.51, 1.08, 0.35, 1.15);
transform-origin: top center;
}
.popup-survey-leave-active {
transition:
opacity 0.25s ease,
transform 0.25s cubic-bezier(0.68, -0.17, 0.23, 0.11);
transform-origin: top center;
}
.popup-survey-enter-from,
.popup-survey-leave-to {
opacity: 0;
transform: translateY(10rem) scale(0.8) scaleY(1.6);
}
</style> </style>
<style> <style>
.mac { .mac {

View File

@@ -1,18 +1,18 @@
export { default as ATLauncherIcon } from './atlauncher.svg'
export { default as BuyMeACoffeeIcon } from './bmac.svg' export { default as BuyMeACoffeeIcon } from './bmac.svg'
export { default as DiscordIcon } from './discord.svg' export { default as DiscordIcon } from './discord.svg'
export { default as GDLauncherIcon } from './gdlauncher.png'
export { default as GithubIcon } from './github.svg'
export { default as GitLabIcon } from './gitlab.svg'
export { default as GoogleIcon } from './google.svg'
export { default as KoFiIcon } from './kofi.svg' export { default as KoFiIcon } from './kofi.svg'
export { default as MastodonIcon } from './mastodon.svg'
export { default as MicrosoftIcon } from './microsoft.svg'
export { default as MultiMCIcon } from './multimc.webp'
export { default as OpenCollectiveIcon } from './opencollective.svg'
export { default as PatreonIcon } from './patreon.svg' export { default as PatreonIcon } from './patreon.svg'
export { default as PaypalIcon } from './paypal.svg' export { default as PaypalIcon } from './paypal.svg'
export { default as OpenCollectiveIcon } from './opencollective.svg'
export { default as TwitterIcon } from './twitter.svg'
export { default as GithubIcon } from './github.svg'
export { default as MastodonIcon } from './mastodon.svg'
export { default as RedditIcon } from './reddit.svg'
export { default as GoogleIcon } from './google.svg'
export { default as MicrosoftIcon } from './microsoft.svg'
export { default as SteamIcon } from './steam.svg'
export { default as GitLabIcon } from './gitlab.svg'
export { default as ATLauncherIcon } from './atlauncher.svg'
export { default as GDLauncherIcon } from './gdlauncher.png'
export { default as MultiMCIcon } from './multimc.webp'
export { default as PrismIcon } from './prism.svg' export { default as PrismIcon } from './prism.svg'
export { default as RedditIcon } from './reddit.svg'
export { default as SteamIcon } from './steam.svg'
export { default as TwitterIcon } from './twitter.svg'

View File

@@ -1,12 +1,12 @@
export { default as SwapIcon } from './arrow-left-right.svg'
export { default as ToggleIcon } from './toggle.svg'
export { default as PackageIcon } from './package.svg'
export { default as VersionIcon } from './milestone.svg'
export { default as TextInputIcon } from './text-cursor-input.svg'
export { default as AddProjectImage } from './add-project.svg' export { default as AddProjectImage } from './add-project.svg'
export { default as NewInstanceImage } from './new-instance.svg' export { default as SwapIcon } from './arrow-left-right.svg'
export { default as MenuIcon } from './menu.svg' export { default as MenuIcon } from './menu.svg'
export { default as ChatIcon } from './messages-square.svg' export { default as ChatIcon } from './messages-square.svg'
export { default as Pirate } from './pirate.svg' export { default as Pirate } from './pirate.svg'
export { default as Microsoft } from './microsoft.svg' export { default as Microsoft } from './microsoft.svg'
export { default as PirateShip } from './pirate-ship.svg' export { default as PirateShip } from './pirate-ship.svg'
export { default as VersionIcon } from './milestone.svg'
export { default as NewInstanceImage } from './new-instance.svg'
export { default as PackageIcon } from './package.svg'
export { default as TextInputIcon } from './text-cursor-input.svg'
export { default as ToggleIcon } from './toggle.svg'

View File

@@ -1,24 +1,26 @@
<script setup> <script setup>
import Instance from '@/components/ui/Instance.vue'
import { computed, ref } from 'vue'
import { import {
ClipboardCopyIcon, ClipboardCopyIcon,
EyeIcon,
FolderOpenIcon, FolderOpenIcon,
PlayIcon, PlayIcon,
PlusIcon, PlusIcon,
TrashIcon,
StopCircleIcon,
EyeIcon,
SearchIcon, SearchIcon,
StopCircleIcon,
TrashIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button, DropdownSelect } from '@modrinth/ui' import { Button, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
import { formatCategoryHeader } from '@modrinth/utils' import { formatCategoryHeader } from '@modrinth/utils'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { duplicate, remove } from '@/helpers/profile.js' import { computed, ref } from 'vue'
import { handleError } from '@/store/notifications.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import Instance from '@/components/ui/Instance.vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue' import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { duplicate, remove } from '@/helpers/profile.js'
const { handleError } = injectNotificationManager()
const props = defineProps({ const props = defineProps({
instances: { instances: {

View File

@@ -1,5 +1,6 @@
<script setup> <script setup>
import { computed, onBeforeUnmount, ref, watch } from 'vue' import { computed, onBeforeUnmount, ref, watch } from 'vue'
import { useLoading } from '@/store/state.js' import { useLoading } from '@/store/state.js'
const props = defineProps({ const props = defineProps({

View File

@@ -1,31 +1,33 @@
<script setup> <script setup>
import { import {
ClipboardCopyIcon, ClipboardCopyIcon,
FolderOpenIcon,
PlayIcon,
PlusIcon,
TrashIcon,
DownloadIcon, DownloadIcon,
GlobeIcon,
StopCircleIcon,
ExternalIcon, ExternalIcon,
EyeIcon, EyeIcon,
FolderOpenIcon,
GlobeIcon,
PlayIcon,
PlusIcon,
StopCircleIcon,
TrashIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue' import { HeadingLink, injectNotificationManager } from '@modrinth/ui'
import Instance from '@/components/ui/Instance.vue' import { openUrl } from '@tauri-apps/plugin-opener'
import { computed, onMounted, onUnmounted, ref } from 'vue' import { computed, onMounted, onUnmounted, ref } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import ProjectCard from '@/components/ui/ProjectCard.vue'
import { get_by_profile_path } from '@/helpers/process.js'
import { handleError } from '@/store/notifications.js'
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { showProfileInFolder } from '@/helpers/utils.js'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import Instance from '@/components/ui/Instance.vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import ProjectCard from '@/components/ui/ProjectCard.vue'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { get_by_profile_path } from '@/helpers/process.js'
import { duplicate, kill, remove, run } from '@/helpers/profile.js'
import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js' import { handleSevereError } from '@/store/error.js'
import { install as installVersion } from '@/store/install.js' import { install as installVersion } from '@/store/install.js'
import { openUrl } from '@tauri-apps/plugin-opener'
import { HeadingLink } from '@modrinth/ui' const { handleError } = injectNotificationManager()
const router = useRouter() const router = useRouter()
@@ -163,7 +165,14 @@ const handleOptionsClick = async (args) => {
await navigator.clipboard.writeText(args.item.path) await navigator.clipboard.writeText(args.item.path)
break break
case 'install': { case 'install': {
await installVersion(args.item.project_id, null, null, 'ProjectCardContextMenu') await installVersion(
args.item.project_id,
null,
null,
'ProjectCardContextMenu',
() => {},
() => {},
).catch(handleError)
break break
} }

View File

@@ -20,7 +20,8 @@
<Avatar size="xs" :src="avatarUrl" /> <Avatar size="xs" :src="avatarUrl" />
<div> <div>
<h4> <h4>
<component :is="getAccountType(selectedAccount)" class="vector-icon" /> {{ selectedAccount.profile.name }} <component :is="getAccountType(selectedAccount)" class="vector-icon" /> {{
selectedAccount.profile.name }}
</h4> </h4>
<p>Selected</p> <p>Selected</p>
</div> </div>
@@ -107,9 +108,7 @@
</div> </div>
</div> </div>
</ModalWrapper> </ModalWrapper>
<ModalWrapper <ModalWrapper ref="authenticationElybyErrorModal" class="modal"
ref="authenticationElybyErrorModal"
class="modal"
header="Error while proceeding authentication event with Ely.by"> header="Error while proceeding authentication event with Ely.by">
<div class="flex flex-col gap-4 px-6 py-5"> <div class="flex flex-col gap-4 px-6 py-5">
<label class="text-base font-medium text-red-700"> <label class="text-base font-medium text-red-700">
@@ -181,7 +180,7 @@ import {
ElyByIcon, ElyByIcon,
SpinnerIcon SpinnerIcon
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, Button, Card } from '@modrinth/ui' import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue' import { ref, computed, onMounted, onBeforeUnmount, onUnmounted } from 'vue'
import { import {
elyby_auth_authenticate, elyby_auth_authenticate,
@@ -193,13 +192,14 @@ import {
login as login_flow, login as login_flow,
get_default_user, get_default_user,
} from '@/helpers/auth' } from '@/helpers/auth'
import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events' import { process_listener } from '@/helpers/events'
import { handleSevereError } from '@/store/error.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_available_skins } from '@/helpers/skins'
import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts' import { getPlayerHeadUrl } from '@/helpers/rendering/batch-skin-renderer.ts'
import { get_available_skins } from '@/helpers/skins'
import { handleSevereError } from '@/store/error.js'
const { handleError } = injectNotificationManager()
defineProps({ defineProps({
mode: { mode: {
@@ -417,7 +417,7 @@ function setLoginDisabled(value) {
defineExpose({ defineExpose({
refreshValues, refreshValues,
setLoginDisabled, setLoginDisabled,
loginDisabled: microsoftLoginDisabled, microsoftLoginDisabled,
}) })
await refreshValues() await refreshValues()

View File

@@ -1,11 +1,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { DropdownIcon, PlusIcon, FolderOpenIcon } from '@modrinth/assets' import { DropdownIcon, FolderOpenIcon, PlusIcon } from '@modrinth/assets'
import { ButtonStyled, OverflowMenu } from '@modrinth/ui' import { ButtonStyled, injectNotificationManager, OverflowMenu } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { add_project_from_path } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { add_project_from_path } from '@/helpers/profile.js'
const { handleError } = injectNotificationManager()
const props = defineProps({ const props = defineProps({
instance: { instance: {
type: Object, type: Object,

View File

@@ -42,11 +42,12 @@
</template> </template>
<script setup> <script setup>
import { ChevronRightIcon, ChevronLeftIcon } from '@modrinth/assets' import { ChevronLeftIcon, ChevronRightIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui' import { Button } from '@modrinth/ui'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { useRoute } from 'vue-router'
import { computed } from 'vue' import { computed } from 'vue'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const route = useRoute() const route = useRoute()

View File

@@ -1,13 +1,21 @@
<template> <template>
<transition name="fade"> <transition name="fade">
<div v-show="shown" ref="contextMenu" class="context-menu" :style="{ <div
v-show="shown"
ref="contextMenu"
class="context-menu"
:style="{
left: left, left: left,
top: top, top: top,
}"> }"
>
<div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)"> <div v-for="(option, index) in options" :key="index" @click.stop="optionClicked(option.name)">
<hr v-if="option.type === 'divider'" class="divider" /> <hr v-if="option.type === 'divider'" class="divider" />
<div v-else-if="!(isLinkedData(item) && option.name === `add_content`)" class="item clickable" <div
:class="[option.color ?? 'base']"> v-else-if="!(isLinkedData(item) && option.name === `add_content`)"
class="item clickable"
:class="[option.color ?? 'base']"
>
<slot :name="option.name" /> <slot :name="option.name" />
</div> </div>
</div> </div>

View File

@@ -5,17 +5,9 @@
<p class="input-label">Profile Code</p> <p class="input-label">Profile Code</p>
<div class="iconified-input"> <div class="iconified-input">
<SearchIcon aria-hidden="true" class="text-lg" /> <SearchIcon aria-hidden="true" class="text-lg" />
<input <input ref="codeInput" v-model="profileCode" autocomplete="off" class="h-12 card-shadow"
ref="codeInput" spellcheck="false" type="text" placeholder="Enter CurseForge profile code" maxlength="20"
v-model="profileCode" @keyup.enter="importProfile" />
autocomplete="off"
class="h-12 card-shadow"
spellcheck="false"
type="text"
placeholder="Enter CurseForge profile code"
maxlength="20"
@keyup.enter="importProfile"
/>
<Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')"> <Button v-if="profileCode" class="r-btn" @click="() => (profileCode = '')">
<XIcon /> <XIcon />
</Button> </Button>
@@ -37,10 +29,7 @@
<span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span> <span class="progress-percentage">{{ Math.floor(importProgress.percentage) }}%</span>
</div> </div>
<div class="progress-bar-container"> <div class="progress-bar-container">
<div <div class="progress-bar" :style="{ width: `${importProgress.percentage}%` }"></div>
class="progress-bar"
:style="{ width: `${importProgress.percentage}%` }"
></div>
</div> </div>
</div> </div>
@@ -49,21 +38,12 @@
<XIcon /> <XIcon />
Cancel Cancel
</Button> </Button>
<Button <Button v-if="!metadata" @click="fetchMetadata" :disabled="!profileCode.trim() || fetching"
v-if="!metadata" color="secondary">
@click="fetchMetadata"
:disabled="!profileCode.trim() || fetching"
color="secondary"
>
<SearchIcon v-if="!fetching" /> <SearchIcon v-if="!fetching" />
{{ fetching ? 'Checking...' : 'Check Profile' }} {{ fetching ? 'Checking...' : 'Check Profile' }}
</Button> </Button>
<Button <Button v-if="metadata" @click="importProfile" :disabled="importing" color="primary">
v-if="metadata"
@click="importProfile"
:disabled="importing"
color="primary"
>
<DownloadIcon v-if="!importing" /> <DownloadIcon v-if="!importing" />
{{ importing ? 'Importing...' : 'Import Profile' }} {{ importing ? 'Importing...' : 'Import Profile' }}
</Button> </Button>
@@ -86,7 +66,6 @@ import {
fetch_curseforge_profile_metadata, fetch_curseforge_profile_metadata,
import_curseforge_profile import_curseforge_profile
} from '@/helpers/import.js' } from '@/helpers/import.js'
import { handleError } from '@/store/notifications.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { loading_listener } from '@/helpers/events.js' import { loading_listener } from '@/helpers/events.js'

View File

@@ -1,26 +1,30 @@
<script setup> <script setup>
import { import {
CheckIcon, CheckIcon,
CopyIcon,
DropdownIcon, DropdownIcon,
XIcon,
HammerIcon, HammerIcon,
LogInIcon, LogInIcon,
UpdatedIcon, UpdatedIcon,
CopyIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { ButtonStyled, Collapsible, injectNotificationManager } from '@modrinth/ui'
import { computed, ref } from 'vue'
import { ChatIcon } from '@/assets/icons' import { ChatIcon } from '@/assets/icons'
import { ButtonStyled, Collapsible } from '@modrinth/ui'
import { ref, computed } from 'vue'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { handleError } from '@/store/notifications.js'
import { handleSevereError } from '@/store/error.js'
import { cancel_directory_change } from '@/helpers/settings.ts'
import { install } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { login as login_flow, set_default_user } from '@/helpers/auth.js'
import { install } from '@/helpers/profile.js'
import { cancel_directory_change } from '@/helpers/settings.ts'
import { handleSevereError } from '@/store/error.js'
// [AR] Feature
import { applyMigrationFix } from '@/helpers/utils.js' import { applyMigrationFix } from '@/helpers/utils.js'
import { restartApp } from '@/helpers/utils.js' import { restartApp } from '@/helpers/utils.js'
const { handleError } = injectNotificationManager()
const errorModal = ref() const errorModal = ref()
const error = ref() const error = ref()
const closable = ref(true) const closable = ref(true)
@@ -327,20 +331,10 @@ async function onApplyMigrationFix(eol) {
<template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template> <template v-if="copied"> <CheckIcon class="text-green" /> Copied! </template>
<template v-else> <CopyIcon /> Copy debug info </template> <template v-else> <CopyIcon /> Copy debug info </template>
</button> </button>
<ButtonStyled class="neon-button neon">
<a href="https://me.astralium.su/get/ar/help" target="_blank" rel="noopener noreferrer">
Get AstralRinth support
</a>
</ButtonStyled>
<ButtonStyled class="neon-button neon" >
<a href="https://me.astralium.su/get/ar" target="_blank" rel="noopener noreferrer">
Checkout latest releases
</a>
</ButtonStyled>
</ButtonStyled> </ButtonStyled>
</div> </div>
<template v-if="hasDebugInfo"> <template v-if="hasDebugInfo">
<div class="bg-button-bg rounded-xl mt-2 overflow-hidden"> <div class="bg-button-bg rounded-xl mt-2 overflow-clip">
<button <button
class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer" class="flex items-center justify-between w-full bg-transparent border-0 px-4 py-3 cursor-pointer"
@click="errorCollapsed = !errorCollapsed" @click="errorCollapsed = !errorCollapsed"
@@ -352,9 +346,7 @@ async function onApplyMigrationFix(eol) {
/> />
</button> </button>
<Collapsible :collapsed="errorCollapsed"> <Collapsible :collapsed="errorCollapsed">
<pre <pre class="m-0 px-4 py-3 bg-bg rounded-none">{{ debugInfo }}</pre>
class="m-0 px-4 py-3 bg-bg rounded-none whitespace-pre-wrap break-words overflow-x-auto max-w-full"
>{{ debugInfo }}</pre>
</Collapsible> </Collapsible>
</div> </div>
<template v-if="errorType === 'state_init'"> <template v-if="errorType === 'state_init'">

View File

@@ -1,12 +1,14 @@
<script setup> <script setup>
import { XIcon, PlusIcon } from '@modrinth/assets' import { PlusIcon, XIcon } from '@modrinth/assets'
import { Button, Checkbox } from '@modrinth/ui' import { Button, Checkbox, injectNotificationManager } from '@modrinth/ui'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import { ref } from 'vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { handleError } from '@/store/notifications.js' import { ref } from 'vue'
import { PackageIcon, VersionIcon } from '@/assets/icons'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { export_profile_mrpack, get_pack_export_candidates } from '@/helpers/profile.js'
const { handleError } = injectNotificationManager()
const props = defineProps({ const props = defineProps({
instance: { instance: {

View File

@@ -1,6 +1,4 @@
<script setup> <script setup>
import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { import {
DownloadIcon, DownloadIcon,
GameIcon, GameIcon,
@@ -9,17 +7,20 @@ import {
StopCircleIcon, StopCircleIcon,
TimerIcon, TimerIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, ButtonStyled, useRelativeTime } from '@modrinth/ui' import { Avatar, ButtonStyled, injectNotificationManager, useRelativeTime } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import { finish_install, kill, run } from '@/helpers/profile' import dayjs from 'dayjs'
import { get_by_profile_path } from '@/helpers/process' import { computed, onMounted, onUnmounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
import { process_listener } from '@/helpers/events' import { process_listener } from '@/helpers/events'
import { handleError } from '@/store/state.js' import { get_by_profile_path } from '@/helpers/process'
import { finish_install, kill, run } from '@/helpers/profile'
import { showProfileInFolder } from '@/helpers/utils.js' import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js' import { handleSevereError } from '@/store/error.js'
import { trackEvent } from '@/helpers/analytics'
import dayjs from 'dayjs'
const { handleError } = injectNotificationManager()
const formatRelativeTime = useRelativeTime() const formatRelativeTime = useRelativeTime()
const props = defineProps({ const props = defineProps({
@@ -93,7 +94,7 @@ const stop = async (e, context) => {
const repair = async (e) => { const repair = async (e) => {
e?.stopPropagation() e?.stopPropagation()
await finish_install(props.instance) await finish_install(props.instance).catch(handleError)
} }
const openFolder = async () => { const openFolder = async () => {

View File

@@ -206,8 +206,6 @@
</template> </template>
<script setup> <script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
import { import {
CodeIcon, CodeIcon,
FolderOpenIcon, FolderOpenIcon,
@@ -218,24 +216,29 @@ import {
UploadIcon, UploadIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, Button, Checkbox, Chips } from '@modrinth/ui' import { Avatar, Button, Checkbox, Chips, injectNotificationManager } from '@modrinth/ui'
import { computed, onUnmounted, ref, shallowRef } from 'vue'
import { get_loaders } from '@/helpers/tags'
import { create } from '@/helpers/profile'
import { open } from '@tauri-apps/plugin-dialog'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import { get_game_versions, get_loader_versions } from '@/helpers/metadata' import { getCurrentWebview } from '@tauri-apps/api/webview'
import { handleError } from '@/store/notifications.js' import { open } from '@tauri-apps/plugin-dialog'
import { computed, onUnmounted, ref, shallowRef } from 'vue'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ProgressBar from '@/components/ui/ProgressBar.vue'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { create_profile_and_install_from_file } from '@/helpers/pack.js'
import { import {
get_default_launcher_path, get_default_launcher_path,
get_importable_instances, get_importable_instances,
import_instance, import_instance,
} from '@/helpers/import.js' } from '@/helpers/import.js'
import ProgressBar from '@/components/ui/ProgressBar.vue' import { get_game_versions, get_loader_versions } from '@/helpers/metadata'
import { getCurrentWebview } from '@tauri-apps/api/webview' import { create_profile_and_install_from_file } from '@/helpers/pack.js'
import { create } from '@/helpers/profile'
import { get_loaders } from '@/helpers/tags'
import CurseForgeProfileImportModal from '@/components/ui/CurseForgeProfileImportModal.vue'
const { handleError } = injectNotificationManager()
const profile_name = ref('') const profile_name = ref('')
const game_version = ref('') const game_version = ref('')

View File

@@ -1,8 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { convertFileSrc } from '@tauri-apps/api/core'
import { formatCategory } from '@modrinth/utils'
import { GameIcon, LeftArrowIcon } from '@modrinth/assets' import { GameIcon, LeftArrowIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled } from '@modrinth/ui' import { Avatar, ButtonStyled } from '@modrinth/ui'
import { formatCategory } from '@modrinth/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
type Instance = { type Instance = {
game_version: string game_version: string

View File

@@ -35,13 +35,15 @@
</ModalWrapper> </ModalWrapper>
</template> </template>
<script setup> <script setup>
import { PlusIcon, CheckIcon, XIcon } from '@modrinth/assets' import { CheckIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui' import { Button, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import { find_filtered_jres } from '@/helpers/jre.js'
import { handleError } from '@/store/notifications.js'
import { trackEvent } from '@/helpers/analytics'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { find_filtered_jres } from '@/helpers/jre.js'
const { handleError } = injectNotificationManager()
const chosenInstallOptions = ref([]) const chosenInstallOptions = ref([])
const detectJavaModal = ref(null) const detectJavaModal = ref(null)

View File

@@ -53,20 +53,22 @@
<script setup> <script setup>
import { import {
SearchIcon,
PlayIcon,
CheckIcon, CheckIcon,
XIcon,
FolderSearchIcon,
DownloadIcon, DownloadIcon,
FolderSearchIcon,
PlayIcon,
SearchIcon,
XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button } from '@modrinth/ui' import { Button, injectNotificationManager } from '@modrinth/ui'
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
import { ref } from 'vue'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { ref } from 'vue'
import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue' import JavaDetectionModal from '@/components/ui/JavaDetectionModal.vue'
import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { auto_install_java, find_filtered_jres, get_jre, test_jre } from '@/helpers/jre.js'
const { handleError } = injectNotificationManager()
const props = defineProps({ const props = defineProps({
version: { version: {

View File

@@ -1,11 +1,12 @@
<script setup> <script setup>
import { CheckIcon } from '@modrinth/assets' import { CheckIcon } from '@modrinth/assets'
import { Button, Badge } from '@modrinth/ui' import { Badge, Button } from '@modrinth/ui'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { releaseColor } from '@/helpers/utils'
import { SwapIcon } from '@/assets/icons/index.js' import { SwapIcon } from '@/assets/icons/index.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { update_managed_modrinth_version } from '@/helpers/profile'
import { releaseColor } from '@/helpers/utils'
const props = defineProps({ const props = defineProps({
versions: { versions: {

View File

@@ -30,7 +30,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onMounted, onUnmounted, ref, watch } from 'vue' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import type { RouteLocationRaw } from 'vue-router' import type { RouteLocationRaw } from 'vue-router'
import { useRoute, RouterLink } from 'vue-router' import { RouterLink, useRoute } from 'vue-router'
const route = useRoute() const route = useRoute()

View File

@@ -1,10 +1,10 @@
<script setup> <script setup>
import { Avatar, TagItem } from '@modrinth/ui'
import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets' import { DownloadIcon, HeartIcon, TagIcon } from '@modrinth/assets'
import { formatNumber, formatCategory } from '@modrinth/utils' import { Avatar, TagItem } from '@modrinth/ui'
import { computed } from 'vue' import { formatCategory, formatNumber } from '@modrinth/utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { computed } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)

View File

@@ -1,13 +1,15 @@
<script setup> <script setup>
import { list } from '@/helpers/profile' import { SpinnerIcon } from '@modrinth/assets'
import { handleError } from '@/store/notifications' import { Avatar, injectNotificationManager } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { onUnmounted, ref } from 'vue' import { onUnmounted, ref } from 'vue'
import { profile_listener } from '@/helpers/events.js'
import NavButton from '@/components/ui/NavButton.vue' import NavButton from '@/components/ui/NavButton.vue'
import { Avatar } from '@modrinth/ui' import { profile_listener } from '@/helpers/events.js'
import { convertFileSrc } from '@tauri-apps/api/core' import { list } from '@/helpers/profile'
import { SpinnerIcon } from '@modrinth/assets'
const { handleError } = injectNotificationManager()
const recentInstances = ref([]) const recentInstances = ref([])
const getInstances = async () => { const getInstances = async () => {

View File

@@ -20,12 +20,21 @@
> >
{{ selectedProcess.profile.name }} {{ selectedProcess.profile.name }}
</router-link> </router-link>
<div v-if="currentProcesses.length > 1" class="arrow button-base" :class="{ rotate: showProfiles }" <div
@click="toggleProfiles()"> v-if="currentProcesses.length > 1"
class="arrow button-base"
:class="{ rotate: showProfiles }"
@click="toggleProfiles()"
>
<DropdownIcon /> <DropdownIcon />
</div> </div>
</div> </div>
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click="stop(selectedProcess)"> <Button
v-tooltip="'Stop instance'"
icon-only
class="icon-button stop"
@click="stop(selectedProcess)"
>
<StopCircleIcon /> <StopCircleIcon />
</Button> </Button>
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()"> <Button v-tooltip="'View logs'" icon-only class="icon-button" @click="goToTerminal()">
@@ -45,20 +54,39 @@
</h3> </h3>
<ProgressBar :progress="Math.floor((100 * loadingBar.current) / loadingBar.total)" /> <ProgressBar :progress="Math.floor((100 * loadingBar.current) / loadingBar.total)" />
<div class="row"> <div class="row">
{{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}% {{ loadingBar.message }} {{ Math.floor((100 * loadingBar.current) / loadingBar.total) }}%
{{ loadingBar.message }}
</div> </div>
</div> </div>
</Card> </Card>
</transition> </transition>
<transition name="download"> <transition name="download">
<Card v-if="showProfiles === true && currentProcesses.length > 0" ref="profiles" class="profile-card"> <Card
<Button v-for="process in currentProcesses" :key="process.uuid" class="profile-button" v-if="showProfiles === true && currentProcesses.length > 0"
@click="selectProcess(process)"> ref="profiles"
class="profile-card"
>
<Button
v-for="process in currentProcesses"
:key="process.uuid"
class="profile-button"
@click="selectProcess(process)"
>
<div class="text"><span class="circle running" /> {{ process.profile.name }}</div> <div class="text"><span class="circle running" /> {{ process.profile.name }}</div>
<Button v-tooltip="'Stop instance'" icon-only class="icon-button stop" @click.stop="stop(process)"> <Button
v-tooltip="'Stop instance'"
icon-only
class="icon-button stop"
@click.stop="stop(process)"
>
<StopCircleIcon /> <StopCircleIcon />
</Button> </Button>
<Button v-tooltip="'View logs'" icon-only class="icon-button" @click.stop="goToTerminal(process.profile.path)"> <Button
v-tooltip="'View logs'"
icon-only
class="icon-button"
@click.stop="goToTerminal(process.profile.path)"
>
<TerminalSquareIcon /> <TerminalSquareIcon />
</Button> </Button>
</Button> </Button>
@@ -69,21 +97,23 @@
<script setup> <script setup>
import { import {
DownloadIcon, DownloadIcon,
DropdownIcon,
StopCircleIcon, StopCircleIcon,
TerminalSquareIcon, TerminalSquareIcon,
DropdownIcon,
UnplugIcon, UnplugIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button, ButtonStyled, Card } from '@modrinth/ui' import { Button, ButtonStyled, Card, injectNotificationManager } from '@modrinth/ui'
import { onBeforeUnmount, onMounted, ref } from 'vue' import { onBeforeUnmount, onMounted, ref } from 'vue'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
import { loading_listener, process_listener } from '@/helpers/events'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { progress_bars_list } from '@/helpers/state.js'
import ProgressBar from '@/components/ui/ProgressBar.vue' import ProgressBar from '@/components/ui/ProgressBar.vue'
import { handleError } from '@/store/notifications.js'
import { get_many } from '@/helpers/profile.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { loading_listener, process_listener } from '@/helpers/events'
import { get_all as getRunningProcesses, kill as killProcess } from '@/helpers/process'
import { get_many } from '@/helpers/profile.js'
import { progress_bars_list } from '@/helpers/state.js'
const { handleError } = injectNotificationManager()
const router = useRouter() const router = useRouter()
const card = ref(null) const card = ref(null)
@@ -252,7 +282,6 @@ onBeforeUnmount(() => {
transition: transform 0.2s ease-in-out; transition: transform 0.2s ease-in-out;
display: flex; display: flex;
align-items: center; align-items: center;
&.rotate { &.rotate {
transform: rotate(180deg); transform: rotate(180deg);
} }
@@ -274,10 +303,8 @@ onBeforeUnmount(() => {
gap: var(--gap-xs); gap: var(--gap-xs);
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
-webkit-user-select: none; -webkit-user-select: none; /* Safari */
/* Safari */ -ms-user-select: none; /* IE 10 and IE 11 */
-ms-user-select: none;
/* IE 10 and IE 11 */
user-select: none; user-select: none;
&.clickable:hover { &.clickable:hover {

View File

@@ -117,16 +117,19 @@
</template> </template>
<script setup> <script setup>
import { TagsIcon, DownloadIcon, HeartIcon, PlusIcon, CheckIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, HeartIcon, PlusIcon, TagsIcon } from '@modrinth/assets'
import { ButtonStyled, Avatar } from '@modrinth/ui' import { Avatar, ButtonStyled, injectNotificationManager } from '@modrinth/ui'
import { formatNumber, formatCategory } from '@modrinth/utils' import { formatCategory, formatNumber } from '@modrinth/utils'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { ref, computed } from 'vue' import { computed, ref } from 'vue'
import { install as installVersion } from '@/store/install.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { install as installVersion } from '@/store/install.js'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const router = useRouter() const router = useRouter()
const props = defineProps({ const props = defineProps({
@@ -174,7 +177,7 @@ async function install() {
(profile) => { (profile) => {
router.push(`/instance/${profile}`) router.push(`/instance/${profile}`)
}, },
) ).catch(handleError)
} }
const modpack = computed(() => props.project.project_type === 'modpack') const modpack = computed(() => props.project.project_type === 'modpack')

View File

@@ -82,11 +82,12 @@
</template> </template>
<script setup> <script setup>
import { MaximizeIcon, MinimizeIcon, XIcon } from '@modrinth/assets'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import ProgressBar from '@/components/ui/ProgressBar.vue' import ProgressBar from '@/components/ui/ProgressBar.vue'
import { loading_listener } from '@/helpers/events.js' import { loading_listener } from '@/helpers/events.js'
import { getCurrentWindow } from '@tauri-apps/api/window'
import { XIcon, MaximizeIcon, MinimizeIcon } from '@modrinth/assets'
import { getOS } from '@/helpers/utils.js' import { getOS } from '@/helpers/utils.js'
import { useLoading } from '@/store/loading.js' import { useLoading } from '@/store/loading.js'

View File

@@ -1,12 +1,14 @@
<script setup> <script setup>
import { Button } from '@modrinth/ui' import { Button, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue' 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' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get_project, get_version } from '@/helpers/cache.js'
import { get_categories } from '@/helpers/tags.js'
import { install as installVersion } from '@/store/install.js'
const { handleError } = injectNotificationManager()
const confirmModal = ref(null) const confirmModal = ref(null)
const project = ref(null) const project = ref(null)
@@ -37,7 +39,14 @@ defineExpose({
async function install() { async function install() {
confirmModal.value.hide() confirmModal.value.hide()
await installVersion(project.value.id, version.value.id, null, 'URLConfirmModal') await installVersion(
project.value.id,
version.value.id,
null,
'URLConfirmModal',
() => {},
() => {},
).catch(handleError)
} }
</script> </script>

View File

@@ -1,23 +1,30 @@
<script setup lang="ts"> <script setup lang="ts">
import { Avatar, ButtonStyled, OverflowMenu, useRelativeTime } from '@modrinth/ui'
import { import {
UserPlusIcon,
MoreVerticalIcon,
MailIcon, MailIcon,
MoreVerticalIcon,
SettingsIcon, SettingsIcon,
TrashIcon, TrashIcon,
UserPlusIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { ref, onUnmounted, watch, computed } from 'vue' import {
import { friend_listener } from '@/helpers/events' Avatar,
import { friends, friend_statuses, add_friend, remove_friend } from '@/helpers/friends' ButtonStyled,
import { get_user_many } from '@/helpers/cache' injectNotificationManager,
import { handleError } from '@/store/notifications.js' OverflowMenu,
import ContextMenu from '@/components/ui/ContextMenu.vue' useRelativeTime,
} from '@modrinth/ui'
import type { Dayjs } from 'dayjs' import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import { computed, onUnmounted, ref, watch } from 'vue'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_user_many } from '@/helpers/cache'
import { friend_listener } from '@/helpers/events'
import { add_friend, friend_statuses, friends, remove_friend } from '@/helpers/friends'
const { handleError } = injectNotificationManager()
const formatRelativeTime = useRelativeTime() const formatRelativeTime = useRelativeTime()
const props = defineProps<{ const props = defineProps<{

View File

@@ -56,16 +56,18 @@
</template> </template>
<script setup> <script setup>
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import { DownloadIcon, XIcon } from '@modrinth/assets'
import { XIcon, DownloadIcon } from '@modrinth/assets' import { Button, injectNotificationManager } from '@modrinth/ui'
import { Button } from '@modrinth/ui'
import { formatCategory } from '@modrinth/utils' import { formatCategory } from '@modrinth/utils'
import { add_project_from_version as installMod } from '@/helpers/profile'
import { ref } from 'vue' import { ref } from 'vue'
import { handleError } from '@/store/state.js'
import { trackEvent } from '@/helpers/analytics'
import Multiselect from 'vue-multiselect' import Multiselect from 'vue-multiselect'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { add_project_from_version as installMod } from '@/helpers/profile'
const { handleError } = injectNotificationManager()
const instance = ref(null) const instance = ref(null)
const project = ref(null) const project = ref(null)
const versions = ref(null) const versions = ref(null)

View File

@@ -1,11 +1,13 @@
<script setup> <script setup>
import { DownloadIcon, XIcon } from '@modrinth/assets' import { DownloadIcon, XIcon } from '@modrinth/assets'
import { Button } from '@modrinth/ui' import { Button, injectNotificationManager } from '@modrinth/ui'
import { create_profile_and_install as pack_install } from '@/helpers/pack'
import { ref } from 'vue' import { ref } from 'vue'
import { trackEvent } from '@/helpers/analytics'
import { handleError } from '@/store/state.js'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { create_profile_and_install as pack_install } from '@/helpers/pack'
const { handleError } = injectNotificationManager()
const versionId = ref() const versionId = ref()
const project = ref() const project = ref()

View File

@@ -1,29 +1,30 @@
<script setup> <script setup>
import { import {
CheckIcon,
DownloadIcon, DownloadIcon,
PlusIcon, PlusIcon,
RightArrowIcon,
UploadIcon, UploadIcon,
XIcon, XIcon,
RightArrowIcon,
CheckIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, Button, Card } from '@modrinth/ui' import { Avatar, Button, Card, injectNotificationManager } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core'
import { open } from '@tauri-apps/plugin-dialog'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import { useRouter } from 'vue-router'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { import {
add_project_from_version as installMod, add_project_from_version as installMod,
check_installed, check_installed,
create,
get, get,
list, list,
create,
} from '@/helpers/profile' } from '@/helpers/profile'
import { open } from '@tauri-apps/plugin-dialog'
import { installVersionDependencies } from '@/store/install.js' import { installVersionDependencies } from '@/store/install.js'
import { handleError } from '@/store/notifications.js'
import { 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 { handleError } = injectNotificationManager()
const router = useRouter() const router = useRouter()
const versions = ref() const versions = ref()
@@ -109,7 +110,7 @@ async function install(instance) {
} }
await installMod(instance.path, version.id).catch(handleError) await installMod(instance.path, version.id).catch(handleError)
await installVersionDependencies(instance, version) await installVersionDependencies(instance, version).catch(handleError)
instance.installedMod = true instance.installedMod = true
instance.installing = false instance.installing = false
@@ -184,7 +185,7 @@ const createInstance = async () => {
await router.push(`/instance/${encodeURIComponent(id)}/`) await router.push(`/instance/${encodeURIComponent(id)}/`)
const instance = await get(id, true) const instance = await get(id, true)
await installVersionDependencies(instance, versions.value[0]) await installVersionDependencies(instance, versions.value[0]).catch(handleError)
trackEvent('InstanceCreate', { trackEvent('InstanceCreate', {
profile_name: name.value, profile_name: name.value,

View File

@@ -1,17 +1,25 @@
<script setup lang="ts"> <script setup lang="ts">
import { CopyIcon, EditIcon, PlusIcon, SpinnerIcon, TrashIcon, UploadIcon } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
Checkbox,
injectNotificationManager,
OverflowMenu,
} from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import { SpinnerIcon, TrashIcon, UploadIcon, PlusIcon, EditIcon, CopyIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, OverflowMenu, Checkbox } from '@modrinth/ui'
import { computed, ref, type Ref, watch } from 'vue'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import { trackEvent } from '@/helpers/analytics'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, type Ref, ref, watch } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import type { InstanceSettingsTabProps, GameInstance } from '../../../helpers/types'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { trackEvent } from '@/helpers/analytics'
import { duplicate, edit, edit_icon, list, remove } from '@/helpers/profile'
import type { GameInstance, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const router = useRouter() const router = useRouter()

View File

@@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { Checkbox } from '@modrinth/ui' import { Checkbox, injectNotificationManager } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import { get } from '@/helpers/settings.ts' import { computed, ref, watch } from 'vue'
import { edit } from '@/helpers/profile'
import type { InstanceSettingsTabProps, AppSettings, Hooks } from '../../../helpers/types'
import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppSettings, Hooks, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>() const props = defineProps<InstanceSettingsTabProps>()

View File

@@ -1,23 +1,23 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
TransferIcon,
IssuesIcon,
HammerIcon,
DownloadIcon, DownloadIcon,
WrenchIcon, HammerIcon,
UndoIcon, IssuesIcon,
SpinnerIcon, SpinnerIcon,
UnplugIcon, TransferIcon,
UndoIcon,
UnlinkIcon, UnlinkIcon,
UnplugIcon,
WrenchIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, Checkbox, Chips, ButtonStyled, TeleportDropdownMenu } from '@modrinth/ui' import {
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue' Avatar,
import { edit, install, update_repair_modrinth } from '@/helpers/profile' ButtonStyled,
import { handleError } from '@/store/notifications' Checkbox,
import { trackEvent } from '@/helpers/analytics' Chips,
import { defineMessages, useVIntl } from '@vintl/vintl' injectNotificationManager,
import { get_loader_versions } from '@/helpers/metadata' TeleportDropdownMenu,
import { get_game_versions, get_loaders } from '@/helpers/tags' } from '@modrinth/ui'
import { import {
formatCategory, formatCategory,
type GameVersionTag, type GameVersionTag,
@@ -25,15 +25,23 @@ import {
type Project, type Project,
type Version, type Version,
} from '@modrinth/utils' } from '@modrinth/utils'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_project, get_version_many } from '@/helpers/cache' import { defineMessages, useVIntl } from '@vintl/vintl'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { computed, type ComputedRef, type Ref, ref, shallowRef, watch } from 'vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project, get_version_many } from '@/helpers/cache'
import { get_loader_versions } from '@/helpers/metadata'
import { edit, install, update_repair_modrinth } from '@/helpers/profile'
import { get_game_versions, get_loaders } from '@/helpers/tags'
import type { import type {
InstanceSettingsTabProps, InstanceSettingsTabProps,
ManifestLoaderVersion,
Manifest, Manifest,
ManifestLoaderVersion,
} from '../../../helpers/types' } from '../../../helpers/types'
import { initAuthlibPatching } from '@/helpers/utils.js' import { initAuthlibPatching } from '@/helpers/utils.js'
@@ -41,6 +49,7 @@ const authLibPatchingModal = ref(null)
const isAuthLibPatchedSuccess = ref(false) const isAuthLibPatchedSuccess = ref(false)
const isAuthLibPatching = ref(false) const isAuthLibPatching = ref(false)
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const repairConfirmModal = ref() const repairConfirmModal = ref()
@@ -545,7 +554,8 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
</div> </div>
<div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2"> <div v-else-if="!modpackProject && instance.linked_data && !fetching" class="mb-2">
<p class="text-brand-red font-medium mt-0"> <p class="text-brand-red font-medium mt-0">
<IssuesIcon class="top-[3px] relative" /> {{ formatMessage(messages.noModpackFound) }} <IssuesIcon class="top-[3px] relative" />
{{ formatMessage(messages.noModpackFound) }}
</p> </p>
<p>{{ formatMessage(messages.debugInformation) }}</p> <p>{{ formatMessage(messages.debugInformation) }}</p>
<div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary"> <div class="bg-bg p-6 rounded-2xl mt-2 text-sm text-secondary">
@@ -572,7 +582,9 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
{{ {{
modpackProject modpackProject
? modpackProject.title ? modpackProject.title
: formatMessage(messages.minecraftVersion, { version: instance.game_version }) : formatMessage(messages.minecraftVersion, {
version: instance.game_version,
})
}} }}
</span> </span>
<span class="text-sm text-secondary leading-none"> <span class="text-sm text-secondary leading-none">
@@ -702,7 +714,12 @@ async function handleInitAuthLibPatching(ismojang: boolean) {
/> />
<div v-else class="mt-2 text-brand-red flex gap-2 items-center"> <div v-else class="mt-2 text-brand-red flex gap-2 items-center">
<IssuesIcon /> <IssuesIcon />
{{ formatMessage(messages.noLoaderVersions, { loader: loader, version: gameVersion }) }} {{
formatMessage(messages.noLoaderVersions, {
loader: loader,
version: gameVersion,
})
}}
</div> </div>
</template> </template>
<div class="mt-4 flex flex-wrap gap-2"> <div class="mt-4 flex flex-wrap gap-2">

View File

@@ -1,20 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { Checkbox, Slider } from '@modrinth/ui'
import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets' import { CheckCircleIcon, XCircleIcon } from '@modrinth/assets'
import { computed, readonly, ref, watch } from 'vue' import { Checkbox, injectNotificationManager, Slider } from '@modrinth/ui'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import JavaSelector from '@/components/ui/JavaSelector.vue' import { computed, readonly, ref, watch } from 'vue'
import { get } from '@/helpers/settings.ts'
import type { InstanceSettingsTabProps, AppSettings, MemorySettings } from '../../../helpers/types'
import useMemorySlider from '@/composables/useMemorySlider'
import JavaSelector from '@/components/ui/JavaSelector.vue'
import useMemorySlider from '@/composables/useMemorySlider'
import { edit, get_optimal_jre_key } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppSettings, InstanceSettingsTabProps, MemorySettings } from '../../../helpers/types'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>() const props = defineProps<InstanceSettingsTabProps>()
const globalSettings = (await get().catch(handleError)) as AppSettings const globalSettings = (await get().catch(handleError)) as unknown as AppSettings
const overrideJavaInstall = ref(!!props.instance.java_path) const overrideJavaInstall = ref(!!props.instance.java_path)
const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError)) const optimalJava = readonly(await get_optimal_jre_key(props.instance.path).catch(handleError))
@@ -34,7 +36,10 @@ const envVars = ref(
const overrideMemorySettings = ref(!!props.instance.memory) const overrideMemorySettings = ref(!!props.instance.memory)
const memory = ref(props.instance.memory ?? globalSettings.memory) const memory = ref(props.instance.memory ?? globalSettings.memory)
const { maxMemory, snapPoints } = await useMemorySlider() const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
}
const editProfileObject = computed(() => { const editProfileObject = computed(() => {
const editProfile: { const editProfile: {

View File

@@ -1,12 +1,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { Checkbox, Toggle } from '@modrinth/ui' import { Checkbox, injectNotificationManager, Toggle } from '@modrinth/ui'
import { computed, ref, type Ref, watch } from 'vue'
import { handleError } from '@/store/notifications'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import { get } from '@/helpers/settings.ts' import { computed, type Ref, ref, watch } from 'vue'
import { edit } from '@/helpers/profile' import { edit } from '@/helpers/profile'
import { get } from '@/helpers/settings.ts'
import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types' import type { AppSettings, InstanceSettingsTabProps } from '../../../helpers/types'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const props = defineProps<InstanceSettingsTabProps>() const props = defineProps<InstanceSettingsTabProps>()

View File

@@ -1,29 +1,29 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
ReportIcon,
AstralRinthLogo,
ShieldIcon,
SettingsIcon,
GaugeIcon,
PaintbrushIcon,
GameIcon,
CoffeeIcon, CoffeeIcon,
GameIcon,
GaugeIcon,
AstralRinthLogo,
DownloadIcon, DownloadIcon,
SpinnerIcon, SpinnerIcon,
PaintbrushIcon,
ReportIcon,
SettingsIcon,
ShieldIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { TabbedModal } from '@modrinth/ui' import { TabbedModal } from '@modrinth/ui'
import { computed, ref, watch } from 'vue'
import { useVIntl, defineMessage } from '@vintl/vintl'
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import { version as getOsVersion, platform as getOsPlatform } from '@tauri-apps/plugin-os' import { platform as getOsPlatform, version as getOsVersion } from '@tauri-apps/plugin-os'
import { useTheming } from '@/store/state' import { defineMessage, useVIntl } from '@vintl/vintl'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue' import { computed, ref, watch } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import AppearanceSettings from '@/components/ui/settings/AppearanceSettings.vue'
import DefaultInstanceSettings from '@/components/ui/settings/DefaultInstanceSettings.vue'
import FeatureFlagSettings from '@/components/ui/settings/FeatureFlagSettings.vue'
import JavaSettings from '@/components/ui/settings/JavaSettings.vue'
import PrivacySettings from '@/components/ui/settings/PrivacySettings.vue'
import ResourceManagementSettings from '@/components/ui/settings/ResourceManagementSettings.vue'
import { get, set } from '@/helpers/settings.ts' import { get, set } from '@/helpers/settings.ts'
// [AR] Imports // [AR] Imports
import { installState, getRemote, updateState } from '@/helpers/update.js' import { installState, getRemote, updateState } from '@/helpers/update.js'
@@ -42,6 +42,7 @@ const initDownload = async () => {
updateRequestFailView.value.show() updateRequestFailView.value.show()
} }
} }
import { useTheming } from '@/store/state'
const themeStore = useTheming() const themeStore = useTheming()
@@ -159,12 +160,16 @@ function devModeCount() {
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<button <button
class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation" class="p-0 m-0 bg-transparent border-none cursor-pointer button-animation"
:class="{ 'text-brand': themeStore.devMode, 'text-secondary': !themeStore.devMode }" :class="{
@click="devModeCount"> 'text-brand': themeStore.devMode,
'text-secondary': !themeStore.devMode,
}"
@click="devModeCount"
>
<AstralRinthLogo class="w-6 h-6" /> <AstralRinthLogo class="w-6 h-6" />
</button> </button>
<div> <div>
<p class="m-0">AstralRinth App {{ version }}</p> <p class="m-0">Modrinth App {{ version }}</p>
<p class="m-0"> <p class="m-0">
<span v-if="osPlatform === 'macos'">MacOS</span> <span v-if="osPlatform === 'macos'">MacOS</span>
<span v-else class="capitalize">{{ osPlatform }}</span> <span v-else class="capitalize">{{ osPlatform }}</span>

View File

@@ -1,6 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { LogInIcon, SpinnerIcon } from '@modrinth/assets' import { LogInIcon, SpinnerIcon } from '@modrinth/assets'
import { ref } from 'vue' import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
defineProps({ defineProps({

View File

@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { ConfirmModal } from '@modrinth/ui' import { ConfirmModal } from '@modrinth/ui'
import { ref } from 'vue'
import { useTheming } from '@/store/theme.js' // import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.ts'
const themeStore = useTheming() const themeStore = useTheming()
@@ -52,10 +53,11 @@ const modal = ref(null)
defineExpose({ defineExpose({
show: () => { show: () => {
// hide_ads_window()
modal.value.show() modal.value.show()
}, },
hide: () => { hide: () => {
// onModalHide() onModalHide()
modal.value.hide() modal.value.hide()
}, },
}) })

View File

@@ -2,6 +2,7 @@
import { ChevronRightIcon } from '@modrinth/assets' import { ChevronRightIcon } from '@modrinth/assets'
import { Avatar } from '@modrinth/ui' import { Avatar } from '@modrinth/ui'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import type { GameInstance } from '@/helpers/types' import type { GameInstance } from '@/helpers/types'
defineProps<{ defineProps<{

View File

@@ -1,22 +1,24 @@
<script setup lang="ts"> <script setup lang="ts">
import { import {
ChevronRightIcon, ChevronRightIcon,
CodeIcon,
CoffeeIcon, CoffeeIcon,
InfoIcon, InfoIcon,
WrenchIcon,
MonitorIcon, MonitorIcon,
CodeIcon, WrenchIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui' import { Avatar, TabbedModal, type TabbedModalTab } from '@modrinth/ui'
import { ref } from 'vue'
import { defineMessage, useVIntl } from '@vintl/vintl'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { ref } from 'vue'
import GeneralSettings from '@/components/ui/instance_settings/GeneralSettings.vue'
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue'
import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue' import InstallationSettings from '@/components/ui/instance_settings/InstallationSettings.vue'
import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue' import JavaSettings from '@/components/ui/instance_settings/JavaSettings.vue'
import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue' import WindowSettings from '@/components/ui/instance_settings/WindowSettings.vue'
import HooksSettings from '@/components/ui/instance_settings/HooksSettings.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import type { InstanceSettingsTabProps } from '../../../helpers/types' import type { InstanceSettingsTabProps } from '../../../helpers/types'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()

View File

@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTemplateRef } from 'vue'
import { NewModal as Modal } from '@modrinth/ui' import { NewModal as Modal } from '@modrinth/ui'
// import { show_ads_window, hide_ads_window } from '@/helpers/ads.js' import { useTemplateRef } from 'vue'
import { useTheming } from '@/store/theme.js'
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.ts'
const themeStore = useTheming() const themeStore = useTheming()
@@ -18,7 +19,7 @@ const props = defineProps({
onHide: { onHide: {
type: Function, type: Function,
default() { default() {
return () => { } return () => {}
}, },
}, },
// showAdOnClose: { // showAdOnClose: {
@@ -40,9 +41,9 @@ defineExpose({
}) })
function onModalHide() { function onModalHide() {
// if (props.showAdOnClose) { // if (props.showAdOnClose) {
// show_ads_window() // show_ads_window()
// } // }
props.onHide?.() props.onHide?.()
} }
</script> </script>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'
import { ShareModal } from '@modrinth/ui' import { ShareModal } from '@modrinth/ui'
import { ref } from 'vue'
import { useTheming } from '@/store/theme.js' // import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { useTheming } from '@/store/theme.ts'
const themeStore = useTheming() const themeStore = useTheming()
@@ -33,6 +34,7 @@ const modal = ref(null)
defineExpose({ defineExpose({
show: (passedContent) => { show: (passedContent) => {
// hide_ads_window()
modal.value.show(passedContent) modal.value.show(passedContent)
}, },
hide: () => { hide: () => {
@@ -40,9 +42,21 @@ defineExpose({
modal.value.hide() modal.value.hide()
}, },
}) })
// function onModalHide() {
// show_ads_window()
// }
</script> </script>
<template> <template>
<ShareModal ref="modal" :header="header" :share-title="shareTitle" :share-text="shareText" :link="link" <ShareModal
:open-in-new-tab="openInNewTab" :on-hide="onModalHide" :noblur="!themeStore.advancedRendering" /> ref="modal"
:header="header"
:share-title="shareTitle"
:share-text="shareText"
:link="link"
:open-in-new-tab="openInNewTab"
:on-hide="onModalHide"
:noblur="!themeStore.advancedRendering"
/>
</template> </template>

View File

@@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { TeleportDropdownMenu, ThemeSelector, Toggle } from '@modrinth/ui' import { TeleportDropdownMenu, ThemeSelector, Toggle } from '@modrinth/ui'
import { useTheming } from '@/store/state'
import { get, set } from '@/helpers/settings.ts'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.ts'
import { getOS } from '@/helpers/utils' import { getOS } from '@/helpers/utils'
import { useTheming } from '@/store/state'
import type { ColorTheme } from '@/store/theme.ts' import type { ColorTheme } from '@/store/theme.ts'
const themeStore = useTheming() const themeStore = useTheming()

View File

@@ -1,8 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { get, set } from '@/helpers/settings.ts' import { injectNotificationManager, Slider, Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { Slider, Toggle } from '@modrinth/ui'
import useMemorySlider from '@/composables/useMemorySlider' import useMemorySlider from '@/composables/useMemorySlider'
import { get, set } from '@/helpers/settings.ts'
const { handleError } = injectNotificationManager()
const fetchSettings = await get() const fetchSettings = await get()
fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ') fetchSettings.launchArgs = fetchSettings.extra_launch_args.join(' ')
@@ -10,7 +13,10 @@ fetchSettings.envVars = fetchSettings.custom_env_vars.map((x) => x.join('=')).jo
const settings = ref(fetchSettings) const settings = ref(fetchSettings)
const { maxMemory, snapPoints } = await useMemorySlider() const { maxMemory, snapPoints } = (await useMemorySlider().catch(handleError)) as unknown as {
maxMemory: number
snapPoints: number[]
}
watch( watch(
settings, settings,

View File

@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { Toggle } from '@modrinth/ui' import { Toggle } from '@modrinth/ui'
import { useTheming } from '@/store/state'
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import { get as getSettings, set as setSettings } from '@/helpers/settings.ts' import { get as getSettings, set as setSettings } from '@/helpers/settings.ts'
import { useTheming } from '@/store/state'
import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts' import { DEFAULT_FEATURE_FLAGS, type FeatureFlag } from '@/store/theme.ts'
const themeStore = useTheming() const themeStore = useTheming()

View File

@@ -1,8 +1,11 @@
<script setup> <script setup>
import { injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import { get_java_versions, set_java_version } from '@/helpers/jre'
import { handleError } from '@/store/notifications'
import JavaSelector from '@/components/ui/JavaSelector.vue' import JavaSelector from '@/components/ui/JavaSelector.vue'
import { get_java_versions, set_java_version } from '@/helpers/jre'
const { handleError } = injectNotificationManager()
const javaVersions = ref(await get_java_versions().catch(handleError)) const javaVersions = ref(await get_java_versions().catch(handleError))
async function updateJavaVersion(version) { async function updateJavaVersion(version) {

View File

@@ -1,8 +1,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.ts'
import { Toggle } from '@modrinth/ui' import { Toggle } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics' import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
import { get, set } from '@/helpers/settings.ts'
const settings = ref(await get()) const settings = ref(await get())
@@ -26,7 +27,7 @@ watch(
<div> <div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2> <h2 class="m-0 text-lg font-extrabold text-contrast">Personalized ads</h2>
<p class="m-0 text-sm"> <p class="m-0 text-sm">
(Hard disabled by AR) Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this Modrinth's ad provider, Aditude, shows ads based on your preferences. By disabling this
option, you opt out and ads will no longer be shown based on your interests. option, you opt out and ads will no longer be shown based on your interests.
</p> </p>
</div> </div>
@@ -38,7 +39,7 @@ watch(
<div> <div>
<h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2> <h2 class="m-0 text-lg font-extrabold text-contrast">Telemetry</h2>
<p class="m-0 text-sm"> <p class="m-0 text-sm">
(Hard disabled by AR) • Modrinth collects anonymized analytics and usage data to improve our user experience and Modrinth collects anonymized analytics and usage data to improve our user experience and
customize your experience. By disabling this option, you opt out and your data will no customize your experience. By disabling this option, you opt out and your data will no
longer be collected. longer be collected.
</p> </p>

View File

@@ -1,13 +1,14 @@
<script setup> <script setup>
import { Button, Slider } from '@modrinth/ui'
import { ref, watch } from 'vue'
import { get, set } from '@/helpers/settings.ts'
import { purge_cache_types } from '@/helpers/cache.js'
import { handleError } from '@/store/notifications.js'
import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets' import { BoxIcon, FolderSearchIcon, TrashIcon } from '@modrinth/assets'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue' import { Button, injectNotificationManager, Slider } from '@modrinth/ui'
import { open } from '@tauri-apps/plugin-dialog' import { open } from '@tauri-apps/plugin-dialog'
import { ref, watch } from 'vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { purge_cache_types } from '@/helpers/cache.js'
import { get, set } from '@/helpers/settings.ts'
const { handleError } = injectNotificationManager()
const settings = ref(await get()) const settings = ref(await get())
watch( watch(

View File

@@ -100,37 +100,40 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, watch, useTemplateRef } from 'vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
import { import {
SkinPreviewRenderer, CheckIcon,
ChevronRightIcon,
SaveIcon,
SpinnerIcon,
UploadIcon,
XIcon,
} from '@modrinth/assets'
import {
Button, Button,
RadioButtons, ButtonStyled,
CapeButton, CapeButton,
CapeLikeTextButton, CapeLikeTextButton,
ButtonStyled, injectNotificationManager,
RadioButtons,
SkinPreviewRenderer,
} from '@modrinth/ui' } from '@modrinth/ui'
import { computed, ref, useTemplateRef, watch } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
import { import {
add_and_equip_custom_skin, add_and_equip_custom_skin,
remove_custom_skin,
unequip_skin,
type Skin,
type Cape, type Cape,
type SkinModel,
get_normalized_skin_texture,
determineModelType, determineModelType,
get_normalized_skin_texture,
remove_custom_skin,
type Skin,
type SkinModel,
unequip_skin,
} from '@/helpers/skins.ts' } from '@/helpers/skins.ts'
import { handleError } from '@/store/notifications'
import { const { handleError } = injectNotificationManager()
UploadIcon,
CheckIcon,
SaveIcon,
XIcon,
ChevronRightIcon,
SpinnerIcon,
} from '@modrinth/assets'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
const modal = useTemplateRef('modal') const modal = useTemplateRef('modal')
const selectCapeModal = useTemplateRef('selectCapeModal') const selectCapeModal = useTemplateRef('selectCapeModal')

View File

@@ -1,15 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useTemplateRef, ref, computed } from 'vue' import { CheckIcon, XIcon } from '@modrinth/assets'
import type { Cape, SkinModel } from '@/helpers/skins.ts'
import { import {
ButtonStyled, ButtonStyled,
ScrollablePanel,
CapeButton, CapeButton,
CapeLikeTextButton, CapeLikeTextButton,
ScrollablePanel,
SkinPreviewRenderer, SkinPreviewRenderer,
} from '@modrinth/ui' } from '@modrinth/ui'
import { CheckIcon, XIcon } from '@modrinth/assets' import { computed, ref, useTemplateRef } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import type { Cape, SkinModel } from '@/helpers/skins.ts'
const modal = useTemplateRef('modal') const modal = useTemplateRef('modal')

View File

@@ -27,14 +27,15 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, onBeforeUnmount, watch } from 'vue'
import { UploadIcon } from '@modrinth/assets' import { UploadIcon } from '@modrinth/assets'
import { useNotifications } from '@/store/state' import { injectNotificationManager } from '@modrinth/ui'
import { getCurrentWebview } from '@tauri-apps/api/webview' import { getCurrentWebview } from '@tauri-apps/api/webview'
import { onBeforeUnmount, ref, watch } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import { get_dragged_skin_data } from '@/helpers/skins' import { get_dragged_skin_data } from '@/helpers/skins'
const notifications = useNotifications() const { addNotification } = injectNotificationManager()
const modal = ref() const modal = ref()
const fileInput = ref<HTMLInputElement>() const fileInput = ref<HTMLInputElement>()
@@ -99,7 +100,7 @@ async function setupDragDropListener() {
const data = await get_dragged_skin_data(filePath) const data = await get_dragged_skin_data(filePath)
await processData(data.buffer) await processData(data.buffer)
} catch (error) { } catch (error) {
notifications.addNotification({ addNotification({
title: 'Error processing file', title: 'Error processing file',
text: error instanceof Error ? error.message : 'Failed to read the dropped file.', text: error instanceof Error ? error.message : 'Failed to read the dropped file.',
type: 'error', type: 'error',

View File

@@ -1,6 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs'
import { import {
EyeIcon, EyeIcon,
FolderOpenIcon, FolderOpenIcon,
@@ -13,25 +11,29 @@ import {
Avatar, Avatar,
ButtonStyled, ButtonStyled,
commonMessages, commonMessages,
injectNotificationManager,
OverflowMenu, OverflowMenu,
SmartClickable, SmartClickable,
useRelativeTime, useRelativeTime,
} from '@modrinth/ui' } from '@modrinth/ui'
import { useVIntl } from '@vintl/vintl'
import { computed, nextTick, ref, onMounted, onUnmounted } from 'vue'
import { showProfileInFolder } from '@/helpers/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import { useRouter } from 'vue-router'
import type { GameInstance } from '@/helpers/types'
import { get_project } from '@/helpers/cache'
import { capitalizeString } from '@modrinth/utils' import { capitalizeString } from '@modrinth/utils'
import { kill, run } from '@/helpers/profile' import { convertFileSrc } from '@tauri-apps/api/core'
import { handleSevereError } from '@/store/error' import { useVIntl } from '@vintl/vintl'
import { trackEvent } from '@/helpers/analytics' import type { Dayjs } from 'dayjs'
import { get_by_profile_path } from '@/helpers/process' import dayjs from 'dayjs'
import { handleError } from '@/store/notifications' import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue'
import { process_listener } from '@/helpers/events' import { useRouter } from 'vue-router'
import { trackEvent } from '@/helpers/analytics'
import { get_project } from '@/helpers/cache'
import { process_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types'
import { showProfileInFolder } from '@/helpers/utils'
import { handleSevereError } from '@/store/error'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime() const formatRelativeTime = useRelativeTime()

View File

@@ -1,30 +1,32 @@
<script setup lang="ts"> <script setup lang="ts">
import { import { GAME_MODES, HeadingLink, injectNotificationManager } from '@modrinth/ui'
type ProtocolVersion,
type ServerWorld,
type ServerData,
type WorldWithProfile,
get_recent_worlds,
getWorldIdentifier,
get_profile_protocol_version,
refreshServerData,
start_join_server,
start_join_singleplayer_world,
} from '@/helpers/worlds.ts'
import { HeadingLink, GAME_MODES } from '@modrinth/ui'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import InstanceItem from '@/components/ui/world/InstanceItem.vue'
import { watch, onMounted, onUnmounted, ref, computed } from 'vue'
import type { Dayjs } from 'dayjs' import type { Dayjs } from 'dayjs'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import { useTheming } from '@/store/theme.ts' import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
import { kill, run } from '@/helpers/profile'
import { handleError } from '@/store/notifications' import InstanceItem from '@/components/ui/world/InstanceItem.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
import { process_listener, profile_listener } from '@/helpers/events' import { process_listener, profile_listener } from '@/helpers/events'
import { get_all } from '@/helpers/process' import { get_all } from '@/helpers/process'
import { kill, run } from '@/helpers/profile'
import type { GameInstance } from '@/helpers/types' import type { GameInstance } from '@/helpers/types'
import {
get_profile_protocol_version,
get_recent_worlds,
getWorldIdentifier,
type ProtocolVersion,
refreshServerData,
type ServerData,
type ServerWorld,
start_join_server,
start_join_singleplayer_world,
type WorldWithProfile,
} from '@/helpers/worlds.ts'
import { handleSevereError } from '@/store/error' import { handleSevereError } from '@/store/error'
import { useTheming } from '@/store/theme.ts'
const { handleError } = injectNotificationManager()
const props = defineProps<{ const props = defineProps<{
recentInstances: GameInstance[] recentInstances: GameInstance[]

View File

@@ -1,28 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import dayjs from 'dayjs'
import type {
ProtocolVersion,
ServerStatus,
ServerWorld,
SingleplayerWorld,
World,
} from '@/helpers/worlds.ts'
import { set_world_display_status, getWorldIdentifier } from '@/helpers/worlds.ts'
import { formatNumber, getPingLevel } from '@modrinth/utils'
import { import {
useRelativeTime,
Avatar,
ButtonStyled,
commonMessages,
OverflowMenu,
SmartClickable,
} from '@modrinth/ui'
import {
IssuesIcon,
EyeIcon,
ClipboardCopyIcon, ClipboardCopyIcon,
EditIcon, EditIcon,
EyeIcon,
FolderOpenIcon, FolderOpenIcon,
IssuesIcon,
MoreVerticalIcon, MoreVerticalIcon,
NoSignalIcon, NoSignalIcon,
PlayIcon, PlayIcon,
@@ -35,14 +17,33 @@ import {
UserIcon, UserIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import {
Avatar,
ButtonStyled,
commonMessages,
OverflowMenu,
SmartClickable,
useRelativeTime,
} from '@modrinth/ui'
import { formatNumber, getPingLevel } from '@modrinth/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import type { MessageDescriptor } from '@vintl/vintl' import type { MessageDescriptor } from '@vintl/vintl'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import dayjs from 'dayjs'
import { Tooltip } from 'floating-vue'
import type { Component } from 'vue' import type { Component } from 'vue'
import { computed } from 'vue' import { computed } from 'vue'
import { copyToClipboard } from '@/helpers/utils'
import { convertFileSrc } from '@tauri-apps/api/core'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { Tooltip } from 'floating-vue'
import { copyToClipboard } from '@/helpers/utils'
import type {
ProtocolVersion,
ServerStatus,
ServerWorld,
SingleplayerWorld,
World,
} from '@/helpers/worlds.ts'
import { getWorldIdentifier, set_world_display_status } from '@/helpers/worlds.ts'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const formatRelativeTime = useRelativeTime() const formatRelativeTime = useRelativeTime()
@@ -235,7 +236,8 @@ const messages = defineMessages({
/> />
<Tooltip :disabled="!hasPlayersTooltip"> <Tooltip :disabled="!hasPlayersTooltip">
<span :class="{ 'cursor-help': hasPlayersTooltip }"> <span :class="{ 'cursor-help': hasPlayersTooltip }">
{{ formatNumber(serverStatus.players?.online, false) }} online {{ formatNumber(serverStatus.players?.online, false) }}
online
</span> </span>
<template #popper> <template #popper>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
@@ -248,7 +250,8 @@ const messages = defineMessages({
</template> </template>
</template> </template>
<template v-else> <template v-else>
<NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" /> Offline <NoSignalIcon aria-hidden="true" stroke-width="3px" class="shrink-0" />
Offline
</template> </template>
</div> </div>
</div> </div>
@@ -258,7 +261,9 @@ const messages = defineMessages({
world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null world.last_played ? dayjs(world.last_played).format('MMMM D, YYYY [at] h:mm A') : null
" "
class="w-fit shrink-0" class="w-fit shrink-0"
:class="{ 'cursor-help smart-clickable:allow-pointer-events': world.last_played }" :class="{
'cursor-help smart-clickable:allow-pointer-events': world.last_played,
}"
> >
<template v-if="world.last_played"> <template v-if="world.last_played">
{{ {{
@@ -435,17 +440,20 @@ const messages = defineMessages({
{{ formatMessage(messages.viewInstance) }} {{ formatMessage(messages.viewInstance) }}
</template> </template>
<template #edit> <template #edit>
<EditIcon aria-hidden="true" /> {{ formatMessage(commonMessages.editButton) }} <EditIcon aria-hidden="true" />
{{ formatMessage(commonMessages.editButton) }}
</template> </template>
<template #open-folder> <template #open-folder>
<FolderOpenIcon aria-hidden="true" /> <FolderOpenIcon aria-hidden="true" />
{{ formatMessage(commonMessages.openFolderButton) }} {{ formatMessage(commonMessages.openFolderButton) }}
</template> </template>
<template #copy-address> <template #copy-address>
<ClipboardCopyIcon aria-hidden="true" /> {{ formatMessage(messages.copyAddress) }} <ClipboardCopyIcon aria-hidden="true" />
{{ formatMessage(messages.copyAddress) }}
</template> </template>
<template #refresh> <template #refresh>
<UpdatedIcon aria-hidden="true" /> {{ formatMessage(commonMessages.refreshButton) }} <UpdatedIcon aria-hidden="true" />
{{ formatMessage(commonMessages.refreshButton) }}
</template> </template>
<template #dont-show-on-home> <template #dont-show-on-home>
<XIcon aria-hidden="true" /> <XIcon aria-hidden="true" />

View File

@@ -1,15 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets' import { PlayIcon, PlusIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages } from '@modrinth/ui' import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import type { GameInstance } from '@/helpers/types'
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
import { defineMessages, useVIntl } from '@vintl/vintl' import { defineMessages, useVIntl } from '@vintl/vintl'
import { handleError } from '@/store/notifications' import { ref } from 'vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import InstanceModalTitlePrefix from '@/components/ui/modal/InstanceModalTitlePrefix.vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types'
import { add_server_to_profile, type ServerPackStatus, type ServerWorld } from '@/helpers/worlds.ts'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -1,21 +1,22 @@
<script setup lang="ts"> <script setup lang="ts">
import { SaveIcon, XIcon } from '@modrinth/assets' import { SaveIcon, XIcon } from '@modrinth/assets'
import { ButtonStyled, commonMessages } from '@modrinth/ui' import { ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import type { GameInstance } from '@/helpers/types' import type { GameInstance } from '@/helpers/types'
import { import {
type ServerPackStatus, type DisplayStatus,
edit_server_in_profile, edit_server_in_profile,
type ServerPackStatus,
type ServerWorld, type ServerWorld,
set_world_display_status, set_world_display_status,
type DisplayStatus,
} from '@/helpers/worlds.ts' } from '@/helpers/worlds.ts'
import { defineMessage, useVIntl } from '@vintl/vintl'
import { handleError } from '@/store/notifications'
import ServerModalBody from '@/components/ui/world/modal/ServerModalBody.vue'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -1,15 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { ChevronRightIcon, SaveIcon, XIcon, UndoIcon } from '@modrinth/assets' import { ChevronRightIcon, SaveIcon, UndoIcon, XIcon } from '@modrinth/assets'
import { Avatar, ButtonStyled, commonMessages } from '@modrinth/ui' import { Avatar, ButtonStyled, commonMessages, injectNotificationManager } from '@modrinth/ui'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { computed, ref } from 'vue' import { computed, ref } from 'vue'
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue' import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
import type { GameInstance } from '@/helpers/types' import type { GameInstance } from '@/helpers/types'
import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts' import type { DisplayStatus, SingleplayerWorld } from '@/helpers/worlds.ts'
import { set_world_display_status, rename_world, reset_world_icon } from '@/helpers/worlds.ts' import { rename_world, reset_world_icon, set_world_display_status } from '@/helpers/worlds.ts'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { handleError } from '@/store/notifications'
import HideFromHomeOption from '@/components/ui/world/modal/HideFromHomeOption.vue'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const emit = defineEmits<{ const emit = defineEmits<{

View File

@@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { Checkbox } from '@modrinth/ui'
import { defineMessage, useVIntl } from '@vintl/vintl' import { defineMessage, useVIntl } from '@vintl/vintl'
import { computed } from 'vue' import { computed } from 'vue'
import { Checkbox } from '@modrinth/ui'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const value = defineModel<boolean>({ required: true }) const value = defineModel<boolean>({ required: true })

View File

@@ -1,7 +1,8 @@
<script setup lang="ts"> <script setup lang="ts">
import { TeleportDropdownMenu } from '@modrinth/ui' import { TeleportDropdownMenu } from '@modrinth/ui'
import { defineMessages, type MessageDescriptor, useVIntl } from '@vintl/vintl'
import type { ServerPackStatus } from '@/helpers/worlds.ts' import type { ServerPackStatus } from '@/helpers/worlds.ts'
import { type MessageDescriptor, defineMessages, useVIntl } from '@vintl/vintl'
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()

View File

@@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import cssContent from '@/assets/stylesheets/macFix.css?inline' import cssContent from '@/assets/stylesheets/macFix.css?inline'
export async function useCheckDisableMouseover() { export async function useCheckDisableMouseover() {

View File

@@ -1,9 +1,9 @@
import { ref, computed } from 'vue' import { computed, ref } from 'vue'
import { get_max_memory } from '@/helpers/jre.js' import { get_max_memory } from '@/helpers/jre.js'
import { handleError } from '@/store/notifications.js'
export default async function () { export default async function () {
const maxMemory = ref(Math.floor((await get_max_memory().catch(handleError)) / 1024)) const maxMemory = ref(Math.floor((await get_max_memory()) / 1024))
const snapPoints = computed(() => { const snapPoints = computed(() => {
let points = [] let points = []

View File

@@ -1,6 +1,5 @@
import { fetch } from '@tauri-apps/plugin-http'
import { handleError } from '@/store/state.js'
import { getVersion } from '@tauri-apps/api/app' import { getVersion } from '@tauri-apps/api/app'
import { fetch } from '@tauri-apps/plugin-http'
export const useFetch = async (url, item, isSilent) => { export const useFetch = async (url, item, isSilent) => {
try { try {
@@ -11,8 +10,9 @@ export const useFetch = async (url, item, isSilent) => {
}) })
} catch (err) { } catch (err) {
if (!isSilent) { if (!isSilent) {
handleError({ message: `Error fetching ${item}` }) throw err
} } else {
console.error(err) console.error(err)
} }
}
} }

View File

@@ -4,6 +4,7 @@
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { create } from './profile' import { create } from './profile'
/* /*

View File

@@ -28,7 +28,11 @@ export async function get_logs_by_filename(profilePath, logType, filename) {
/// Get a profile's log text only by filename /// Get a profile's log text only by filename
export async function get_output_by_filename(profilePath, logType, filename) { export async function get_output_by_filename(profilePath, logType, filename) {
return await invoke('plugin:logs|logs_get_output_by_filename', { profilePath, logType, filename }) return await invoke('plugin:logs|logs_get_output_by_filename', {
profilePath,
logType,
filename,
})
} }
/// Delete a profile's log by filename /// Delete a profile's log by filename

View File

@@ -4,6 +4,7 @@
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { create } from './profile' import { create } from './profile'
// Installs pack from a version ID // Installs pack from a version ID

View File

@@ -4,8 +4,8 @@
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { install_to_existing_profile } from '@/helpers/pack.js' import { install_to_existing_profile } from '@/helpers/pack.js'
import { handleError } from '@/store/notifications.js'
/// Add instance /// Add instance
/* /*
@@ -128,7 +128,10 @@ export async function remove_project(path, projectPath) {
// Update a managed Modrinth profile to a specific version // Update a managed Modrinth profile to a specific version
export async function update_managed_modrinth_version(path, versionId) { export async function update_managed_modrinth_version(path, versionId) {
return await invoke('plugin:profile|profile_update_managed_modrinth_version', { path, versionId }) return await invoke('plugin:profile|profile_update_managed_modrinth_version', {
path,
versionId,
})
} }
// Repair a managed Modrinth profile // Repair a managed Modrinth profile
@@ -197,8 +200,8 @@ export async function finish_install(instance) {
linkedData.version_id, linkedData.version_id,
instance.name, instance.name,
instance.path, instance.path,
).catch(handleError) )
} else { } else {
await install(instance.path, false).catch(handleError) await install(instance.path, false)
} }
} }

View File

@@ -1,17 +1,18 @@
import * as THREE from 'three' import { ClassicPlayerModel, SlimPlayerModel } from '@modrinth/assets'
import type { Skin, Cape } from '../skins'
import { get_normalized_skin_texture, determineModelType } from '../skins'
import { reactive } from 'vue'
import { import {
setupSkinModel,
disposeCaches,
loadTexture,
applyCapeTexture, applyCapeTexture,
createTransparentTexture, createTransparentTexture,
disposeCaches,
loadTexture,
setupSkinModel,
} from '@modrinth/utils' } from '@modrinth/utils'
import { skinPreviewStorage } from '../storage/skin-preview-storage' import * as THREE from 'three'
import { reactive } from 'vue'
import type { Cape, Skin } from '../skins'
import { determineModelType, get_normalized_skin_texture } from '../skins'
import { headStorage } from '../storage/head-storage' import { headStorage } from '../storage/head-storage'
import { ClassicPlayerModel, SlimPlayerModel } from '@modrinth/assets' import { skinPreviewStorage } from '../storage/skin-preview-storage'
export interface RenderResult { export interface RenderResult {
forwards: string forwards: string

View File

@@ -4,8 +4,9 @@
* and deserialized into a usable JS object. * and deserialized into a usable JS object.
*/ */
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import type { ColorTheme, FeatureFlag } from '@/store/theme.ts'
import type { Hooks, MemorySettings, WindowSize } from '@/helpers/types' import type { Hooks, MemorySettings, WindowSize } from '@/helpers/types'
import type { ColorTheme, FeatureFlag } from '@/store/theme.ts'
// Settings object // Settings object
/* /*

View File

@@ -1,6 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
import { handleError } from '@/store/notifications'
import { arrayBufferToBase64 } from '@modrinth/utils' import { arrayBufferToBase64 } from '@modrinth/utils'
import { invoke } from '@tauri-apps/api/core'
export interface Cape { export interface Cape {
id: string id: string
@@ -39,7 +38,7 @@ export const DEFAULT_MODELS: Record<string, SkinModel> = {
export function filterSavedSkins(list: Skin[]) { export function filterSavedSkins(list: Skin[]) {
const customSkins = list.filter((s) => s.source !== 'default') const customSkins = list.filter((s) => s.source !== 'default')
fixUnknownSkins(customSkins).catch(handleError) fixUnknownSkins(customSkins)
return customSkins return customSkins
} }

View File

@@ -1,6 +1,7 @@
import { get_full_path, get_mod_full_path } from '@/helpers/profile'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import { get_full_path, get_mod_full_path } from '@/helpers/profile'
export async function isDev() { export async function isDev() {
return await invoke('is_dev') return await invoke('is_dev')
} }

View File

@@ -1,9 +1,10 @@
import { autoToHTML } from '@geometrically/minecraft-motd-parser'
import type { GameVersion } from '@modrinth/ui'
import { invoke } from '@tauri-apps/api/core' import { invoke } from '@tauri-apps/api/core'
import dayjs from 'dayjs'
import { get_full_path } from '@/helpers/profile' import { get_full_path } from '@/helpers/profile'
import { openPath } from '@/helpers/utils' import { openPath } from '@/helpers/utils'
import { autoToHTML } from '@geometrically/minecraft-motd-parser'
import dayjs from 'dayjs'
import type { GameVersion } from '@modrinth/ui'
type BaseWorld = { type BaseWorld = {
name: string name: string

View File

@@ -1,12 +1,14 @@
import { createApp } from 'vue'
import router from '@/routes'
import App from '@/App.vue'
import { createPinia } from 'pinia'
import FloatingVue from 'floating-vue'
import 'floating-vue/dist/style.css' import 'floating-vue/dist/style.css'
import { createPlugin } from '@vintl/vintl/plugin'
import * as Sentry from '@sentry/vue' import * as Sentry from '@sentry/vue'
import { VueScanPlugin } from '@taijased/vue-render-tracker' import { VueScanPlugin } from '@taijased/vue-render-tracker'
import { createPlugin } from '@vintl/vintl/plugin'
import FloatingVue from 'floating-vue'
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from '@/App.vue'
import router from '@/routes'
const VIntlPlugin = createPlugin({ const VIntlPlugin = createPlugin({
controllerOpts: { controllerOpts: {

View File

@@ -1,33 +1,35 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, ref, shallowRef, watch } from 'vue' import { ClipboardCopyIcon, ExternalIcon, GlobeIcon, SearchIcon, XIcon } from '@modrinth/assets'
import type { Ref } from 'vue'
import { SearchIcon, XIcon, ClipboardCopyIcon, GlobeIcon, ExternalIcon } from '@modrinth/assets'
import type { Category, GameVersion, Platform, ProjectType, SortType, Tags } from '@modrinth/ui' import type { Category, GameVersion, Platform, ProjectType, SortType, Tags } from '@modrinth/ui'
import { import {
SearchFilterControl,
SearchSidebarFilter,
Button, Button,
Checkbox, Checkbox,
DropdownSelect, DropdownSelect,
injectNotificationManager,
LoadingIndicator, LoadingIndicator,
Pagination, Pagination,
SearchFilterControl,
SearchSidebarFilter,
useSearch, useSearch,
} from '@modrinth/ui' } from '@modrinth/ui'
import { handleError } from '@/store/state' import { openUrl } from '@tauri-apps/plugin-opener'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { defineMessages, useVIntl } from '@vintl/vintl'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags' import type { Ref } from 'vue'
import { computed, nextTick, ref, shallowRef, watch } from 'vue'
import type { LocationQuery } from 'vue-router' import type { LocationQuery } from 'vue-router'
import { useRoute, useRouter } from 'vue-router' import { useRoute, useRouter } from 'vue-router'
import SearchCard from '@/components/ui/SearchCard.vue'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js' import ContextMenu from '@/components/ui/ContextMenu.vue'
import { get_search_results } from '@/helpers/cache.js'
import NavTabs from '@/components/ui/NavTabs.vue'
import type Instance from '@/components/ui/Instance.vue' import type Instance from '@/components/ui/Instance.vue'
import InstanceIndicator from '@/components/ui/InstanceIndicator.vue' import InstanceIndicator from '@/components/ui/InstanceIndicator.vue'
import { defineMessages, useVIntl } from '@vintl/vintl' import NavTabs from '@/components/ui/NavTabs.vue'
import ContextMenu from '@/components/ui/ContextMenu.vue' import SearchCard from '@/components/ui/SearchCard.vue'
import { openUrl } from '@tauri-apps/plugin-opener' import { get_search_results } from '@/helpers/cache.js'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile.js'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const { handleError } = injectNotificationManager()
const { formatMessage } = useVIntl() const { formatMessage } = useVIntl()
const router = useRouter() const router = useRouter()
@@ -160,6 +162,8 @@ const {
createPageParams, createPageParams,
} = useSearch(projectTypes, tags, instanceFilters) } = useSearch(projectTypes, tags, instanceFilters)
const previousFilterState = ref('')
const offline = ref(!navigator.onLine) const offline = ref(!navigator.onLine)
window.addEventListener('offline', () => { window.addEventListener('offline', () => {
offline.value = true offline.value = true
@@ -220,7 +224,20 @@ async function refreshSearch() {
} }
} }
results.value = rawResults.result results.value = rawResults.result
const currentFilterState = JSON.stringify({
query: query.value,
filters: currentFilters.value,
sort: currentSortType.value,
maxResults: maxResults.value,
projectTypes: projectTypes.value,
})
if (previousFilterState.value && previousFilterState.value !== currentFilterState) {
currentPage.value = 1 currentPage.value = 1
}
previousFilterState.value = currentFilterState
const persistentParams: LocationQuery = {} const persistentParams: LocationQuery = {}
@@ -380,6 +397,15 @@ const handleOptionsClick = (args) => {
} }
await refreshSearch() await refreshSearch()
// Initialize previousFilterState after first search
previousFilterState.value = JSON.stringify({
query: query.value,
filters: currentFilters.value,
sort: currentSortType.value,
maxResults: maxResults.value,
projectTypes: projectTypes.value,
})
</script> </script>
<template> <template>

View File

@@ -1,17 +1,19 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref, onUnmounted, computed } from 'vue' import { injectNotificationManager } from '@modrinth/ui'
import { useRoute } from 'vue-router'
import RowDisplay from '@/components/RowDisplay.vue'
import { list } from '@/helpers/profile.js'
import { profile_listener } from '@/helpers/events'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { handleError } from '@/store/notifications.js'
import dayjs from 'dayjs'
import { get_search_results } from '@/helpers/cache.js'
import type { SearchResult } from '@modrinth/utils' import type { SearchResult } from '@modrinth/utils'
import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue' import dayjs from 'dayjs'
import type { GameInstance } from '@/helpers/types' import { computed, onUnmounted, ref } from 'vue'
import { useRoute } from 'vue-router'
import RowDisplay from '@/components/RowDisplay.vue'
import RecentWorldsList from '@/components/ui/world/RecentWorldsList.vue'
import { get_search_results } from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events'
import { list } from '@/helpers/profile.js'
import type { GameInstance } from '@/helpers/types'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()

View File

@@ -12,42 +12,44 @@ import {
Button, Button,
ButtonStyled, ButtonStyled,
ConfirmModal, ConfirmModal,
injectNotificationManager,
SkinButton, SkinButton,
SkinLikeTextButton, SkinLikeTextButton,
SkinPreviewRenderer, SkinPreviewRenderer,
} from '@modrinth/ui' } from '@modrinth/ui'
import { arrayBufferToBase64 } from '@modrinth/utils'
import { computedAsync } from '@vueuse/core' import { computedAsync } from '@vueuse/core'
import type { Ref } from 'vue' import type { Ref } from 'vue'
import { computed, inject, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue' import { computed, inject, onMounted, onUnmounted, ref, useTemplateRef, watch } from 'vue'
import type AccountsCard from '@/components/ui/AccountsCard.vue'
import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue' import EditSkinModal from '@/components/ui/skin/EditSkinModal.vue'
import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue' import SelectCapeModal from '@/components/ui/skin/SelectCapeModal.vue'
import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue' import UploadSkinModal from '@/components/ui/skin/UploadSkinModal.vue'
import { handleError, useNotifications } from '@/store/notifications' import { trackEvent } from '@/helpers/analytics'
import { get_default_user, login as login_flow, users } from '@/helpers/auth'
import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
import { generateSkinPreviews, skinBlobUrlMap } from '@/helpers/rendering/batch-skin-renderer.ts'
import { get as getSettings } from '@/helpers/settings.ts'
import type { Cape, Skin } from '@/helpers/skins.ts' import type { Cape, Skin } from '@/helpers/skins.ts'
import { import {
normalize_skin_texture,
equip_skin, equip_skin,
filterDefaultSkins, filterDefaultSkins,
filterSavedSkins, filterSavedSkins,
get_available_capes, get_available_capes,
get_available_skins, get_available_skins,
get_normalized_skin_texture, get_normalized_skin_texture,
normalize_skin_texture,
remove_custom_skin, remove_custom_skin,
set_default_cape, set_default_cape,
} from '@/helpers/skins.ts' } from '@/helpers/skins.ts'
import { get as getSettings } from '@/helpers/settings.ts'
import { get_default_user, login as login_flow, users } from '@/helpers/auth'
import type { RenderResult } from '@/helpers/rendering/batch-skin-renderer.ts'
import { generateSkinPreviews, skinBlobUrlMap } from '@/helpers/rendering/batch-skin-renderer.ts'
import { handleSevereError } from '@/store/error' import { handleSevereError } from '@/store/error'
import { trackEvent } from '@/helpers/analytics'
import type AccountsCard from '@/components/ui/AccountsCard.vue'
import { arrayBufferToBase64 } from '@modrinth/utils'
const editSkinModal = useTemplateRef('editSkinModal') const editSkinModal = useTemplateRef('editSkinModal')
const selectCapeModal = useTemplateRef('selectCapeModal') const selectCapeModal = useTemplateRef('selectCapeModal')
const uploadSkinModal = useTemplateRef('uploadSkinModal') const uploadSkinModal = useTemplateRef('uploadSkinModal')
const notifications = useNotifications() const notifications = injectNotificationManager()
const { handleError } = notifications
const settings = ref(await getSettings()) const settings = ref(await getSettings())
const skins = ref<Skin[]>([]) const skins = ref<Skin[]>([])
@@ -64,7 +66,14 @@ const defaultCape = ref<Cape>()
const originalSelectedSkin = ref<Skin | null>(null) const originalSelectedSkin = ref<Skin | null>(null)
const originalDefaultCape = ref<Cape>() const originalDefaultCape = ref<Cape>()
const savedSkins = computed(() => filterSavedSkins(skins.value)) const savedSkins = computed(() => {
try {
return filterSavedSkins(skins.value)
} catch (error) {
handleError(error as Error)
return []
}
})
const defaultSkins = computed(() => filterDefaultSkins(skins.value)) const defaultSkins = computed(() => filterDefaultSkins(skins.value))
const currentCape = computed(() => { const currentCape = computed(() => {
@@ -113,7 +122,7 @@ async function loadCapes() {
defaultCape.value = capes.value.find((c) => c.is_equipped) defaultCape.value = capes.value.find((c) => c.is_equipped)
originalDefaultCape.value = defaultCape.value originalDefaultCape.value = defaultCape.value
} catch (error) { } catch (error) {
if (currentUser.value) { if (currentUser.value && error instanceof Error) {
handleError(error) handleError(error)
} }
} }
@@ -126,7 +135,7 @@ async function loadSkins() {
selectedSkin.value = skins.value.find((s) => s.is_equipped) ?? null selectedSkin.value = skins.value.find((s) => s.is_equipped) ?? null
originalSelectedSkin.value = selectedSkin.value originalSelectedSkin.value = selectedSkin.value
} catch (error) { } catch (error) {
if (currentUser.value) { if (currentUser.value && error instanceof Error) {
handleError(error) handleError(error)
} }
} }
@@ -161,7 +170,7 @@ async function changeSkin(newSkin: Skin) {
text: "You're changing your skin too frequently. Mojang's servers have temporarily blocked further requests. Please wait a moment before trying again.", text: "You're changing your skin too frequently. Mojang's servers have temporarily blocked further requests. Please wait a moment before trying again.",
}) })
} else { } else {
handleError(error) handleError(error as Error)
} }
} }
} }
@@ -190,7 +199,7 @@ async function handleCapeSelected(cape: Cape | undefined) {
text: "You're changing your cape too frequently. Mojang's servers have temporarily blocked further requests. Please wait a moment before trying again.", text: "You're changing your cape too frequently. Mojang's servers have temporarily blocked further requests. Please wait a moment before trying again.",
}) })
} else { } else {
handleError(error) handleError(error as Error)
} }
} }
} }
@@ -207,7 +216,7 @@ async function loadCurrentUser() {
const allAccounts = await users() const allAccounts = await users()
currentUser.value = allAccounts.find((acc) => acc.profile.id === defaultId) currentUser.value = allAccounts.find((acc) => acc.profile.id === defaultId)
} catch (e) { } catch (e) {
handleError(e) handleError(e as Error)
currentUser.value = undefined currentUser.value = undefined
currentUserId.value = undefined currentUserId.value = undefined
} }
@@ -276,7 +285,7 @@ async function checkUserChanges() {
await loadSkins() await loadSkins()
} }
} catch (error) { } catch (error) {
if (currentUser.value) { if (currentUser.value && error instanceof Error) {
handleError(error) handleError(error)
} }
} }
@@ -376,7 +385,7 @@ await Promise.all([loadCapes(), loadSkins(), loadCurrentUser()])
color="green" color="green"
aria-label="Edit skin" aria-label="Edit skin"
class="pointer-events-auto" class="pointer-events-auto"
@click.stop="(e) => editSkinModal?.show(e, skin)" @click.stop="(e: MouseEvent) => editSkinModal?.show(e, skin)"
> >
<EditIcon /> Edit <EditIcon /> Edit
</Button> </Button>

View File

@@ -1,6 +1,6 @@
import Index from './Index.vue'
import Browse from './Browse.vue' import Browse from './Browse.vue'
import Worlds from './Worlds.vue' import Index from './Index.vue'
import Skins from './Skins.vue' import Skins from './Skins.vue'
import Worlds from './Worlds.vue'
export { Index, Browse, Worlds, Skins } export { Browse, Index, Skins, Worlds }

View File

@@ -157,13 +157,6 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import {
Avatar,
ButtonStyled,
ContentPageHeader,
LoadingIndicator,
OverflowMenu,
} from '@modrinth/ui'
import { import {
CheckCircleIcon, CheckCircleIcon,
ClipboardCopyIcon, ClipboardCopyIcon,
@@ -187,28 +180,38 @@ import {
UserPlusIcon, UserPlusIcon,
XIcon, XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile' import {
import { get_by_profile_path } from '@/helpers/process' Avatar,
import { process_listener, profile_listener } from '@/helpers/events' ButtonStyled,
import { useRoute, useRouter } from 'vue-router' ContentPageHeader,
import { computed, onUnmounted, ref, watch } from 'vue' injectNotificationManager,
import { handleError, useBreadcrumbs, useLoading } from '@/store/state' LoadingIndicator,
import { showProfileInFolder } from '@/helpers/utils.js' OverflowMenu,
import ContextMenu from '@/components/ui/ContextMenu.vue' } from '@modrinth/ui'
import NavTabs from '@/components/ui/NavTabs.vue'
import { trackEvent } from '@/helpers/analytics'
import { convertFileSrc } from '@tauri-apps/api/core' import { convertFileSrc } from '@tauri-apps/api/core'
import { handleSevereError } from '@/store/error.js'
import { get_project, get_version_many } from '@/helpers/cache.js'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import duration from 'dayjs/plugin/duration' import duration from 'dayjs/plugin/duration'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { computed, onUnmounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import ContextMenu from '@/components/ui/ContextMenu.vue'
import ExportModal from '@/components/ui/ExportModal.vue' import ExportModal from '@/components/ui/ExportModal.vue'
import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.vue' import InstanceSettingsModal from '@/components/ui/modal/InstanceSettingsModal.vue'
import NavTabs from '@/components/ui/NavTabs.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project, get_version_many } from '@/helpers/cache.js'
import { process_listener, profile_listener } from '@/helpers/events'
import { get_by_profile_path } from '@/helpers/process'
import { finish_install, get, get_full_path, kill, run } from '@/helpers/profile'
import { showProfileInFolder } from '@/helpers/utils.js'
import { handleSevereError } from '@/store/error.js'
import { useBreadcrumbs, useLoading } from '@/store/state'
dayjs.extend(duration) dayjs.extend(duration)
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -328,7 +331,7 @@ const stopInstance = async (context) => {
} }
const repairInstance = async () => { const repairInstance = async () => {
await finish_install(instance.value) await finish_install(instance.value).catch(handleError)
} }
const handleRightClick = (event) => { const handleRightClick = (event) => {

View File

@@ -88,30 +88,32 @@
</template> </template>
<script setup> <script setup>
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets' import { CheckIcon, ClipboardCopyIcon, ShareIcon, TrashIcon } from '@modrinth/assets'
import { Button, Card, Checkbox, DropdownSelect } from '@modrinth/ui' import { Button, Card, Checkbox, DropdownSelect, injectNotificationManager } from '@modrinth/ui'
import {
delete_logs_by_filename,
get_logs,
get_output_by_filename,
get_latest_log_cursor,
} from '@/helpers/logs.js'
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import isToday from 'dayjs/plugin/isToday' import isToday from 'dayjs/plugin/isToday'
import isYesterday from 'dayjs/plugin/isYesterday' import isYesterday from 'dayjs/plugin/isYesterday'
import { get_by_profile_path } from '@/helpers/process.js'
import { useRoute } from 'vue-router'
import { process_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
import { ofetch } from 'ofetch' import { ofetch } from 'ofetch'
import { computed, nextTick, onBeforeUnmount, onMounted, onUnmounted, ref, watch } from 'vue'
import { useRoute } from 'vue-router'
import { RecycleScroller } from 'vue-virtual-scroller' import { RecycleScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue' import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import { process_listener } from '@/helpers/events.js'
import {
delete_logs_by_filename,
get_latest_log_cursor,
get_logs,
get_output_by_filename,
} from '@/helpers/logs.js'
import { get_by_profile_path } from '@/helpers/process.js'
dayjs.extend(isToday) dayjs.extend(isToday)
dayjs.extend(isYesterday) dayjs.extend(isYesterday)
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const props = defineProps({ const props = defineProps({

View File

@@ -60,7 +60,10 @@
if (x.id) { if (x.id) {
item.project = { item.project = {
id: x.id, id: x.id,
link: { path: `/project/${x.id}`, query: { i: props.instance.path } }, link: {
path: `/project/${x.id}`,
query: { i: props.instance.path },
},
linkProps: {}, linkProps: {},
} }
} }
@@ -271,16 +274,35 @@ import {
Button, Button,
ButtonStyled, ButtonStyled,
ContentListPanel, ContentListPanel,
injectNotificationManager,
OverflowMenu, OverflowMenu,
Pagination, Pagination,
RadialHeader, RadialHeader,
Toggle, Toggle,
} from '@modrinth/ui' } from '@modrinth/ui'
import type { ContentItem } from '@modrinth/ui/src/components/content/ContentListItem.vue'
import type { Organization, Project, TeamMember, Version } from '@modrinth/utils' import type { Organization, Project, TeamMember, Version } from '@modrinth/utils'
import { formatProjectType } from '@modrinth/utils' import { formatProjectType } from '@modrinth/utils'
import { getCurrentWebview } from '@tauri-apps/api/webview'
import { defineMessages, useVIntl } from '@vintl/vintl'
import dayjs from 'dayjs'
import type { ComputedRef } from 'vue' import type { ComputedRef } from 'vue'
import { computed, onUnmounted, ref, watch } from 'vue' import { computed, onUnmounted, ref, watch } from 'vue'
import { defineMessages, useVIntl } from '@vintl/vintl'
import { TextInputIcon } from '@/assets/icons'
import AddContentButton from '@/components/ui/AddContentButton.vue'
import type ContextMenu from '@/components/ui/ContextMenu.vue'
import ExportModal from '@/components/ui/ExportModal.vue'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import { trackEvent } from '@/helpers/analytics'
import {
get_organization_many,
get_project_many,
get_team_many,
get_version_many,
} from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events.js'
import { import {
add_project_from_path, add_project_from_path,
get_projects, get_projects,
@@ -289,26 +311,10 @@ import {
update_all, update_all,
update_project, update_project,
} from '@/helpers/profile.js' } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import { trackEvent } from '@/helpers/analytics'
import { highlightModInProfile } from '@/helpers/utils.js'
import { TextInputIcon } from '@/assets/icons'
import ExportModal from '@/components/ui/ExportModal.vue'
import ModpackVersionModal from '@/components/ui/ModpackVersionModal.vue'
import AddContentButton from '@/components/ui/AddContentButton.vue'
import {
get_organization_many,
get_project_many,
get_team_many,
get_version_many,
} from '@/helpers/cache.js'
import { profile_listener } from '@/helpers/events.js'
import ShareModalWrapper from '@/components/ui/modal/ShareModalWrapper.vue'
import { getCurrentWebview } from '@tauri-apps/api/webview'
import dayjs from 'dayjs'
import type { CacheBehaviour, ContentFile, GameInstance } from '@/helpers/types' import type { CacheBehaviour, ContentFile, GameInstance } from '@/helpers/types'
import type ContextMenu from '@/components/ui/ContextMenu.vue' import { highlightModInProfile } from '@/helpers/utils.js'
import type { ContentItem } from '@modrinth/ui/src/components/content/ContentListItem.vue'
const { handleError } = injectNotificationManager()
const props = defineProps<{ const props = defineProps<{
instance: GameInstance instance: GameInstance

View File

@@ -1,9 +1,10 @@
<template>{{ instance.name }} overview</template> <template>{{ instance.name }} overview</template>
<script setup lang="ts"> <script setup lang="ts">
import type { GameInstance } from '@/helpers/types'
import type ContextMenu from '@/components/ui/ContextMenu.vue'
import type { Version } from '@modrinth/utils' import type { Version } from '@modrinth/utils'
import type ContextMenu from '@/components/ui/ContextMenu.vue'
import type { GameInstance } from '@/helpers/types'
defineProps<{ defineProps<{
instance: GameInstance instance: GameInstance
options: InstanceType<typeof ContextMenu> options: InstanceType<typeof ContextMenu>

View File

@@ -121,55 +121,56 @@
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onUnmounted, watch } from 'vue' import { PlusIcon, SearchIcon, SpinnerIcon, UpdatedIcon, XIcon } from '@modrinth/assets'
import { useRoute } from 'vue-router'
import type { GameInstance } from '@/helpers/types'
import { import {
Button, Button,
ButtonStyled, ButtonStyled,
RadialHeader,
FilterBar, FilterBar,
type FilterBarOption, type FilterBarOption,
type GameVersion,
GAME_MODES, GAME_MODES,
type GameVersion,
injectNotificationManager,
RadialHeader,
} from '@modrinth/ui' } from '@modrinth/ui'
import { PlusIcon, SpinnerIcon, UpdatedIcon, SearchIcon, XIcon } from '@modrinth/assets' import type { Version } from '@modrinth/utils'
import { import { defineMessages } from '@vintl/vintl'
type ProtocolVersion, import { computed, onUnmounted, ref, watch } from 'vue'
type SingleplayerWorld, import { useRoute } from 'vue-router'
type World,
type ServerWorld, import type ContextMenu from '@/components/ui/ContextMenu.vue'
type ServerData, import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
type ProfileEvent,
get_profile_protocol_version,
remove_server_from_profile,
delete_world,
start_join_server,
start_join_singleplayer_world,
getWorldIdentifier,
refreshServerData,
refreshWorld,
sortWorlds,
refreshServers,
hasWorldQuickPlaySupport,
refreshWorlds,
handleDefaultProfileUpdateEvent,
showWorldInFolder,
hasServerQuickPlaySupport,
} from '@/helpers/worlds.ts'
import AddServerModal from '@/components/ui/world/modal/AddServerModal.vue' import AddServerModal from '@/components/ui/world/modal/AddServerModal.vue'
import EditServerModal from '@/components/ui/world/modal/EditServerModal.vue' import EditServerModal from '@/components/ui/world/modal/EditServerModal.vue'
import EditWorldModal from '@/components/ui/world/modal/EditSingleplayerWorldModal.vue' import EditWorldModal from '@/components/ui/world/modal/EditSingleplayerWorldModal.vue'
import WorldItem from '@/components/ui/world/WorldItem.vue' import WorldItem from '@/components/ui/world/WorldItem.vue'
import ConfirmModalWrapper from '@/components/ui/modal/ConfirmModalWrapper.vue'
import { handleError } from '@/store/notifications'
import type ContextMenu from '@/components/ui/ContextMenu.vue'
import type { Version } from '@modrinth/utils'
import { profile_listener } from '@/helpers/events' import { profile_listener } from '@/helpers/events'
import { get_game_versions } from '@/helpers/tags' import { get_game_versions } from '@/helpers/tags'
import { defineMessages } from '@vintl/vintl' import type { GameInstance } from '@/helpers/types'
import {
delete_world,
get_profile_protocol_version,
getWorldIdentifier,
handleDefaultProfileUpdateEvent,
hasServerQuickPlaySupport,
hasWorldQuickPlaySupport,
type ProfileEvent,
type ProtocolVersion,
refreshServerData,
refreshServers,
refreshWorld,
refreshWorlds,
remove_server_from_profile,
type ServerData,
type ServerWorld,
showWorldInFolder,
type SingleplayerWorld,
sortWorlds,
start_join_server,
start_join_singleplayer_world,
type World,
} from '@/helpers/worlds.ts'
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const addServerModal = ref<InstanceType<typeof AddServerModal>>() const addServerModal = ref<InstanceType<typeof AddServerModal>>()
@@ -282,7 +283,7 @@ async function editServer(server: ServerWorld) {
await refreshServer(server.address) await refreshServer(server.address)
} }
} else { } else {
handleError(`Error refreshing server, refreshing all worlds`) handleError(new Error(`Error refreshing server, refreshing all worlds`))
await refreshAllWorlds() await refreshAllWorlds()
} }
} }
@@ -301,7 +302,7 @@ async function editWorld(path: string, name: string, removeIcon: boolean) {
} }
sortWorlds(worlds.value) sortWorlds(worlds.value)
} else { } else {
handleError(`Error finding world in list, refreshing all worlds`) handleError(new Error(`Error finding world in list, refreshing all worlds`))
await refreshAllWorlds() await refreshAllWorlds()
} }
} }
@@ -311,7 +312,7 @@ async function deleteWorld(world: SingleplayerWorld) {
worlds.value = worlds.value.filter((w) => w.type !== 'singleplayer' || w.path !== world.path) worlds.value = worlds.value.filter((w) => w.type !== 'singleplayer' || w.path !== world.path)
} }
function handleJoinError(err: unknown) { function handleJoinError(err: Error) {
handleError(err) handleError(err)
startingInstance.value = false startingInstance.value = false
worldPlaying.value = undefined worldPlaying.value = undefined
@@ -436,7 +437,7 @@ function promptToRemoveWorld(world: World): boolean {
async function proceedRemoveServer() { async function proceedRemoveServer() {
if (!serverToRemove.value) { if (!serverToRemove.value) {
handleError(`Error removing server, no server marked for removal.`) handleError(new Error(`Error removing server, no server marked for removal.`))
return return
} }
await removeServer(serverToRemove.value) await removeServer(serverToRemove.value)
@@ -445,7 +446,7 @@ async function proceedRemoveServer() {
async function proceedDeleteWorld() { async function proceedDeleteWorld() {
if (!worldToDelete.value) { if (!worldToDelete.value) {
handleError(`Error deleting world, no world marked for removal.`) handleError(new Error(`Error deleting world, no world marked for removal.`))
return return
} }
await deleteWorld(worldToDelete.value) await deleteWorld(worldToDelete.value)

View File

@@ -1,7 +1,7 @@
import Index from './Index.vue' import Index from './Index.vue'
import Logs from './Logs.vue'
import Mods from './Mods.vue'
import Overview from './Overview.vue' import Overview from './Overview.vue'
import Worlds from './Worlds.vue' import Worlds from './Worlds.vue'
import Mods from './Mods.vue'
import Logs from './Logs.vue'
export { Index, Overview, Worlds, Mods, Logs } export { Index, Logs, Mods, Overview, Worlds }

View File

@@ -1,16 +1,17 @@
<script setup> <script setup>
import { onUnmounted, ref, shallowRef } from 'vue'
import { list } from '@/helpers/profile.js'
import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs.js'
import { profile_listener } from '@/helpers/events.js'
import { handleError } from '@/store/notifications.js'
import { Button } from '@modrinth/ui'
import { PlusIcon } from '@modrinth/assets' import { PlusIcon } from '@modrinth/assets'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue' import { Button, injectNotificationManager } from '@modrinth/ui'
import { NewInstanceImage } from '@/assets/icons' import { onUnmounted, ref, shallowRef } from 'vue'
import NavTabs from '@/components/ui/NavTabs.vue' import { useRoute } from 'vue-router'
import { NewInstanceImage } from '@/assets/icons'
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
import NavTabs from '@/components/ui/NavTabs.vue'
import { profile_listener } from '@/helpers/events.js'
import { list } from '@/helpers/profile.js'
import { useBreadcrumbs } from '@/store/breadcrumbs.js'
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()

View File

@@ -1,6 +1,6 @@
import Custom from './Custom.vue'
import Downloaded from './Downloaded.vue'
import Index from './Index.vue' import Index from './Index.vue'
import Overview from './Overview.vue' import Overview from './Overview.vue'
import Downloaded from './Downloaded.vue'
import Custom from './Custom.vue'
export { Index, Overview, Downloaded, Custom } export { Custom, Downloaded, Index, Overview }

View File

@@ -83,16 +83,18 @@
<script setup> <script setup>
import { import {
ExpandIcon,
RightArrowIcon,
LeftArrowIcon,
ExternalIcon,
ContractIcon,
XIcon,
CalendarIcon, CalendarIcon,
ContractIcon,
ExpandIcon,
ExternalIcon,
LeftArrowIcon,
RightArrowIcon,
XIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { Button, Card } from '@modrinth/ui' import { Button, Card } from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
// import { hide_ads_window, show_ads_window } from '@/helpers/ads.js'
import { trackEvent } from '@/helpers/analytics' import { trackEvent } from '@/helpers/analytics'
const props = defineProps({ const props = defineProps({
@@ -108,6 +110,7 @@ const zoomedIn = ref(false)
const hideImage = () => { const hideImage = () => {
expandedGalleryItem.value = null expandedGalleryItem.value = null
// show_ads_window()
} }
const nextImage = () => { const nextImage = () => {
@@ -135,6 +138,7 @@ const previousImage = () => {
} }
const expandImage = (item, index) => { const expandImage = (item, index) => {
// hide_ads_window()
expandedGalleryItem.value = item expandedGalleryItem.value = item
expandedGalleryIndex.value = index expandedGalleryIndex.value = index
zoomedIn.value = false zoomedIn.value = false

View File

@@ -131,44 +131,45 @@
<script setup> <script setup>
import { import {
BookmarkIcon, BookmarkIcon,
MoreVerticalIcon,
DownloadIcon,
ReportIcon,
HeartIcon,
ExternalIcon,
CheckIcon, CheckIcon,
GlobeIcon,
ClipboardCopyIcon, ClipboardCopyIcon,
DownloadIcon,
ExternalIcon,
GlobeIcon,
HeartIcon,
MoreVerticalIcon,
ReportIcon,
} from '@modrinth/assets' } from '@modrinth/assets'
import { import {
ButtonStyled,
injectNotificationManager,
OverflowMenu,
ProjectBackgroundGradient,
ProjectHeader, ProjectHeader,
ProjectSidebarCompatibility, ProjectSidebarCompatibility,
ButtonStyled,
OverflowMenu,
ProjectSidebarLinks,
ProjectSidebarCreators, ProjectSidebarCreators,
ProjectSidebarDetails, ProjectSidebarDetails,
ProjectBackgroundGradient, ProjectSidebarLinks,
} from '@modrinth/ui' } from '@modrinth/ui'
import { openUrl } from '@tauri-apps/plugin-opener'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import relativeTime from 'dayjs/plugin/relativeTime' import relativeTime from 'dayjs/plugin/relativeTime'
import { useRoute, useRouter } from 'vue-router'
import { ref, shallowRef, watch } from 'vue' import { ref, shallowRef, watch } from 'vue'
import { useBreadcrumbs } from '@/store/breadcrumbs' import { useRoute, useRouter } from 'vue-router'
import { handleError } from '@/store/notifications.js'
import ContextMenu from '@/components/ui/ContextMenu.vue' import ContextMenu from '@/components/ui/ContextMenu.vue'
import { install as installVersion } from '@/store/install.js'
import { get_project, get_team, get_version_many } from '@/helpers/cache.js'
import NavTabs from '@/components/ui/NavTabs.vue'
import { useTheming } from '@/store/state.js'
import InstanceIndicator from '@/components/ui/InstanceIndicator.vue' import InstanceIndicator from '@/components/ui/InstanceIndicator.vue'
import { openUrl } from '@tauri-apps/plugin-opener' import NavTabs from '@/components/ui/NavTabs.vue'
import { get_project, get_team, get_version_many } from '@/helpers/cache.js'
import { get as getInstance, get_projects as getInstanceProjects } from '@/helpers/profile'
import { get_categories, get_game_versions, get_loaders } from '@/helpers/tags'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { install as installVersion } from '@/store/install.js'
import { useTheming } from '@/store/state.js'
dayjs.extend(relativeTime) dayjs.extend(relativeTime)
const { handleError } = injectNotificationManager()
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()
@@ -251,7 +252,7 @@ async function install(version) {
(profile) => { (profile) => {
router.push(`/instance/${profile}`) router.push(`/instance/${profile}`)
}, },
) ).catch(handleError)
} }
const options = ref(null) const options = ref(null)

View File

@@ -182,15 +182,16 @@
</template> </template>
<script setup> <script setup>
import { DownloadIcon, FileIcon, ReportIcon, ExternalIcon, CheckIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, ExternalIcon, FileIcon, ReportIcon } from '@modrinth/assets'
import { Avatar, Badge, Breadcrumbs, Button, Card, CopyCode } from '@modrinth/ui'
import { formatBytes, renderString } from '@modrinth/utils' import { formatBytes, renderString } from '@modrinth/utils'
import { Breadcrumbs, Badge, Avatar, Card, Button, CopyCode } from '@modrinth/ui' import { computed, ref, watch } from 'vue'
import { releaseColor } from '@/helpers/utils'
import { ref, watch, computed } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { useBreadcrumbs } from '@/store/breadcrumbs'
import { SwapIcon } from '@/assets/icons' import { SwapIcon } from '@/assets/icons'
import { get_project_many, get_version_many } from '@/helpers/cache.js' import { get_project_many, get_version_many } from '@/helpers/cache.js'
import { releaseColor } from '@/helpers/utils'
import { useBreadcrumbs } from '@/store/breadcrumbs'
const breadcrumbs = useBreadcrumbs() const breadcrumbs = useBreadcrumbs()

View File

@@ -65,12 +65,17 @@
</template> </template>
<script setup> <script setup>
import { ProjectPageVersions, ButtonStyled, OverflowMenu } from '@modrinth/ui'
import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets' import { CheckIcon, DownloadIcon, ExternalIcon, MoreVerticalIcon } from '@modrinth/assets'
import {
ButtonStyled,
injectNotificationManager,
OverflowMenu,
ProjectPageVersions,
} from '@modrinth/ui'
import { ref } from 'vue' import { ref } from 'vue'
import { SwapIcon } from '@/assets/icons/index.js' import { SwapIcon } from '@/assets/icons/index.js'
import { get_game_versions, get_loaders } from '@/helpers/tags.js' import { get_game_versions, get_loaders } from '@/helpers/tags.js'
import { handleError } from '@/store/notifications.js'
defineProps({ defineProps({
project: { project: {
@@ -103,6 +108,8 @@ defineProps({
}, },
}) })
const { handleError } = injectNotificationManager()
const [loaders, gameVersions] = await Promise.all([ const [loaders, gameVersions] = await Promise.all([
get_loaders().catch(handleError).then(ref), get_loaders().catch(handleError).then(ref),
get_game_versions().catch(handleError).then(ref), get_game_versions().catch(handleError).then(ref),

View File

@@ -1,7 +1,7 @@
import Index from './Index.vue'
import Description from './Description.vue' import Description from './Description.vue'
import Versions from './Versions.vue'
import Gallery from './Gallery.vue' import Gallery from './Gallery.vue'
import Index from './Index.vue'
import Version from './Version.vue' import Version from './Version.vue'
import Versions from './Versions.vue'
export { Index, Description, Versions, Gallery, Version } export { Description, Gallery, Index, Version, Versions }

View File

@@ -0,0 +1,48 @@
import {
AbstractWebNotificationManager,
type NotificationPanelLocation,
type WebNotification,
} from '@modrinth/ui'
import { type Ref, ref } from 'vue'
export class AppNotificationManager extends AbstractWebNotificationManager {
private readonly state: Ref<WebNotification[]>
private readonly locationState: Ref<NotificationPanelLocation>
public constructor() {
super()
this.state = ref<WebNotification[]>([])
this.locationState = ref<NotificationPanelLocation>('right')
}
public getNotificationLocation(): NotificationPanelLocation {
return this.locationState.value
}
public setNotificationLocation(location: NotificationPanelLocation): void {
this.locationState.value = location
}
public getNotifications(): WebNotification[] {
return this.state.value
}
protected addNotificationToStorage(notification: WebNotification): void {
this.state.value.push(notification)
}
protected removeNotificationFromStorage(id: string | number): void {
const index = this.state.value.findIndex((n) => n.id === id)
if (index > -1) {
this.state.value.splice(index, 1)
}
}
protected removeNotificationFromStorageByIndex(index: number): void {
this.state.value.splice(index, 1)
}
protected clearAllNotificationsFromStorage(): void {
this.state.value.splice(0)
}
}

View File

@@ -1,8 +1,9 @@
import { createRouter, createWebHistory } from 'vue-router' import { createRouter, createWebHistory } from 'vue-router'
import * as Pages from '@/pages' import * as Pages from '@/pages'
import * as Project from '@/pages/project'
import * as Instance from '@/pages/instance' import * as Instance from '@/pages/instance'
import * as Library from '@/pages/library' import * as Library from '@/pages/library'
import * as Project from '@/pages/project'
/** /**
* Configures application routing. Add page to pages/index and then add to route table here. * Configures application routing. Add page to pages/index and then add to route table here.

View File

@@ -1,4 +1,9 @@
import dayjs from 'dayjs'
import { defineStore } from 'pinia' import { defineStore } from 'pinia'
import { trackEvent } from '@/helpers/analytics.js'
import { get_project, get_version_many } from '@/helpers/cache.js'
import { create_profile_and_install as packInstall } from '@/helpers/pack.js'
import { import {
add_project_from_version, add_project_from_version,
check_installed, check_installed,
@@ -7,11 +12,6 @@ import {
list, list,
remove_project, remove_project,
} from '@/helpers/profile.js' } from '@/helpers/profile.js'
import { handleError } from '@/store/notifications.js'
import { get_project, get_version_many } from '@/helpers/cache.js'
import { create_profile_and_install as packInstall } from '@/helpers/pack.js'
import { trackEvent } from '@/helpers/analytics.js'
import dayjs from 'dayjs'
export const useInstall = defineStore('installStore', { export const useInstall = defineStore('installStore', {
state: () => ({ state: () => ({
@@ -49,11 +49,11 @@ export const install = async (
callback = () => {}, callback = () => {},
createInstanceCallback = () => {}, createInstanceCallback = () => {},
) => { ) => {
const project = await get_project(projectId, 'must_revalidate').catch(handleError) const project = await get_project(projectId, 'must_revalidate')
if (project.project_type === 'modpack') { if (project.project_type === 'modpack') {
const version = versionId ?? project.versions[project.versions.length - 1] const version = versionId ?? project.versions[project.versions.length - 1]
const packs = await list().catch(handleError) const packs = await list()
if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) { if (packs.length === 0 || !packs.find((pack) => pack.linked_data?.project_id === project.id)) {
await packInstall( await packInstall(
@@ -62,7 +62,7 @@ export const install = async (
project.title, project.title,
project.icon_url, project.icon_url,
createInstanceCallback, createInstanceCallback,
).catch(handleError) )
trackEvent('PackInstall', { trackEvent('PackInstall', {
id: project.id, id: project.id,
@@ -79,8 +79,8 @@ export const install = async (
} else { } else {
if (instancePath) { if (instancePath) {
const [instance, instanceProjects, versions] = await Promise.all([ const [instance, instanceProjects, versions] = await Promise.all([
await get(instancePath).catch(handleError), await get(instancePath),
await get_projects(instancePath).catch(handleError), await get_projects(instancePath),
await get_version_many(project.versions, 'must_revalidate'), await get_version_many(project.versions, 'must_revalidate'),
]) ])
@@ -117,7 +117,7 @@ export const install = async (
} }
} }
await add_project_from_version(instance.path, version.id).catch(handleError) await add_project_from_version(instance.path, version.id)
await installVersionDependencies(instance, version) await installVersionDependencies(instance, version)
trackEvent('ProjectInstall', { trackEvent('ProjectInstall', {
@@ -142,7 +142,7 @@ export const install = async (
) )
} }
} else { } else {
const versions = (await get_version_many(project.versions).catch(handleError)).sort( const versions = (await get_version_many(project.versions)).sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published), (a, b) => dayjs(b.date_published) - dayjs(a.date_published),
) )
@@ -171,30 +171,22 @@ export const installVersionDependencies = async (profile, version) => {
// disallow fabric api install on quilt // disallow fabric api install on quilt
if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue if (dep.project_id === 'P7dR8mSH' && profile.loader === 'quilt') continue
if (dep.version_id) { if (dep.version_id) {
if ( if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
dep.project_id &&
(await check_installed(profile.path, dep.project_id).catch(handleError))
)
continue
await add_project_from_version(profile.path, dep.version_id) await add_project_from_version(profile.path, dep.version_id)
} else { } else {
if ( if (dep.project_id && (await check_installed(profile.path, dep.project_id))) continue
dep.project_id &&
(await check_installed(profile.path, dep.project_id).catch(handleError)) const depProject = await get_project(dep.project_id, 'must_revalidate')
const depVersions = (await get_version_many(depProject.versions, 'must_revalidate')).sort(
(a, b) => dayjs(b.date_published) - dayjs(a.date_published),
) )
continue
const depProject = await get_project(dep.project_id, 'must_revalidate').catch(handleError)
const depVersions = (
await get_version_many(depProject.versions, 'must_revalidate').catch(handleError)
).sort((a, b) => dayjs(b.date_published) - dayjs(a.date_published))
const latest = depVersions.find( const latest = depVersions.find(
(v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader), (v) => v.game_versions.includes(profile.game_version) && v.loaders.includes(profile.loader),
) )
if (latest) { if (latest) {
await add_project_from_version(profile.path, latest.id).catch(handleError) await add_project_from_version(profile.path, latest.id)
} }
} }
} }

Some files were not shown because too many files have changed in this diff Show More