Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 97 additions & 14 deletions apps/app-frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,14 @@ import SplashScreen from '@/components/ui/SplashScreen.vue'
import WindowControls from '@/components/ui/WindowControls.vue'
import { useCheckDisableMouseover } from '@/composables/macCssFix.js'
import { config } from '@/config'
import { hide_ads_window, init_ads_window, show_ads_window } from '@/helpers/ads.js'
import {
ads_consent_listener,
get_ads_consent_required,
hide_ads_window,
init_ads_window,
perform_ads_consent_action,
show_ads_window,
} from '@/helpers/ads.js'
import { debugAnalytics, initAnalytics, trackEvent } from '@/helpers/analytics'
import { check_reachable } from '@/helpers/auth.js'
import { get_user, get_version } from '@/helpers/cache.js'
Expand Down Expand Up @@ -184,6 +191,8 @@ const { handleError, addNotification } = notificationManager
const popupNotificationManager = new AppPopupNotificationManager()
providePopupNotificationManager(popupNotificationManager)
const { addPopupNotification } = popupNotificationManager
let adsConsentPopupId = null
let unlistenAdsConsent

const appVersion = getVersion()
const tauriApiClient = new TauriModrinthClient({
Expand Down Expand Up @@ -213,9 +222,18 @@ const { data: authenticatedModrinthUser } = useQuery({
enabled: () => !!credentials.value?.session,
retry: false,
})
const hasPlus = computed(
() =>
!!credentials.value?.user &&
(hasMidasBadge(credentials.value.user) ||
hasActivePride26Midas(authenticatedModrinthUser.value?.campaigns?.pride_26)),
)
const showAd = computed(
() => sidebarVisible.value && !hasPlus.value && credentials.value !== undefined,
)
providePageContext({
hierarchicalSidebarAvailable: ref(true),
showAds: ref(false),
showAds: showAd,
floatingActionBarOffsets: {
left: ref(APP_LEFT_NAV_WIDTH),
right: computed(() => (sidebarVisible.value ? `${APP_SIDEBAR_WIDTH}px` : '0px')),
Expand Down Expand Up @@ -295,6 +313,12 @@ const authUnreachable = computed(() => {

onMounted(async () => {
await useCheckDisableMouseover()
try {
unlistenAdsConsent = await ads_consent_listener(handleAdsConsentRequired)
handleAdsConsentRequired(await get_ads_consent_required())
} catch (error) {
handleError(error)
}

document.querySelector('body').addEventListener('click', handleClick)
document.querySelector('body').addEventListener('auxclick', handleAuxClick)
Expand All @@ -308,6 +332,7 @@ onUnmounted(async () => {
unsubscribeSidebarToggle()
clearDelayedUpdatePopup()

await unlistenAdsConsent?.()
await unlistenUpdateDownload?.()
})

Expand All @@ -332,8 +357,77 @@ const messages = defineMessages({
defaultMessage:
'Minecraft authentication servers may be down right now. Check your internet connection and try again later.',
},
adsConsentTitle: {
id: 'app.ads-consent.title',
defaultMessage: 'Your privacy and how ads support Modrinth',
},
adsConsentBody: {
id: 'app.ads-consent.body',
defaultMessage:
'Ads make Modrinth possible and fund creator payouts. Our partners may store unique identifiers to personalize ads and measure performance.',
},
adsConsentManage: {
id: 'app.ads-consent.manage',
defaultMessage: 'Manage preferences',
},
adsConsentReject: {
id: 'app.ads-consent.reject',
defaultMessage: 'Reject all',
},
adsConsentAccept: {
id: 'app.ads-consent.accept',
defaultMessage: 'Accept all',
},
})

function handleAdsConsentRequired(required) {
if (!required) {
if (adsConsentPopupId !== null) {
popupNotificationManager.removeNotification(adsConsentPopupId)
adsConsentPopupId = null
}
return
}

if (
adsConsentPopupId !== null &&
popupNotificationManager.getNotifications().some((item) => item.id === adsConsentPopupId)
) {
return
}

const notification = addPopupNotification({
title: formatMessage(messages.adsConsentTitle),
text: formatMessage(messages.adsConsentBody),
type: 'info',
hideIcon: true,
autoCloseMs: null,
dismissible: false,
buttons: [
{
label: formatMessage(messages.adsConsentManage),
action: () => perform_ads_consent_action('manage').catch(handleError),
color: 'standard',
keepOpen: true,
},
{
label: formatMessage(messages.adsConsentReject),
action: () => perform_ads_consent_action('reject').catch(handleError),
color: 'brand',
keepOpen: true,
},
{
label: formatMessage(messages.adsConsentAccept),
action: () => perform_ads_consent_action('accept').catch(handleError),
color: 'brand',
keepOpen: true,
},
],
})

adsConsentPopupId = notification.id
}

async function setupApp() {
const {
native_decorations,
Expand Down Expand Up @@ -403,7 +497,7 @@ async function setupApp() {
addNotification({
title: 'Warning',
text: e.message,
type: 'warn',
type: 'warning',
}),
)

Expand Down Expand Up @@ -703,17 +797,6 @@ async function logOut() {
await fetchCredentials()
}

const hasPlus = computed(
() =>
!!credentials.value?.user &&
(hasMidasBadge(credentials.value.user) ||
hasActivePride26Midas(authenticatedModrinthUser.value?.campaigns?.pride_26)),
)

const showAd = computed(
() => sidebarVisible.value && !hasPlus.value && credentials.value !== undefined,
)

async function fetchIntercomToken() {
const creds = await getCreds()
if (!creds?.session) {
Expand Down
60 changes: 50 additions & 10 deletions apps/app-frontend/src/components/ui/settings/PrivacySettings.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
<script setup lang="ts">
import { Toggle } from '@modrinth/ui'
import { Settings2Icon } from '@modrinth/assets'
import {
ButtonStyled,
defineMessages,
injectNotificationManager,
injectPageContext,
Toggle,
useVIntl,
} from '@modrinth/ui'
import { ref, watch } from 'vue'

import { open_ads_consent_preferences } from '@/helpers/ads.js'
import { optInAnalytics, optOutAnalytics } from '@/helpers/analytics'
import { get, set } from '@/helpers/settings.ts'

const { formatMessage } = useVIntl()
const { handleError } = injectNotificationManager()
const { showAds } = injectPageContext()
const settings = ref(await get())

const messages = defineMessages({
adsConsentTitle: {
id: 'app.ads-consent.title',
defaultMessage: 'Your privacy and how ads support Modrinth',
},
adsConsentIntro: {
id: 'app.settings.privacy.ads-consent.intro',
defaultMessage:
'Ads on certain pages make Modrinth possible and fund creator payouts. Our ad provider, Aditude, may use unique identifiers to personalize ads and measure performance. You can opt out or manage your preferences below.',
},
adsConsentManage: {
id: 'app.ads-consent.manage',
defaultMessage: 'Manage preferences',
},
})

async function manageAdsPreferences() {
await open_ads_consent_preferences().catch(handleError)
}

watch(
settings,
async () => {
Expand All @@ -23,18 +55,26 @@ watch(
</script>

<template>
<div class="flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Personalized ads</h2>
<p class="m-0 mt-1 text-sm">
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.
</p>
<div v-if="showAds">
<h2 class="m-0 text-lg font-semibold text-contrast">
{{ formatMessage(messages.adsConsentTitle) }}
</h2>
<div class="mt-1 flex flex-col gap-2.5 items-start">
<div class="flex flex-col gap-1 items-start">
<div class="text-sm">
{{ formatMessage(messages.adsConsentIntro) }}
</div>
</div>
<ButtonStyled>
<button class="!shadow-none" @click="manageAdsPreferences">
<Settings2Icon aria-hidden="true" />
{{ formatMessage(messages.adsConsentManage) }}
</button>
</ButtonStyled>
</div>
<Toggle id="personalized-ads" v-model="settings.personalized_ads" />
</div>

<div class="mt-4 flex items-center justify-between gap-4">
<div class="mt-8 flex items-center justify-between gap-4">
<div>
<h2 class="m-0 text-lg font-semibold text-contrast">Telemetry</h2>
<p class="m-0 mt-1 text-sm">
Expand Down
17 changes: 17 additions & 0 deletions apps/app-frontend/src/helpers/ads.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { invoke } from '@tauri-apps/api/core'
import { listen } from '@tauri-apps/api/event'

export async function init_ads_window(overrideShown = false) {
return await invoke('plugin:ads|init_ads_window', {
Expand All @@ -15,6 +16,22 @@ export async function hide_ads_window(reset) {
return await invoke('plugin:ads|hide_ads_window', { reset })
}

export async function get_ads_consent_required() {
return await invoke('plugin:ads|get_ads_consent_required')
}

export async function perform_ads_consent_action(action) {
return await invoke('plugin:ads|perform_ads_consent_action', { action })
}

export async function open_ads_consent_preferences() {
return await invoke('plugin:ads|open_ads_consent_preferences')
}

export async function ads_consent_listener(callback) {
return await listen('ads-consent-required', (event) => callback(event.payload))
}

export async function record_ads_click() {
return await invoke('plugin:ads|record_ads_click')
}
Expand Down
18 changes: 18 additions & 0 deletions apps/app-frontend/src/locales/en-US/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@
"app.action-bar.view-logs": {
"message": "View logs"
},
"app.ads-consent.accept": {
"message": "Accept all"
},
"app.ads-consent.body": {
"message": "Ads make Modrinth possible and fund creator payouts. Our partners may store unique identifiers to personalize ads and measure performance."
},
"app.ads-consent.manage": {
"message": "Manage preferences"
},
"app.ads-consent.reject": {
"message": "Reject all"
},
"app.ads-consent.title": {
"message": "Your privacy and how ads support Modrinth"
},
"app.appearance-settings.advanced-rendering.description": {
"message": "Enables advanced rendering such as blur effects that may cause performance issues without hardware-accelerated rendering."
},
Expand Down Expand Up @@ -500,6 +515,9 @@
"app.settings.downloading": {
"message": "Downloading v{version}"
},
"app.settings.privacy.ads-consent.intro": {
"message": "Ads on certain pages make Modrinth possible and fund creator payouts. Our ad provider, Aditude, may use unique identifiers to personalize ads and measure performance. You can opt out or manage your preferences below."
},
"app.settings.tabs.appearance": {
"message": "Appearance"
},
Expand Down
5 changes: 5 additions & 0 deletions apps/app/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,12 @@ fn main() {
"scroll_ads_window",
"show_ads_window",
"show_ads_consent_overlay",
"show_ads_consent_preferences",
"open_ads_consent_preferences",
"hide_ads_consent_preferences",
"hide_ads_consent_overlay",
"get_ads_consent_required",
"perform_ads_consent_action",
"record_ads_click",
"open_link",
"get_ads_personalization",
Expand Down
Loading
Loading