Skip to content
Draft
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
2 changes: 2 additions & 0 deletions apps/frontend/src/assets/styles/global.scss
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ html {

interpolate-size: allow-keywords;
scrollbar-gutter: stable;

--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
}

.light-mode {
Expand Down
2 changes: 1 addition & 1 deletion apps/frontend/src/components/ui/Chips.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export default {
},
},
created() {
if (this.items.length > 0 && this.neverEmpty) {
if (this.items.length > 0 && this.neverEmpty && !this.modelValue) {
this.selected = this.items[0]
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<script setup lang="ts">
import { MegaphoneIcon } from '@modrinth/assets'
import {
defineMessages,
IntlFormatted,
normalizeChildren,
SettingsFormGroup,
SettingsToggleCard,
StyledInput,
useVIntl,
} from '@modrinth/ui'

import type { NoteDisclosure } from './types'

const model = defineModel<NoteDisclosure>({ required: true })

defineProps<{
disabled?: boolean
}>()

const { formatMessage } = useVIntl()

const messages = defineMessages({
title: {
id: 'project.settings.disclosures.advertising.title',
defaultMessage: 'Contains advertisements',
},
description1: {
id: 'project.settings.disclosures.advertising.description.1',
defaultMessage: `You must enable this if your project contains advertisements, sponsorships, or promotions of other works.`,
},
description2: {
id: 'project.settings.disclosures.advertising.description.2',
defaultMessage: `If the promotion has no direct monetary value <emphasis>and</emphasis> it is for something that the average person would consider <italic>relevant</italic> and <italic>unobtrusive</italic> (such as a link to your Modrinth profile in the corner of the settings page for your own mod), we would not consider that an advertisement.`,
},
noteDescription: {
id: 'project.settings.disclosures.advertising.note-description',
defaultMessage:
'Please explain how your project utilizes advertising so that users can know what to expect.',
},
notePlaceholder: {
id: 'project.settings.disclosures.advertising.note-placeholder',
defaultMessage: 'e.g. Adds the Modrinth SMP server to your server list automatically.',
},
})
</script>

<template>
<SettingsToggleCard
v-model="model.enabled"
:disabled="disabled"
:icon="MegaphoneIcon"
:title="formatMessage(messages.title)"
>
<p>{{ formatMessage(messages.description1) }}</p>
<p>
<IntlFormatted :message-id="messages.description2">
<template #italic="{ children }">
<span class="italic">
<component :is="() => normalizeChildren(children)" />
</span>
</template>
<template #emphasis="{ children }">
<span class="font-bold italic">
<component :is="() => normalizeChildren(children)" />
</span>
</template>
</IntlFormatted>
</p>
<template #expanded>
<SettingsFormGroup
:title="formatMessage(messages.noteDescription)"
title-for="advertising-disclosure-note"
>
<StyledInput
id="advertising-disclosure-note"
v-model="model.note"
multiline
:rows="3"
class="max-w-[40rem]"
:disabled="disabled"
:placeholder="formatMessage(messages.notePlaceholder)"
/>
</SettingsFormGroup>
</template>
</SettingsToggleCard>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
<script setup lang="ts">
import { SparklesIcon } from '@modrinth/assets'
import {
Checkbox,
defineMessages,
IntlFormatted,
normalizeChildren,
SettingsFormGroup,
SettingsToggleCard,
StyledInput,
useVIntl,
} from '@modrinth/ui'

import type { AiDisclosure, AiUsage } from './types'

const model = defineModel<AiDisclosure>({ required: true })

defineProps<{
disabled?: boolean
}>()

const { formatMessage } = useVIntl()

const AI_USES: AiUsage[] = ['code', 'assets', 'text', 'functionality']

const messages = defineMessages({
title: {
id: 'project.settings.disclosures.ai.title',
defaultMessage: 'Contains AI-generated content',
},
description: {
id: 'project.settings.disclosures.ai.description',
defaultMessage: `You must enable this if this project contains a substantial amount of AI-generated code, any
assets that are substantially AI-generated, the project's functionality relies on the use of
generative AI, or if any element of your project's page such as description or publishing
relies on generative AI.`,
},
contentRules: {
id: 'project.settings.disclosures.ai.content-rules',
defaultMessage:
"Please refer to Section 6 of Modrinth's <rules>Content Rules</rules> for more information.",
},
typesDescription: {
id: 'project.settings.disclosures.ai.types-description',
defaultMessage: 'Select what this project uses generative AI for.',
},
typeCode: {
id: 'project.settings.disclosures.ai.types-code',
defaultMessage: 'Code',
},
typeAssets: {
id: 'project.settings.disclosures.ai.types-assets',
defaultMessage: 'Assets',
},
typeText: {
id: 'project.settings.disclosures.ai.types-text',
defaultMessage: 'Text',
},
typeFunctionality: {
id: 'project.settings.disclosures.ai.types-functionality',
defaultMessage: 'Functionality',
},
noteDescription: {
id: 'project.settings.disclosures.ai.note-description',
defaultMessage:
'You may optionally provide a note to explain how you use generative AI in this project.',
},
notePlaceholder: {
id: 'project.settings.disclosures.ai.note-placeholder',
defaultMessage: 'e.g. The Chinese and Arabic translations are AI-generated.',
},
})

const useLabels = {
code: messages.typeCode,
assets: messages.typeAssets,
text: messages.typeText,
functionality: messages.typeFunctionality,
} as const

function hasUse(use: AiUsage): boolean {
return model.value.uses.includes(use)
}

function setUse(use: AiUsage, enabled: boolean) {
if (enabled) {
if (!model.value.uses.includes(use)) {
model.value.uses = [...model.value.uses, use]
}
return
}
model.value.uses = model.value.uses.filter((entry) => entry !== use)
}
</script>

<template>
<SettingsToggleCard
v-model="model.enabled"
:disabled="disabled"
:icon="SparklesIcon"
:title="formatMessage(messages.title)"
:description="formatMessage(messages.description)"
>
<p class="text-secondary">
<IntlFormatted :message-id="messages.contentRules">
<template #rules="{ children }">
<nuxt-link
to="/legal/rules#generative-ai"
target="_blank"
class="smart-clickable:allow-pointer-events underline hover:text-primary"
>
<component :is="() => normalizeChildren(children)" />
</nuxt-link>
</template>
</IntlFormatted>
</p>
<template #expanded>
<SettingsFormGroup :title="formatMessage(messages.typesDescription)">
<div class="grid gap-2 sm:grid-cols-4">
<Checkbox
v-for="use in AI_USES"
:key="use"
:model-value="hasUse(use)"
:disabled="disabled"
@update:model-value="(enabled) => setUse(use, enabled)"
>
{{ formatMessage(useLabels[use]) }}
</Checkbox>
</div>
</SettingsFormGroup>
<SettingsFormGroup
:title="formatMessage(messages.noteDescription)"
title-for="ai-disclosure-note"
>
<StyledInput
id="ai-disclosure-note"
v-model="model.note"
multiline
:rows="3"
class="max-w-[40rem]"
:disabled="disabled"
:placeholder="formatMessage(messages.notePlaceholder)"
/>
</SettingsFormGroup>
</template>
</SettingsToggleCard>
</template>
Loading
Loading