Skip to content

Commit 53ec2c5

Browse files
authored
Handle project type on per version basis for multi-type projects (#4945)
* infer project type by draft version loader * fix detecting modpack project type when editing * fix no loaders check * pnpm run fix
1 parent cace1a5 commit 53ec2c5

File tree

4 files changed

+108
-85
lines changed

4 files changed

+108
-85
lines changed

apps/frontend/src/components/ui/create-project-version/CreateProjectVersionModal.vue

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const modal = useTemplateRef<ComponentExposed<typeof MultiStageModal>>('modal')
2222
const ctx = createManageVersionContext(modal)
2323
provideManageVersionContext(ctx)
2424
25-
const { newDraftVersion, setProjectType } = ctx
25+
const { newDraftVersion } = ctx
2626
2727
const { projectV2 } = injectProjectPageContext()
2828
const { addNotification } = injectNotificationManager()
@@ -62,7 +62,6 @@ function openCreateVersionModal(
6262
stageId: string | null = null,
6363
) {
6464
newDraftVersion(projectV2.value.id, version)
65-
setProjectType(projectV2.value)
6665
modal.value?.setStage(stageId ?? 0)
6766
modal.value?.show()
6867
}

apps/frontend/src/components/ui/create-project-version/stages/AddFilesStage.vue

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,7 @@ const {
103103
existingFilesToDelete,
104104
setPrimaryFile,
105105
setInferredVersionData,
106-
setProjectType,
107106
editingVersion,
108-
projectType,
109107
} = injectManageVersionContext()
110108
111109
const addDetectedData = async () => {
@@ -125,17 +123,9 @@ const addDetectedData = async () => {
125123
...draftVersion.value,
126124
...mappedInferredData,
127125
}
128-
129-
setProjectType(projectV2.value, primaryFile)
130126
} catch (err) {
131127
console.error('Error parsing version file data', err)
132128
}
133-
134-
if (projectType.value === 'resourcepack') {
135-
draftVersion.value.loaders = ['minecraft']
136-
} else if (projectType.value === 'modpack' && draftVersion.value.loaders?.length === 0) {
137-
draftVersion.value.loaders = ['minecraft']
138-
}
139129
}
140130
141131
// add detected data when the primary file changes

apps/frontend/src/components/ui/create-project-version/stages/AddLoadersStage.vue

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242

4343
<script lang="ts" setup>
4444
import { XIcon } from '@modrinth/assets'
45-
import { ButtonStyled, injectProjectPageContext, TagItem } from '@modrinth/ui'
45+
import { ButtonStyled, TagItem } from '@modrinth/ui'
4646
import { formatCategory } from '@modrinth/utils'
4747
4848
import { injectManageVersionContext } from '~/providers/version/manage-version-modal'
@@ -51,22 +51,19 @@ import LoaderPicker from '../components/LoaderPicker.vue'
5151
5252
const generatedState = useGeneratedState()
5353
54-
const { projectV2 } = injectProjectPageContext()
5554
const loaders = computed(() => generatedState.value.loaders)
5655
57-
const { draftVersion, setProjectType } = injectManageVersionContext()
56+
const { draftVersion } = injectManageVersionContext()
5857
5958
const toggleLoader = (loader: string) => {
6059
if (draftVersion.value.loaders.includes(loader)) {
6160
draftVersion.value.loaders = draftVersion.value.loaders.filter((l) => l !== loader)
6261
} else {
6362
draftVersion.value.loaders = [...draftVersion.value.loaders, loader]
6463
}
65-
setProjectType(projectV2.value)
6664
}
6765
6866
const onClearAll = () => {
6967
draftVersion.value.loaders = []
70-
setProjectType(projectV2.value)
7168
}
7269
</script>

apps/frontend/src/providers/version/manage-version-modal.ts

Lines changed: 105 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,6 @@ export interface ManageVersionContextValue {
8686
file: File,
8787
project: Labrinth.Projects.v2.Project,
8888
) => Promise<InferredVersionInfo>
89-
setProjectType: (
90-
project: Labrinth.Projects.v2.Project,
91-
file?: File | null,
92-
) => Promise<Labrinth.Projects.v2.ProjectType | undefined>
9389
getProject: (projectId: string) => Promise<Labrinth.Projects.v3.Project>
9490
getVersion: (versionId: string) => Promise<Labrinth.Versions.v3.Version>
9591

