Skip to content

Commit e13a89d

Browse files
External projects moderator database (#5692)
* Begin external projects moderator database frontend * add copy link button * begin project page permissions settings * MEL database backend routes * include filename in external files * Hook up frontend external license page to backend * more work on user-facing external projects stuff * put user-facing stuff behind feature flag * prepr * clippy --------- Co-authored-by: aecsocket <aecsocket@tutanota.com>
1 parent 565ac2c commit e13a89d

40 files changed

Lines changed: 2098 additions & 94 deletions

apps/frontend/src/composables/featureFlags.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
4747
showDiscoverProjectButtons: false,
4848
useV1ContentTabAPI: true,
4949
labrinthApiCanary: false,
50+
dismissedExternalProjectsInfo: false,
51+
modpackPermissionsPage: false,
5052
} as const)
5153

5254
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS

apps/frontend/src/layouts/default.vue

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,11 @@
323323
color: 'orange',
324324
link: '/moderation/reports',
325325
},
326+
{
327+
id: 'external-projects',
328+
color: 'orange',
329+
link: '/moderation/external-projects',
330+
},
326331
{
327332
divider: true,
328333
},
@@ -377,6 +382,9 @@
377382
<template #review-reports>
378383
<ReportIcon aria-hidden="true" /> {{ formatMessage(messages.reports) }}
379384
</template>
385+
<template #external-projects>
386+
<GlobeIcon aria-hidden="true" /> {{ formatMessage(messages.externalProjects) }}
387+
</template>
380388
<template #user-lookup>
381389
<UserSearchIcon aria-hidden="true" /> {{ formatMessage(messages.lookupByEmail) }}
382390
</template>
@@ -705,6 +713,7 @@ import {
705713
DropdownIcon,
706714
FileIcon,
707715
GlassesIcon,
716+
GlobeIcon,
708717
HamburgerIcon,
709718
HomeIcon,
710719
IssuesIcon,
@@ -880,6 +889,10 @@ const messages = defineMessages({
880889
id: 'layout.action.reports',
881890
defaultMessage: 'Review reports',
882891
},
892+
externalProjects: {
893+
id: 'layout.action.external-projects',
894+
defaultMessage: 'External projects',
895+
},
883896
lookupByEmail: {
884897
id: 'layout.action.lookup-by-email',
885898
defaultMessage: 'Lookup by email',

apps/frontend/src/locales/en-US/index.json

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1676,6 +1676,9 @@
16761676
"layout.action.create-new": {
16771677
"message": "Create new..."
16781678
},
1679+
"layout.action.external-projects": {
1680+
"message": "External projects"
1681+
},
16791682
"layout.action.file-lookup": {
16801683
"message": "File lookup"
16811684
},
@@ -1928,14 +1931,17 @@
19281931
"moderation.moderate": {
19291932
"message": "Moderate"
19301933
},
1934+
"moderation.page.external-projects": {
1935+
"message": "External projects"
1936+
},
19311937
"moderation.page.projects": {
19321938
"message": "Projects"
19331939
},
19341940
"moderation.page.reports": {
19351941
"message": "Reports"
19361942
},
19371943
"moderation.page.technicalReview": {
1938-
"message": "Technical Review"
1944+
"message": "Tech review"
19391945
},
19401946
"muralpay.account-type.checking": {
19411947
"message": "Checking"
@@ -2621,6 +2627,45 @@
26212627
"project.settings.general.url.title": {
26222628
"message": "URL"
26232629
},
2630+
"project.settings.permissions.attention-needed.description.proj-approved": {
2631+
"message": "Please provide proof that you have permission to redistribute all of the following files and any withheld versions will be automatically published."
2632+
},
2633+
"project.settings.permissions.attention-needed.description.proj-draft": {
2634+
"message": "Please provide proof that you have permission to redistribute all of the following files before you can submit your project for review."
2635+
},
2636+
"project.settings.permissions.attention-needed.title": {
2637+
"message": "Unknown embedded content"
2638+
},
2639+
"project.settings.permissions.completed.description": {
2640+
"message": "All external content has attributions provided."
2641+
},
2642+
"project.settings.permissions.completed.title": {
2643+
"message": "Attributions completed!"
2644+
},
2645+
"project.settings.permissions.empty-state.description": {
2646+
"message": "None of your versions contain external content, so you don't need to worry about obtaining permissions."
2647+
},
2648+
"project.settings.permissions.empty-state.heading": {
2649+
"message": "You're all set!"
2650+
},
2651+
"project.settings.permissions.fail.description": {
2652+
"message": "You don't have permission to redistribute some of the external content you've added. In order to publish on Modrinth, remove the infringing content."
2653+
},
2654+
"project.settings.permissions.fail.title": {
2655+
"message": "Some content can't be included"
2656+
},
2657+
"project.settings.permissions.info-banner.description": {
2658+
"message": "If you include content that isn’t hosted on Modrinth, you need to let us know where it’s from and verify that you have permission to distribute the files. Check out <link>our guide</link> to learn about how to do this properly!"
2659+
},
2660+
"project.settings.permissions.info-banner.title": {
2661+
"message": "Learn how attributions work"
2662+
},
2663+
"project.settings.permissions.learn-more": {
2664+
"message": "Learn more"
2665+
},
2666+
"project.settings.permissions.search-placeholder": {
2667+
"message": "Search {count} {count, plural, one {external project} other {external projects}}..."
2668+
},
26242669
"project.settings.title": {
26252670
"message": "Settings"
26262671
},
@@ -2639,6 +2684,15 @@
26392684
"project.versions.title": {
26402685
"message": "Versions"
26412686
},
2687+
"project.versions.withheld-versions-warning.description": {
2688+
"message": "{count, plural, one {This version is} other {These versions are}} currently withheld and not publicly listed. Please provide proof that you have permission to redistribute certain files included in the modpack {count, plural, one {version} other {versions}}."
2689+
},
2690+
"project.versions.withheld-versions-warning.resolve-button": {
2691+
"message": "Resolve"
2692+
},
2693+
"project.versions.withheld-versions-warning.title": {
2694+
"message": "{count, plural, one {Version {version_name}} other {Versions}} withheld due to unknown embedded content"
2695+
},
26422696
"report.already-reported": {
26432697
"message": "You've already reported {title}"
26442698
},

apps/frontend/src/pages/[type]/[id]/settings.vue

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
InfoIcon,
99
LinkIcon,
1010
ServerIcon,
11+
SignatureIcon,
1112
TagsIcon,
1213
UsersIcon,
1314
VersionIcon,
@@ -46,6 +47,12 @@ const navItems = computed(() => {
4647
projectV3.value?.project_types?.some((type) => ['mod', 'modpack'].includes(type)) &&
4748
isStaff(currentMember.value?.user)
4849
50+
const hasPermissionsPage = computed(
51+
() =>
52+
flags.value.modpackPermissionsPage &&
53+
projectV3.value?.project_types?.some((type) => ['modpack'].includes(type)),
54+
)
55+
4956
const items = [
5057
{
5158
link: `/${base}/settings`,
@@ -75,6 +82,11 @@ const navItems = computed(() => {
7582
label: formatMessage(commonProjectSettingsMessages.description),
7683
icon: AlignLeftIcon,
7784
},
85+
hasPermissionsPage.value && {
86+
link: `/${base}/settings/permissions`,
87+
label: formatMessage(commonProjectSettingsMessages.permissions),
88+
icon: SignatureIcon,
89+
},
7890
!isServerProject.value && {
7991
link: `/${base}/settings/versions`,
8092
label: formatMessage(commonProjectSettingsMessages.versions),
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
<script setup lang="ts">
2+
import { RightArrowIcon, SearchIcon, SortAscIcon, SortDescIcon } from '@modrinth/assets'
3+
import {
4+
Admonition,
5+
ButtonStyled,
6+
Combobox,
7+
type ComboboxOption,
8+
commonMessages,
9+
defineMessages,
10+
EmptyState,
11+
IntlFormatted,
12+
StyledInput,
13+
useVIntl,
14+
} from '@modrinth/ui'
15+
import ExternalProjectPermissionsCard from '@modrinth/ui/src/components/external_files/ExternalProjectPermissionsCard.vue'
16+
import { ref } from 'vue'
17+
18+
const { formatMessage } = useVIntl()
19+
const flags = useFeatureFlags()
20+
21+
if (!flags.value.modpackPermissionsPage) {
22+
throw createError({
23+
fatal: true,
24+
statusCode: 404,
25+
})
26+
}
27+
28+
const externalFiles = ref([{}])
29+
const searchQuery = ref('')
30+
const currentSortType = ref('Oldest')
31+
32+
const sortTypes: ComboboxOption<string>[] = [
33+
{ value: 'Oldest', label: 'Oldest' },
34+
{ value: 'Newest', label: 'Newest' },
35+
]
36+
const messages = defineMessages({
37+
searchPlaceholder: {
38+
id: 'project.settings.permissions.search-placeholder',
39+
defaultMessage:
40+
'Search {count} {count, plural, one {external project} other {external projects}}...',
41+
},
42+
infoBannerTitle: {
43+
id: 'project.settings.permissions.info-banner.title',
44+
defaultMessage: 'Learn how attributions work',
45+
},
46+
infoBannerDescription: {
47+
id: 'project.settings.permissions.info-banner.description',
48+
defaultMessage: `If you include content that isn’t hosted on Modrinth, you need to let us know where it’s from and verify that you have permission to distribute the files. Check out <link>our guide</link> to learn about how to do this properly!`,
49+
},
50+
learnMore: {
51+
id: 'project.settings.permissions.learn-more',
52+
defaultMessage: 'Learn more',
53+
},
54+
emptyStateHeading: {
55+
id: 'project.settings.permissions.empty-state.heading',
56+
defaultMessage: `You're all set!`,
57+
},
58+
emptyStateDescription: {
59+
id: 'project.settings.permissions.empty-state.description',
60+
defaultMessage: `None of your versions contain external content, so you don't need to worry about obtaining permissions.`,
61+
},
62+
completedTitle: {
63+
id: 'project.settings.permissions.completed.title',
64+
defaultMessage: `Attributions completed!`,
65+
},
66+
completedDescription: {
67+
id: 'project.settings.permissions.completed.description',
68+
defaultMessage: 'All external content has attributions provided.',
69+
},
70+
failTitle: {
71+
id: 'project.settings.permissions.fail.title',
72+
defaultMessage: `Some content can't be included`,
73+
},
74+
failDescription: {
75+
id: 'project.settings.permissions.fail.description',
76+
defaultMessage: `You don't have permission to redistribute some of the external content you've added. In order to publish on Modrinth, remove the infringing content.`,
77+
},
78+
attentionNeededTitle: {
79+
id: 'project.settings.permissions.attention-needed.title',
80+
defaultMessage: `Unknown embedded content`,
81+
},
82+
attentionNeededDescriptionApproved: {
83+
id: 'project.settings.permissions.attention-needed.description.proj-approved',
84+
defaultMessage: `Please provide proof that you have permission to redistribute all of the following files and any withheld versions will be automatically published.`,
85+
},
86+
attentionNeededDescriptionDraft: {
87+
id: 'project.settings.permissions.attention-needed.description.proj-draft',
88+
defaultMessage: `Please provide proof that you have permission to redistribute all of the following files before you can submit your project for review.`,
89+
},
90+
})
91+
92+
function dismissInfoBanner() {
93+
flags.value.dismissedExternalProjectsInfo = true
94+
saveFeatureFlags()
95+
}
96+
</script>
97+
<template>
98+
<template v-if="externalFiles.length > 0">
99+
<Admonition
100+
v-if="!flags.dismissedExternalProjectsInfo"
101+
type="info"
102+
class="mb-4"
103+
:header="formatMessage(messages.infoBannerTitle)"
104+
dismissible
105+
@dismiss="dismissInfoBanner"
106+
>
107+
<IntlFormatted :message-id="messages.infoBannerDescription">
108+
<template #link="{ children }">
109+
<a class="text-link" target="_blank"> <component :is="() => children" /> </a>
110+
</template>
111+
</IntlFormatted>
112+
<template #actions>
113+
<div class="flex">
114+
<ButtonStyled color="blue">
115+
<a> {{ formatMessage(messages.learnMore) }} <RightArrowIcon /> </a>
116+
</ButtonStyled>
117+
</div>
118+
</template>
119+
</Admonition>
120+
<Admonition
121+
v-if="true"
122+
type="success"
123+
class="mb-4"
124+
:header="formatMessage(messages.completedTitle)"
125+
:body="formatMessage(messages.completedDescription)"
126+
/>
127+
<Admonition
128+
v-if="true"
129+
type="warning"
130+
class="mb-4"
131+
:header="formatMessage(messages.attentionNeededTitle)"
132+
:body="formatMessage(messages.attentionNeededDescriptionDraft)"
133+
/>
134+
<Admonition
135+
v-if="true"
136+
type="critical"
137+
class="mb-4"
138+
:header="formatMessage(messages.failTitle)"
139+
:body="formatMessage(messages.failDescription)"
140+
/>
141+
<div class="grid grid-cols-[1fr_auto] gap-2">
142+
<StyledInput
143+
v-model="searchQuery"
144+
type="search"
145+
:placeholder="
146+
formatMessage(messages.searchPlaceholder, {
147+
count: externalFiles.length,
148+
})
149+
"
150+
:icon="SearchIcon"
151+
input-class="h-[40px]"
152+
/>
153+
<div>
154+
<Combobox
155+
v-model="currentSortType"
156+
class="!w-full flex-grow sm:!w-[150px] sm:flex-grow-0 lg:!w-[150px]"
157+
:options="sortTypes"
158+
:placeholder="formatMessage(commonMessages.sortByLabel)"
159+
>
160+
<template #selected>
161+
<span class="flex flex-row gap-2 align-middle font-semibold">
162+
<SortAscIcon
163+
v-if="currentSortType === 'Oldest'"
164+
class="size-5 flex-shrink-0 text-secondary"
165+
/>
166+
<SortDescIcon v-else class="size-5 flex-shrink-0 text-secondary" />
167+
<span class="truncate text-contrast">{{ currentSortType }}</span>
168+
</span>
169+
</template>
170+
</Combobox>
171+
</div>
172+
</div>
173+
<div class="mt-4 flex flex-col gap-3">
174+
<ExternalProjectPermissionsCard title="FTB Library" />
175+
</div>
176+
</template>
177+
<template v-else>
178+
<EmptyState
179+
:heading="formatMessage(messages.emptyStateHeading)"
180+
:description="formatMessage(messages.emptyStateDescription)"
181+
type="done"
182+
/>
183+
</template>
184+
</template>

0 commit comments

Comments
 (0)