Skip to content

Commit 34b6e5c

Browse files
authored
Merge branch 'main' into main
2 parents fffb309 + 0bc6502 commit 34b6e5c

File tree

197 files changed

+6725
-2591
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

197 files changed

+6725
-2591
lines changed

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],
55
"editor.detectIndentation": true,
66
"editor.codeActionsOnSave": {
7-
"source.fixAll.eslint": "explicit"
7+
"source.fixAll.eslint": "explicit",
8+
"source.organizeImports": "always",
89
}
910
}

apps/app-frontend/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
<body>
1313
<div id="app"></div>
14+
<script src="https://tally.so/widgets/embed.js" async></script>
1415
<script type="module" src="/src/main.js"></script>
1516
</body>
1617
</html>

apps/app-frontend/src/App.vue

Lines changed: 213 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,36 @@
11
<script setup>
2-
import { computed, onMounted, onUnmounted, ref, watch, provide } from 'vue'
3-
import { RouterView, useRoute, useRouter } from 'vue-router'
2+
import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
3+
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
4+
import AccountsCard from '@/components/ui/AccountsCard.vue'
5+
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
6+
import ErrorModal from '@/components/ui/ErrorModal.vue'
7+
import FriendsList from '@/components/ui/friends/FriendsList.vue'
8+
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
9+
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
10+
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
11+
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
12+
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
13+
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
14+
import NavButton from '@/components/ui/NavButton.vue'
15+
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
16+
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
17+
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
18+
import SplashScreen from '@/components/ui/SplashScreen.vue'
19+
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
20+
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
21+
import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js'
22+
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
23+
import { get_user } from '@/helpers/cache.js'
24+
import { command_listener, warning_listener } from '@/helpers/events.js'
25+
import { useFetch } from '@/helpers/fetch.js'
26+
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js'
27+
import { list } from '@/helpers/profile.js'
28+
import { get } from '@/helpers/settings.ts'
29+
import { get_opening_command, initialize_state } from '@/helpers/state'
30+
import { getOS, isDev, restartApp } from '@/helpers/utils.js'
31+
import { useError } from '@/store/error.js'
32+
import { useInstall } from '@/store/install.js'
33+
import { useLoading, useTheming } from '@/store/state'
434
import {
535
ArrowBigUpDashIcon,
636
ChangeSkinIcon,
@@ -13,69 +43,48 @@ import {
1343
LogOutIcon,
1444
MaximizeIcon,
1545
MinimizeIcon,
46+
NewspaperIcon,
47+
NotepadTextIcon,
1648
PlusIcon,
1749
RestoreIcon,
1850
RightArrowIcon,
1951
SettingsIcon,
2052
WorldIcon,
2153
XIcon,
22-
NewspaperIcon,
2354
} from '@modrinth/assets'
2455
import {
2556
Avatar,
2657
Button,
2758
ButtonStyled,
28-
Notifications,
29-
OverflowMenu,
3059
NewsArticleCard,
60+
NotificationPanel,
61+
OverflowMenu,
62+
provideNotificationManager,
3163
} from '@modrinth/ui'
32-
import { useLoading, useTheming } from '@/store/state'
33-
import ModrinthAppLogo from '@/assets/modrinth_app.svg?component'
34-
import AccountsCard from '@/components/ui/AccountsCard.vue'
35-
import InstanceCreationModal from '@/components/ui/InstanceCreationModal.vue'
36-
import { get } from '@/helpers/settings.ts'
37-
import Breadcrumbs from '@/components/ui/Breadcrumbs.vue'
38-
import RunningAppBar from '@/components/ui/RunningAppBar.vue'
39-
import SplashScreen from '@/components/ui/SplashScreen.vue'
40-
import ErrorModal from '@/components/ui/ErrorModal.vue'
41-
import ModrinthLoadingIndicator from '@/components/LoadingIndicatorBar.vue'
42-
import { handleError, useNotifications } from '@/store/notifications.js'
43-
import { command_listener, warning_listener } from '@/helpers/events.js'
44-
import { type } from '@tauri-apps/plugin-os'
45-
import { getOS, isDev, restartApp } from '@/helpers/utils.js'
46-
import { debugAnalytics, initAnalytics, optOutAnalytics, trackEvent } from '@/helpers/analytics'
47-
import { getCurrentWindow } from '@tauri-apps/api/window'
64+
import { renderString } from '@modrinth/utils'
4865
import { getVersion } from '@tauri-apps/api/app'
49-
import URLConfirmModal from '@/components/ui/URLConfirmModal.vue'
50-
import { create_profile_and_install_from_file } from './helpers/pack'
51-
import { useError } from '@/store/error.js'
52-
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
53-
import ModInstallModal from '@/components/ui/install_flow/ModInstallModal.vue'
54-
import IncompatibilityWarningModal from '@/components/ui/install_flow/IncompatibilityWarningModal.vue'
55-
import InstallConfirmModal from '@/components/ui/install_flow/InstallConfirmModal.vue'
56-
import { useInstall } from '@/store/install.js'
5766
import { invoke } from '@tauri-apps/api/core'
58-
import { get_opening_command, initialize_state } from '@/helpers/state'
59-
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
60-
import { renderString } from '@modrinth/utils'
61-
import { useFetch } from '@/helpers/fetch.js'
62-
import { check } from '@tauri-apps/plugin-updater'
63-
import NavButton from '@/components/ui/NavButton.vue'
64-
import { cancelLogin, get as getCreds, login, logout } from '@/helpers/mr_auth.js'
65-
import { get_user } from '@/helpers/cache.js'
66-
import AppSettingsModal from '@/components/ui/modal/AppSettingsModal.vue'
67-
import AuthGrantFlowWaitModal from '@/components/ui/modal/AuthGrantFlowWaitModal.vue'
68-
import PromotionWrapper from '@/components/ui/PromotionWrapper.vue'
69-
import { hide_ads_window, init_ads_window } from '@/helpers/ads.js'
70-
import FriendsList from '@/components/ui/friends/FriendsList.vue'
67+
import { getCurrentWindow } from '@tauri-apps/api/window'
7168
import { openUrl } from '@tauri-apps/plugin-opener'
72-
import QuickInstanceSwitcher from '@/components/ui/QuickInstanceSwitcher.vue'
73-
import { get_available_capes, get_available_skins } from './helpers/skins'
69+
import { type } from '@tauri-apps/plugin-os'
70+
import { check } from '@tauri-apps/plugin-updater'
71+
import { saveWindowState, StateFlags } from '@tauri-apps/plugin-window-state'
72+
import { $fetch } from 'ofetch'
73+
import { computed, onMounted, onUnmounted, provide, ref, watch } from 'vue'
74+
import { RouterView, useRoute, useRouter } from 'vue-router'
75+
import { create_profile_and_install_from_file } from './helpers/pack'
7476
import { generateSkinPreviews } from './helpers/rendering/batch-skin-renderer'
77+
import { get_available_capes, get_available_skins } from './helpers/skins'
78+
import { AppNotificationManager } from './providers/app-notifications'
7579
7680
const themeStore = useTheming()
7781
82+
const notificationManager = new AppNotificationManager()
83+
provideNotificationManager(notificationManager)
84+
const { handleError, addNotification } = notificationManager
85+
7886
const news = ref([])
87+
const availableSurvey = ref(false)
7988
8089
const urlModal = ref(null)
8190
@@ -167,7 +176,7 @@ async function setupApp() {
167176
}
168177
169178
await warning_listener((e) =>
170-
notificationsWrapper.value.addNotification({
179+
addNotification({
171180
title: 'Warning',
172181
text: e.message,
173182
type: 'warn',
@@ -220,6 +229,12 @@ async function setupApp() {
220229
} catch (error) {
221230
console.warn('Failed to generate skin previews in app setup.', error)
222231
}
232+
233+
if (osType === 'windows') {
234+
await processPendingSurveys()
235+
} else {
236+
console.info('Skipping user surveys on non-Windows platforms')
237+
}
223238
}
224239
225240
const stateFailed = ref(false)
@@ -251,9 +266,6 @@ const route = useRoute()
251266
const loading = useLoading()
252267
loading.setEnabled(false)
253268
254-
const notifications = useNotifications()
255-
const notificationsWrapper = ref()
256-
257269
const error = useError()
258270
const errorModal = ref()
259271
@@ -335,8 +347,6 @@ watch(
335347
onMounted(() => {
336348
invoke('show_window')
337349
338-
notifications.setNotifs(notificationsWrapper.value)
339-
340350
error.setErrorModal(errorModal.value)
341351
342352
install.setIncompatibilityWarningModal(incompatibilityWarningModal)
@@ -412,6 +422,116 @@ function handleAuxClick(e) {
412422
e.target.dispatchEvent(event)
413423
}
414424
}
425+
426+
function cleanupOldSurveyDisplayData() {
427+
const threeWeeksAgo = new Date()
428+
threeWeeksAgo.setDate(threeWeeksAgo.getDate() - 21)
429+
430+
for (let i = 0; i < localStorage.length; i++) {
431+
const key = localStorage.key(i)
432+
433+
if (key.startsWith('survey-') && key.endsWith('-display')) {
434+
const dateValue = new Date(localStorage.getItem(key))
435+
if (dateValue < threeWeeksAgo) {
436+
localStorage.removeItem(key)
437+
}
438+
}
439+
}
440+
}
441+
442+
async function openSurvey() {
443+
if (!availableSurvey.value) {
444+
console.error('No survey to open')
445+
return
446+
}
447+
448+
const creds = await getCreds().catch(handleError)
449+
const userId = creds?.user_id
450+
451+
const formId = availableSurvey.value.tally_id
452+
453+
const popupOptions = {
454+
layout: 'modal',
455+
width: 700,
456+
autoClose: 2000,
457+
hideTitle: true,
458+
hiddenFields: {
459+
user_id: userId,
460+
},
461+
onOpen: () => console.info('Opened user survey'),
462+
onClose: () => {
463+
console.info('Closed user survey')
464+
show_ads_window()
465+
},
466+
onSubmit: () => console.info('Active user survey submitted'),
467+
}
468+
469+
try {
470+
hide_ads_window()
471+
if (window.Tally?.openPopup) {
472+
console.info(`Opening Tally popup for user survey (form ID: ${formId})`)
473+
dismissSurvey()
474+
window.Tally.openPopup(formId, popupOptions)
475+
} else {
476+
console.warn('Tally script not yet loaded')
477+
show_ads_window()
478+
}
479+
} catch (e) {
480+
console.error('Error opening Tally popup:', e)
481+
show_ads_window()
482+
}
483+
484+
console.info(`Found user survey to show with tally_id: ${formId}`)
485+
window.Tally.openPopup(formId, popupOptions)
486+
}
487+
488+
function dismissSurvey() {
489+
localStorage.setItem(`survey-${availableSurvey.value.id}-display`, new Date())
490+
availableSurvey.value = undefined
491+
}
492+
493+
async function processPendingSurveys() {
494+
function isWithinLastTwoWeeks(date) {
495+
const twoWeeksAgo = new Date()
496+
twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14)
497+
return date >= twoWeeksAgo
498+
}
499+
500+
cleanupOldSurveyDisplayData()
501+
502+
const creds = await getCreds().catch(handleError)
503+
const userId = creds?.user_id
504+
505+
const instances = await list().catch(handleError)
506+
const isActivePlayer =
507+
instances.findIndex(
508+
(instance) =>
509+
isWithinLastTwoWeeks(instance.last_played) && !isWithinLastTwoWeeks(instance.created),
510+
) >= 0
511+
512+
let surveys = []
513+
try {
514+
surveys = await $fetch('https://api.modrinth.com/v2/surveys')
515+
} catch (e) {
516+
console.error('Error fetching surveys:', e)
517+
}
518+
519+
const surveyToShow = surveys.find(
520+
(survey) =>
521+
!!(
522+
localStorage.getItem(`survey-${survey.id}-display`) === null &&
523+
survey.type === 'tally_app' &&
524+
((survey.condition === 'active_player' && isActivePlayer) ||
525+
(survey.assigned_users?.includes(userId) && !survey.dismissed_users?.includes(userId)))
526+
),
527+
)
528+
529+
if (surveyToShow) {
530+
availableSurvey.value = surveyToShow
531+
} else {
532+
console.info('No user survey to show')
533+
}
534+
}
415535
</script>
416536
417537
<template>
@@ -565,6 +685,28 @@ function handleAuxClick(e) {
565685
:class="{ 'sidebar-enabled': sidebarVisible }"
566686
>
567687
<div class="app-viewport flex-grow router-view">
688+
<transition name="popup-survey">
689+
<div
690+
v-if="availableSurvey"
691+
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"
692+
>
693+
<h2 class="text-lg font-extrabold mt-0 mb-2">Hey there Modrinth user!</h2>
694+
<p class="m-0 leading-tight">
695+
Would you mind answering a few questions about your experience with Modrinth App?
696+
</p>
697+
<p class="mt-3 mb-4 leading-tight">
698+
This feedback will go directly to the Modrinth team and help guide future updates!
699+
</p>
700+
<div class="flex gap-2">
701+
<ButtonStyled color="brand">
702+
<button @click="openSurvey"><NotepadTextIcon /> Take survey</button>
703+
</ButtonStyled>
704+
<ButtonStyled>
705+
<button @click="dismissSurvey"><XIcon /> No thanks</button>
706+
</ButtonStyled>
707+
</div>
708+
</div>
709+
</transition>
568710
<div
569711
class="loading-indicator-container h-8 fixed z-50"
570712
:style="{
@@ -657,7 +799,7 @@ function handleAuxClick(e) {
657799
</div>
658800
</div>
659801
<URLConfirmModal ref="urlModal" />
660-
<Notifications ref="notificationsWrapper" sidebar />
802+
<NotificationPanel has-sidebar />
661803
<ErrorModal ref="errorModal" />
662804
<ModInstallModal ref="modInstallModal" />
663805
<IncompatibilityWarningModal ref="incompatibilityWarningModal" />
@@ -862,6 +1004,26 @@ function handleAuxClick(e) {
8621004
.sidebar-teleport-content:empty + .sidebar-default-content.sidebar-enabled {
8631005
display: contents;
8641006
}
1007+
1008+
.popup-survey-enter-active {
1009+
transition:
1010+
opacity 0.25s ease,
1011+
transform 0.25s cubic-bezier(0.51, 1.08, 0.35, 1.15);
1012+
transform-origin: top center;
1013+
}
1014+
1015+
.popup-survey-leave-active {
1016+
transition:
1017+
opacity 0.25s ease,
1018+
transform 0.25s cubic-bezier(0.68, -0.17, 0.23, 0.11);
1019+
transform-origin: top center;
1020+
}
1021+
1022+
.popup-survey-enter-from,
1023+
.popup-survey-leave-to {
1024+
opacity: 0;
1025+
transform: translateY(10rem) scale(0.8) scaleY(1.6);
1026+
}
8651027
</style>
8661028
<style>
8671029
.mac {

0 commit comments

Comments
 (0)