From 61c8cd75cd5b498fc2c46d923f120835bf6ac3b6 Mon Sep 17 00:00:00 2001 From: Truman Gao <106889354+tdgao@users.noreply.github.com> Date: Mon, 12 Jan 2026 12:41:14 -0700 Subject: [PATCH] feat: manage project versions v2 (#5049) * update add files copy and go to next step on just one file * rename and reorder stages * add metadata stage and update details stage * implement files inside metadata stage * use regular prettier instead of prettier eslint * remove changelog stage config * save button on details stage * update edit buttons in versions table * add collapse environment selector * implement dependencies list in metadata step * move dependencies into provider * add suggested dependencies to metadata stage * pnpm prepr * fix unused var * Revert "add collapse environment selector" This reverts commit f90fabc7a57ff201f26e1b628eeced8e6ef75865. * hide resource pack loader only when its the only loader * fix no dependencies for modpack * add breadcrumbs with hide breadcrumb option * wider stages * add proper horizonal scroll breadcrumbs * fix titles * handle save version in version page * remove box shadow * add notification provider to storybook * add drop area for versions to drop file right into page * fix mobile versions table buttons overflowing * pnpm prepr * fix drop file opening modal in wrong stage * implement invalid file for dropping files * allow horizontal scroll on breadcrumbs * update infer.js as best as possible * add create version button uploading version state * add extractVersionFromFilename for resource pack and datapack * allow jars for datapack project * detect multiple loaders when possible * iris means compatible with optifine too * infer environment on loader change as well * add tooltip * prevent navigate forward when cannot go to next step * larger breadcrumb click targets * hide loaders and mc versions stage until files added * fix max width in header * fix add files from metadata step jumping steps * define width in NewModal instead * disable remove dependency in metadata stage * switch metadata and details buttons positions * fix remove button spacing * do not allow duplicate suggested dependencies * fix version detection for fabric minecraft version semvar * better verion number detection based on filename * show resource pack loader but uneditable * remove vanilla shader detection * refactor: break up large infer.js into ts and modules * remove duplicated types * add fill missing from file name step * pnpm prepr * fix neoforge loader parse failing and not adding neoforge loader * add missing pack formats * handle new pack format * pnpm prepr * add another regex where it is version in anywhere in filename * only show resource pack or data pack options for filetype on datapack project * add redundant zip folder check * reject RP and DP if has redundant folder * fix hide stage in breadcrumb * add snapshot group key in case no release version. brings out 26.1 snapshots * pnpm prepr * open in group if has something selected * fix resource pack loader uneditable if accidentally selected on different project type * add new environment tags * add unknown and not applicable environment tags * pnpm prepr * use shared constant on labels * use ref for timeout * remove console logs * remove box shadow only for cm-content * feat: xhr upload + fix wrangler prettierignore * fix: upload content type fix * fix dependencies version width * fix already added dependencies logic * add changelog minheight * set progress percentage on button * add legacy fabric detection logic * lint * small update on create version button label --------- Co-authored-by: Calum H. (IMB11) Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com> --- .vscode/settings.json | 12 +- apps/frontend/.prettierignore | 1 + apps/frontend/package.json | 2 + .../CreateProjectVersionModal.vue | 35 +- .../components/AddedDependencyRow.vue | 14 +- .../components/DependenciesList.vue | 57 ++ .../components/LoaderPicker.vue | 13 + .../components/McVersionPicker.vue | 35 +- .../SuggestedDependencies.vue | 66 +-- .../SuggestedDependency.vue | 6 +- .../components/VersionFileRow.vue | 41 +- .../components/ViewOnlyFileRow.vue | 32 ++ .../stages/AddChangelogStage.vue | 23 - .../stages/AddDetailsStage.vue | 272 --------- .../stages/AddFilesStage.vue | 109 +--- ...denciesStage.vue => DependenciesStage.vue} | 153 +----- .../stages/DetailsStage.vue | 65 +++ ...ironmentStage.vue => EnvironmentStage.vue} | 0 .../{AddLoadersStage.vue => LoadersStage.vue} | 2 +- ...cVersionsStage.vue => McVersionsStage.vue} | 2 +- .../stages/MetadataStage.vue | 380 +++++++++++++ apps/frontend/src/helpers/infer.js | 514 ------------------ apps/frontend/src/helpers/infer/constants.ts | 123 +++++ apps/frontend/src/helpers/infer/index.ts | 3 + apps/frontend/src/helpers/infer/infer.ts | 132 +++++ .../src/helpers/infer/loader-parsers.ts | 268 +++++++++ .../src/helpers/infer/multi-file-detectors.ts | 131 +++++ .../src/helpers/infer/pack-parsers.ts | 266 +++++++++ .../src/helpers/infer/version-ranges.ts | 87 +++ .../src/helpers/infer/version-utils.ts | 57 ++ .../pages/[type]/[id]/settings/versions.vue | 41 +- .../pages/[type]/[id]/version/[version].vue | 38 +- .../src/pages/[type]/[id]/versions.vue | 47 +- .../providers/version/manage-version-modal.ts | 421 ++++++++++++-- .../version/stages/add-files-stage.ts | 100 ++++ .../src/providers/version/stages/add-files.ts | 56 -- .../{add-details.ts => dependencies-stage.ts} | 39 +- .../{add-changelog.ts => details-stage.ts} | 17 +- ...dd-environment.ts => environment-stage.ts} | 16 +- .../src/providers/version/stages/index.ts | 53 +- .../{add-loaders.ts => loaders-stage.ts} | 20 +- ...dd-mc-versions.ts => mc-versions-stage.ts} | 19 +- ...{add-dependencies.ts => metadata-stage.ts} | 14 +- .../api-client/src/core/abstract-client.ts | 26 +- .../src/core/abstract-upload-client.ts | 4 +- .../src/modules/labrinth/versions/v3.ts | 31 +- .../src/platform/xhr-upload-client.ts | 18 +- packages/api-client/src/types/upload.ts | 55 +- packages/assets/generated-icons.ts | 2 + packages/assets/icons/file-plus.svg | 18 + packages/assets/styles/defaults.scss | 1 + packages/ui/.storybook/preview.ts | 62 +++ packages/ui/src/components/base/DropArea.vue | 73 ++- .../src/components/base/DropzoneFileInput.vue | 49 +- .../ui/src/components/base/MarkdownEditor.vue | 6 +- .../src/components/base/MultiStageModal.vue | 164 +++++- packages/ui/src/components/modal/NewModal.vue | 19 +- .../project/ProjectPageVersions.vue | 34 +- .../project/ProjectSidebarCompatibility.vue | 90 +-- .../settings/environment/environments.ts | 116 +++- packages/ui/src/locales/en-US/index.json | 51 +- .../ui/src/stories/base/DropArea.stories.ts | 38 +- packages/utils/utils.ts | 2 +- pnpm-lock.yaml | 253 ++++----- 64 files changed, 3185 insertions(+), 1709 deletions(-) create mode 100644 apps/frontend/src/components/ui/create-project-version/components/DependenciesList.vue create mode 100644 apps/frontend/src/components/ui/create-project-version/components/ViewOnlyFileRow.vue delete mode 100644 apps/frontend/src/components/ui/create-project-version/stages/AddChangelogStage.vue delete mode 100644 apps/frontend/src/components/ui/create-project-version/stages/AddDetailsStage.vue rename apps/frontend/src/components/ui/create-project-version/stages/{AddDependenciesStage.vue => DependenciesStage.vue} (55%) create mode 100644 apps/frontend/src/components/ui/create-project-version/stages/DetailsStage.vue rename apps/frontend/src/components/ui/create-project-version/stages/{AddEnvironmentStage.vue => EnvironmentStage.vue} (100%) rename apps/frontend/src/components/ui/create-project-version/stages/{AddLoadersStage.vue => LoadersStage.vue} (98%) rename apps/frontend/src/components/ui/create-project-version/stages/{AddMcVersionsStage.vue => McVersionsStage.vue} (97%) create mode 100644 apps/frontend/src/components/ui/create-project-version/stages/MetadataStage.vue delete mode 100644 apps/frontend/src/helpers/infer.js create mode 100644 apps/frontend/src/helpers/infer/constants.ts create mode 100644 apps/frontend/src/helpers/infer/index.ts create mode 100644 apps/frontend/src/helpers/infer/infer.ts create mode 100644 apps/frontend/src/helpers/infer/loader-parsers.ts create mode 100644 apps/frontend/src/helpers/infer/multi-file-detectors.ts create mode 100644 apps/frontend/src/helpers/infer/pack-parsers.ts create mode 100644 apps/frontend/src/helpers/infer/version-ranges.ts create mode 100644 apps/frontend/src/helpers/infer/version-utils.ts create mode 100644 apps/frontend/src/providers/version/stages/add-files-stage.ts delete mode 100644 apps/frontend/src/providers/version/stages/add-files.ts rename apps/frontend/src/providers/version/stages/{add-details.ts => dependencies-stage.ts} (50%) rename apps/frontend/src/providers/version/stages/{add-changelog.ts => details-stage.ts} (67%) rename apps/frontend/src/providers/version/stages/{add-environment.ts => environment-stage.ts} (74%) rename apps/frontend/src/providers/version/stages/{add-loaders.ts => loaders-stage.ts} (66%) rename apps/frontend/src/providers/version/stages/{add-mc-versions.ts => mc-versions-stage.ts} (72%) rename apps/frontend/src/providers/version/stages/{add-dependencies.ts => metadata-stage.ts} (66%) create mode 100644 packages/assets/icons/file-plus.svg diff --git a/.vscode/settings.json b/.vscode/settings.json index 243953fd..d4d338f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -11,21 +11,21 @@ "source.fixAll.eslint": "explicit", "source.organizeImports": "always" }, - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint", + "editor.defaultFormatter": "esbenp.prettier-vscode", "[vue]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[javascript]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[scss]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[css]": { - "editor.defaultFormatter": "rvest.vs-code-prettier-eslint" + "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[rust]": { "editor.defaultFormatter": "rust-lang.rust-analyzer" diff --git a/apps/frontend/.prettierignore b/apps/frontend/.prettierignore index 85ab0202..bd963877 100644 --- a/apps/frontend/.prettierignore +++ b/apps/frontend/.prettierignore @@ -2,6 +2,7 @@ **/dist **/.output **/.data +**/.wrangler src/generated/** src/locales/** src/public/news/feed diff --git a/apps/frontend/package.json b/apps/frontend/package.json index 29173f4a..fe4430f0 100644 --- a/apps/frontend/package.json +++ b/apps/frontend/package.json @@ -21,7 +21,9 @@ "@nuxtjs/i18n": "^9.0.0", "@types/dompurify": "^3.0.5", "@types/iso-3166-2": "^1.0.4", + "@types/js-yaml": "^4.0.9", "@types/node": "^20.1.0", + "@types/semver": "^7.7.1", "autoprefixer": "^10.4.19", "glob": "^10.2.7", "nuxt": "^3.20.2", diff --git a/apps/frontend/src/components/ui/create-project-version/CreateProjectVersionModal.vue b/apps/frontend/src/components/ui/create-project-version/CreateProjectVersionModal.vue index 98fa4f86..c918fe97 100644 --- a/apps/frontend/src/components/ui/create-project-version/CreateProjectVersionModal.vue +++ b/apps/frontend/src/components/ui/create-project-version/CreateProjectVersionModal.vue @@ -1,15 +1,28 @@ diff --git a/apps/frontend/src/components/ui/create-project-version/components/LoaderPicker.vue b/apps/frontend/src/components/ui/create-project-version/components/LoaderPicker.vue index d20af864..7a037fd1 100644 --- a/apps/frontend/src/components/ui/create-project-version/components/LoaderPicker.vue +++ b/apps/frontend/src/components/ui/create-project-version/components/LoaderPicker.vue @@ -118,4 +118,17 @@ function groupLoaders(loaders: Labrinth.Tags.v2.Loader[]) { } const groupedLoaders = computed(() => groupLoaders(loaders)) + +onMounted(() => { + if (selectedLoaders.value.length === 0) return + + // Find the first group that contains any of the selected loaders + const groups = groupedLoaders.value + for (const [groupName, loadersInGroup] of Object.entries(groups)) { + if (loadersInGroup.some((loader) => selectedLoaders.value.includes(loader.name))) { + loaderGroup.value = groupName as GroupLabels + break + } + } +}) diff --git a/apps/frontend/src/components/ui/create-project-version/components/McVersionPicker.vue b/apps/frontend/src/components/ui/create-project-version/components/McVersionPicker.vue index 575617a6..8387f723 100644 --- a/apps/frontend/src/components/ui/create-project-version/components/McVersionPicker.vue +++ b/apps/frontend/src/components/ui/create-project-version/components/McVersionPicker.vue @@ -66,7 +66,7 @@ import type { Labrinth } from '@modrinth/api-client' import { SearchIcon } from '@modrinth/assets' import { ButtonStyled, Chips } from '@modrinth/ui' import { useMagicKeys } from '@vueuse/core' -import { computed, ref } from 'vue' +import { computed, nextTick, onMounted, ref } from 'vue' type GameVersion = Labrinth.Tags.v2.GameVersion @@ -147,9 +147,15 @@ function groupVersions(gameVersions: GameVersion[]) { ) const getGroupKey = (v: string) => v.split('.').slice(0, 2).join('.') + + const getSnapshotGroupKey = (v: string) => { + const cleanVersion = v.split('-')[0] + return cleanVersion.split('.').slice(0, 2).join('.') + } + const groups: Record = {} - let currentGroupKey = getGroupKey(gameVersions.find((v) => v.major)?.version || '') + let currentGroupKey = getSnapshotGroupKey(gameVersions.find((v) => v.major)?.version || '') gameVersions.forEach((gameVersion) => { if (gameVersion.version_type === 'release') { @@ -157,6 +163,8 @@ function groupVersions(gameVersions: GameVersion[]) { if (!groups[currentGroupKey]) groups[currentGroupKey] = [] groups[currentGroupKey].push(gameVersion.version) } else { + if (!currentGroupKey) currentGroupKey = getSnapshotGroupKey(gameVersion.version) + const key = `${currentGroupKey} ${DEV_RELEASE_KEY}` if (!groups[key]) groups[key] = [] groups[key].push(gameVersion.version) @@ -205,4 +213,27 @@ function compareGroupKeys(a: string, b: string) { function searchFilter(gameVersion: Labrinth.Tags.v2.GameVersion) { return gameVersion.version.toLowerCase().includes(searchQuery.value.toLowerCase()) } + +onMounted(async () => { + if (props.modelValue.length === 0) return + + // Open non-release tab if any non-release versions are selected + const hasNonReleaseVersions = props.gameVersions.some( + (v) => props.modelValue.includes(v.version) && v.version_type !== 'release', + ) + + if (hasNonReleaseVersions) { + versionType.value = 'all' + } + + await nextTick() + const firstSelectedVersion = allVersionsFlat.value.find((v) => props.modelValue.includes(v)) + if (firstSelectedVersion) { + const buttons = Array.from(document.querySelectorAll('button')) + const element = buttons.find((btn) => btn.textContent?.trim() === firstSelectedVersion) + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'center' }) + } + } +}) diff --git a/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependencies.vue b/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependencies.vue index f92ed383..0220e2c6 100644 --- a/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependencies.vue +++ b/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependencies.vue @@ -1,27 +1,24 @@ @@ -32,28 +29,7 @@ import { injectManageVersionContext } from '~/providers/version/manage-version-m import SuggestedDependency from './SuggestedDependency.vue' -export interface SuggestedDependency extends Labrinth.Versions.v3.Dependency { - icon?: string - name?: string - versionName?: string -} - -const props = defineProps<{ - suggestedDependencies: SuggestedDependency[] -}>() - -const { draftVersion } = injectManageVersionContext() - -const visibleDependencies = computed(() => - props.suggestedDependencies - .filter( - (dep) => - !draftVersion.value.dependencies?.some( - (d) => d.project_id === dep.project_id && d.version_id === dep.version_id, - ), - ) - .sort((a, b) => (a.name || '').localeCompare(b.name || '')), -) +const { visibleSuggestedDependencies } = injectManageVersionContext() const emit = defineEmits<{ (e: 'onAddSuggestion', dependency: Labrinth.Versions.v3.Dependency): void diff --git a/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependency.vue b/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependency.vue index c3e21f1c..bf4a3db4 100644 --- a/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependency.vue +++ b/apps/frontend/src/components/ui/create-project-version/components/SuggestedDependencies/SuggestedDependency.vue @@ -1,6 +1,6 @@