@@ -98,6 +94,41 @@ export interface ManageVersionContextValue {
9894
handleSaveVersionEdits: () => Promise<void>
9995
}
10096

97+
const PROJECT_TYPE_LOADERS: Record<string, readonly string[]> = {
98+
mod: [
99+
'fabric',
100+
'neoforge',
101+
'forge',
102+
'quilt',
103+
'liteloader',
104+
'rift',
105+
'ornithe',
106+
'nilloader',
107+
'risugami',
108+
'legacy-fabric',
109+
'bta-babric',
110+
'babric',
111+
'modloader',
112+
'java-agent',
113+
],
114+
shader: ['optifine', 'iris', 'canvas', 'vanilla'],
115+
plugin: [
116+
'paper',
117+
'purpur',
118+
'spigot',
119+
'bukkit',
120+
'sponge',
121+
'folia',
122+
'bungeecord',
123+
'velocity',
124+
'waterfall',
125+
'geyser',
126+
],
127+
datapack: ['datapack'],
128+
resourcepack: ['minecraft'],
129+
modpack: ['mrpack'],
130+
} as const
131+
101132
export const [injectManageVersionContext, provideManageVersionContext] =
102133
createContext<ManageVersionContextValue>('CreateProjectVersionModal')
103134

@@ -113,79 +144,46 @@ export function createManageVersionContext(
113144
const filesToAdd = ref<Labrinth.Versions.v3.DraftVersionFile[]>([])
114145
const existingFilesToDelete = ref<Labrinth.Versions.v3.VersionFileHash['sha1'][]>([])
115146
const inferredVersionData = ref<InferredVersionInfo>()
116-
const projectType = ref<Labrinth.Projects.v2.ProjectType>()
117147
const dependencyProjects = ref<Record<string, Labrinth.Projects.v3.Project>>({})
118148
const dependencyVersions = ref<Record<string, Labrinth.Versions.v3.Version>>({})
119149
const isSubmitting = ref(false)
120150

121-
// Computed state
122-
const editingVersion = computed(() => Boolean(draftVersion.value.version_id))
123-
124-
// Helper functions for project type detection
125-
// TODO: move to infer.js
126-
async function setProjectType(
127-
project: Labrinth.Projects.v2.Project,
128-
file: File | null = null,
129-
): Promise<Labrinth.Projects.v2.ProjectType | undefined> {
130-
if (project.project_type && project.project_type !== 'project') {
131-
projectType.value = project.project_type
132-
return projectType.value
133-
}
134-
151+
const projectType = computed<Labrinth.Projects.v2.ProjectType>(() => {
152+
const primaryFile = filesToAdd.value[0]?.file
135153
if (
136-
(file && file.name.toLowerCase().endsWith('.mrpack')) ||
137-
(file && file.name.toLowerCase().endsWith('.mrpack-primary'))
154+
(primaryFile && primaryFile.name.toLowerCase().endsWith('.mrpack')) ||
155+
(primaryFile && primaryFile.name.toLowerCase().endsWith('.mrpack-primary'))
138156
) {
139-
projectType.value = 'modpack'
140-
return projectType.value
157+
return 'modpack'
141158
}
142159

143-
if (
144-
draftVersion.value.loaders?.some((loader) =>
145-
[
146-
'fabric',
147-
'neoforge',
148-
'forge',
149-
'quilt',
150-
'liteloader',
151-
'rift',
152-
'ornithe',
153-
'nilloader',
154-
'legacy-fabric',
155-
'bta-babric',
156-
'babric',
157-
'modloader',
158-
'java-agent',
159-
].includes(loader),
160-
)
161-
) {
162-
projectType.value = 'mod'
163-
return projectType.value
160+
const loaders = draftVersion.value.loaders || []
161+
162+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.modpack.includes(loader))) {
163+
return 'modpack'
164164
}
165165

166-
try {
167-
if (file) {
168-
const jszip = await JSZip.loadAsync(file)
169-
170-
const hasMcmeta = Object.keys(jszip.files).some(
171-
(f) => f.toLowerCase() === 'pack.mcmeta' || f.toLowerCase().endsWith('/pack.mcmeta'),
172-
)
173-
const hasAssetsDir = Object.keys(jszip.files).some(
174-
(f) => f.toLowerCase() === 'assets/' || f.toLowerCase().startsWith('assets/'),
175-
)
176-
177-
if (hasMcmeta && hasAssetsDir) {
178-
projectType.value = 'resourcepack'
179-
return projectType.value
180-
}
181-
}
182-
} catch {
183-
// not a zip
166+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.datapack.includes(loader))) {
167+
return 'datapack'
168+
}
169+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.resourcepack.includes(loader))) {
170+
return 'resourcepack'
171+
}
172+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.shader.includes(loader))) {
173+
return 'shader'
174+
}
175+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.plugin.includes(loader))) {
176+
return 'plugin'
177+
}
178+
if (loaders.some((loader) => PROJECT_TYPE_LOADERS.mod.includes(loader))) {
179+
return 'mod'
184180
}
185181

186-
projectType.value = undefined
187-
return undefined
188-
}
182+
return 'project'
183+
})
184+
185+
// Computed state
186+
const editingVersion = computed(() => Boolean(draftVersion.value.version_id))
189187

190188
// Version management methods
191189
function newDraftVersion(
@@ -197,7 +195,7 @@ export function createManageVersionContext(
197195
filesToAdd.value = []
198196
existingFilesToDelete.value = []
199197
inferredVersionData.value = undefined
200-
projectType.value = undefined
198+
// projectType.value = undefined
201199
}
202200

203201
function setPrimaryFile(index: number) {
@@ -210,6 +208,33 @@ export function createManageVersionContext(
210208

211209
const tags = useGeneratedState()
212210

211+
const hasFile = (entries: string[], name: string) =>
212+
entries.some((f) => f === name || f.endsWith(`/${name}`))
213+
214+
const hasDir = (entries: string[], dir: string) => entries.some((f) => f.startsWith(`${dir}/`))
215+
216+
async function checkIsResourcePack(file: File): Promise<boolean> {
217+
try {
218+
const zip = await JSZip.loadAsync(file)
219+
const entries = Object.keys(zip.files).map((f) => f.toLowerCase())
220+
221+
return hasFile(entries, 'pack.mcmeta') && hasDir(entries, 'assets')
222+
} catch {
223+
return false
224+
}
225+
}
226+
227+
async function checkIsDataPack(file: File): Promise<boolean> {
228+
try {
229+
const zip = await JSZip.loadAsync(file)
230+
const entries = Object.keys(zip.files).map((f) => f.toLowerCase())
231+
232+
return hasFile(entries, 'pack.mcmeta') && hasDir(entries, 'data')
233+
} catch {
234+
return false
235+
}
236+
}
237+
213238
async function setInferredVersionData(
214239
file: File,
215240
project: Labrinth.Projects.v2.Project,
@@ -234,8 +259,21 @@ export function createManageVersionContext(
234259
console.error('Error fetching versions for environment inference:', error)
235260
}
236261

262+
const noLoaders = !inferred.loaders?.length
263+
264+
if (noLoaders && (await checkIsResourcePack(file))) {
265+
inferred.loaders = ['minecraft']
266+
}
267+
268+
if (noLoaders && (await checkIsDataPack(file))) {
269+
inferred.loaders = ['datapack']
270+
}
271+
272+
if (noLoaders && projectType.value === 'modpack') {
273+
inferred.loaders = ['minecraft']
274+
}
275+
237276
inferredVersionData.value = inferred
238-
projectType.value = await setProjectType(project, file)
239277

240278
return inferred
241279
}
@@ -423,7 +461,6 @@ export function createManageVersionContext(
423461
newDraftVersion,
424462
setPrimaryFile,
425463
setInferredVersionData,
426-
setProjectType,
427464
getProject,
428465
getVersion,
429466
handleCreateVersion,

0 commit comments

Comments
 (0)