feat: create instance with content in creation flow modal

- search modpack becomes search projects
- creation flow modal context receives prepareProjectInstall and createProjectInstall callbacks, from content-install.ts (shared with ContentInstallModal)
This commit is contained in:
tdgao
2026-07-30 12:04:50 -07:00
parent 2f618dbd1e
commit 01c023b9f4
38 changed files with 387 additions and 318 deletions
+34 -4
View File
@@ -280,8 +280,7 @@ const {
fetchExistingInstanceNames,
handleCreate,
handleBrowseModpacks,
searchModpacks,
getProjectVersions,
searchProjects,
getLoaderManifest,
setModpackAlreadyInstalledModal,
handleModpackDuplicateCreateAnyway,
@@ -719,6 +718,7 @@ const {
projectInfo: contentInstallProjectInfo,
handleInstallToInstance,
handleCreateAndInstall,
prepareNewInstance,
handleNavigate: handleContentInstallNavigate,
handleCancel: handleContentInstallCancel,
setContentInstallModal,
@@ -738,6 +738,35 @@ const {
handleIncompatibilityWarningCancel: handleContentInstallIncompatibilityWarningCancel,
} = contentInstall
async function prepareCreationProjectInstall(projectId, projectType) {
if (projectType === 'modpack') {
await contentInstall.install(
projectId,
null,
null,
'CreationModalProject',
undefined,
(instanceId) => void router.push(`/instance/${encodeURIComponent(instanceId)}`),
)
return null
}
await prepareNewInstance(projectId)
const info = contentInstallProjectInfo.value
if (!info) throw new Error(`Project information is unavailable: '${projectId}'`)
return {
projectId,
title: info.title,
iconUrl: info.iconUrl,
link: info.link,
owner: info.owner,
compatibleLoaders: [...contentInstallLoaders.value],
gameVersions: [...contentInstallGameVersions.value],
releaseGameVersions: new Set(contentInstallReleaseGameVersions.value),
}
}
const serverInstall = createServerInstall({ router, handleError, popupNotificationManager })
provideServerInstall(serverInstall)
const {
@@ -1581,8 +1610,9 @@ provideAppUpdateDownloadProgress(appUpdateDownload)
type="instance"
show-snapshot-toggle
:fetch-existing-instance-names="fetchExistingInstanceNames"
:search-modpacks="searchModpacks"
:get-project-versions="getProjectVersions"
:search-projects="searchProjects"
:prepare-project-install="prepareCreationProjectInstall"
:create-project-install="handleCreateAndInstall"
:get-loader-manifest="getLoaderManifest"
@create="handleCreate"
@browse-modpacks="handleBrowseModpacks"
@@ -147,6 +147,7 @@ export interface ContentInstallContext {
loader: string
gameVersion: string
}) => Promise<void>
prepareNewInstance: (projectId: string) => Promise<void>
handleNavigate: (instance: ContentInstallInstance) => void
handleCancel: () => void
setContentInstallModal: (ref: ModalRef) => void
@@ -425,7 +426,12 @@ export function createContentInstall(opts: {
project: Labrinth.Projects.v2.Project,
versions: Labrinth.Versions.v2.Version[],
onInstall: ContentInstallCallback,
hints?: { preferredLoader?: string; preferredGameVersion?: string; showProjectInfo?: boolean },
hints?: {
preferredLoader?: string
preferredGameVersion?: string
showProjectInfo?: boolean
showModal?: boolean
},
) {
currentProject = project
currentVersions = versions
@@ -435,6 +441,7 @@ export function createContentInstall(opts: {
loading.value = true
defaultTab.value = 'existing'
let projectInfoPromise: Promise<unknown> = Promise.resolve()
if (hints?.showProjectInfo) {
projectInfo.value = {
title: project.title,
@@ -442,7 +449,7 @@ export function createContentInstall(opts: {
link: `/project/${project.slug ?? project.id}`,
}
if (project.organization) {
get_organization(project.organization)
projectInfoPromise = get_organization(project.organization)
.then((org: { id: string; slug: string; name: string; icon_url?: string }) => {
if (projectInfo.value) {
const orgSlug = org.slug ?? org.id
@@ -459,7 +466,7 @@ export function createContentInstall(opts: {
})
.catch(() => {})
} else if (project.team) {
get_team(project.team)
projectInfoPromise = get_team(project.team)
.then(
(
members: {
@@ -510,10 +517,12 @@ export function createContentInstall(opts: {
: null
await nextTick()
modalRef?.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
if (hints?.showModal !== false) {
modalRef?.show()
trackEvent('ProjectInstallStart', { source: 'ProjectInstallModal' })
}
get_game_versions()
const gameVersionMetadataPromise = get_game_versions()
.then((allGameVersions) => {
const releases = new Set<string>()
const ordered: string[] = []
@@ -560,6 +569,24 @@ export function createContentInstall(opts: {
} finally {
loading.value = false
}
await gameVersionMetadataPromise
await projectInfoPromise
}
async function prepareNewInstance(projectId: string) {
const project: Labrinth.Projects.v2.Project = await get_project(projectId, 'must_revalidate')
if (!project || project.project_type === 'modpack') {
throw new Error(`Project cannot be prepared as a new instance: '${projectId}'`)
}
const versions = (
(await get_version_many(project.versions)) as Labrinth.Versions.v2.Version[]
).sort((a, b) => dayjs(b.date_published).valueOf() - dayjs(a.date_published).valueOf())
await showModInstallModal(project, versions, () => {}, {
showProjectInfo: true,
showModal: false,
})
}
function getInstallTargets(versions: Labrinth.Versions.v2.Version[]) {
@@ -926,6 +953,7 @@ export function createContentInstall(opts: {
projectInfo,
handleInstallToInstance,
handleCreateAndInstall,
prepareNewInstance,
handleNavigate,
handleCancel,
setContentInstallModal(ref: ModalRef) {
@@ -10,7 +10,7 @@ import { useRouter } from 'vue-router'
import type UnknownPackWarningModal from '@/components/ui/install_flow/UnknownPackWarningModal.vue'
import type ModpackAlreadyInstalledModal from '@/components/ui/modal/ModpackAlreadyInstalledModal.vue'
import { trackEvent } from '@/helpers/analytics'
import { get_project_versions, get_search_results } from '@/helpers/cache.js'
import { get_search_results } from '@/helpers/cache.js'
import { import_instance } from '@/helpers/import.js'
import {
type CreatePackLocation,
@@ -185,8 +185,10 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
router.push('/browse/modpack')
}
async function searchModpacks(query: string, limit: number = 10) {
const params = [`facets=[["project_type:modpack"]]`, `limit=${limit}`]
async function searchProjects(query: string, limit: number = 10) {
const projectTypes = ['mod', 'modpack', 'resourcepack', 'shader', 'datapack']
const facets = JSON.stringify([projectTypes.map((type) => `project_type:${type}`)])
const params = [`facets=${encodeURIComponent(facets)}`, `limit=${limit}`]
if (query) {
params.push(`query=${encodeURIComponent(query)}`)
}
@@ -195,19 +197,13 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
return { hits: [], offset: 0, limit, total_hits: 0 }
}
async function getProjectVersions(projectId: string) {
const versions = await get_project_versions(projectId)
return versions ?? []
}
return {
installationModal,
unknownPackWarningModal,
fetchExistingInstanceNames,
handleCreate,
handleBrowseModpacks,
searchModpacks,
getProjectVersions,
searchProjects,
getLoaderManifest,
setModpackAlreadyInstalledModal,
handleModpackDuplicateCreateAnyway,
@@ -9,9 +9,9 @@ import {
getStoredServerAddonInstallQueue,
injectModrinthClient,
injectNotificationManager,
type ModpackSearchResult,
type PendingServerContentInstall,
type PendingServerContentInstallType,
type ProjectSearchResult,
readPendingServerContentInstalls,
readStoredServerInstallQueue,
removePendingServerContentInstall,
@@ -69,7 +69,7 @@ export interface ServerInstallContentContext {
installQueuedServerInstallsAndBack: () => Promise<boolean>
initServerContext: () => Promise<void>
watchServerContextChanges: () => void
searchServerModpacks: (query: string, limit?: number) => Promise<ModpackSearchResult>
searchServerModpacks: (query: string, limit?: number) => Promise<ProjectSearchResult>
getServerProjectVersions: (projectId: string) => Promise<{ id: string }[]>
enforceSetupModpackRoute: (currentProjectType: string | undefined) => void
getQueuedServerInstallPlans: () => Map<string, BrowseInstallPlan<InstallableSearchResult>>
@@ -1,5 +1,50 @@
<template>
<div class="space-y-6">
<div
v-if="ctx.projectInstall.value"
class="flex items-center gap-2.5 rounded-[20px] bg-surface-2 p-3"
>
<AutoLink :to="ctx.projectInstall.value.link" class="shrink-0">
<div
class="size-14 shrink-0 overflow-hidden rounded-2xl border border-solid border-surface-5"
>
<Avatar
v-if="ctx.projectInstall.value.iconUrl"
:src="ctx.projectInstall.value.iconUrl"
:alt="ctx.projectInstall.value.title"
size="100%"
no-shadow
/>
</div>
</AutoLink>
<div class="flex flex-col gap-1">
<AutoLink
:to="ctx.projectInstall.value.link"
class="font-semibold text-contrast hover:underline"
>
{{ ctx.projectInstall.value.title }}
</AutoLink>
<div
v-if="ctx.projectInstall.value.owner"
class="flex items-center gap-2 text-sm text-secondary"
>
<AutoLink
:to="ctx.projectInstall.value.owner.link"
class="flex items-center gap-1.5 text-inherit no-underline hover:underline"
>
<Avatar
:src="ctx.projectInstall.value.owner.iconUrl"
:alt="ctx.projectInstall.value.owner.name"
size="1.25rem"
:circle="ctx.projectInstall.value.owner.circle"
no-shadow
/>
<span class="font-medium">{{ ctx.projectInstall.value.owner.name }}</span>
</AutoLink>
</div>
</div>
</div>
<!-- Instance-specific: Icon upload -->
<div v-if="ctx.flowType === 'instance'" class="flex items-center gap-4">
<Avatar :src="ctx.instanceIconUrl.value ?? undefined" size="5rem" />
@@ -156,6 +201,7 @@ import { computed, onMounted, ref, watch } from 'vue'
import { useDebugLogger } from '#ui/composables/debug-logger'
import { injectFilePicker, injectModrinthClient, injectTags } from '../../../../providers'
import AutoLink from '../../../base/AutoLink.vue'
import Avatar from '../../../base/Avatar.vue'
import ButtonStyled from '../../../base/ButtonStyled.vue'
import Chips from '../../../base/Chips.vue'
@@ -269,6 +315,9 @@ function formatLoaderVersionTypeLabel(type: LoaderVersionType): string {
// For instance flow, prepend 'vanilla' to available loaders.
// For server flows, vanilla is a separate option in the setup type stage, so exclude it here.
const effectiveLoaders = computed(() => {
if (ctx.projectInstall.value) {
return ctx.projectInstall.value.compatibleLoaders
}
if (ctx.flowType === 'instance') {
return ['vanilla', ...ctx.availableLoaders.filter((l) => l !== 'vanilla')]
}
@@ -337,6 +386,7 @@ function toApiLoaderName(loader: string): string {
}
const gameVersionsLoading = computed(() => {
if (ctx.projectInstall.value) return false
const loader = selectedLoader.value
if (!loader || loader === 'vanilla') return false
if (loader === 'paper') return ctx.paperSupportedVersions.value === null
@@ -346,6 +396,16 @@ const gameVersionsLoading = computed(() => {
// Game versions from tags provider, filtered by loader support
const gameVersionOptions = computed<ComboboxOption<string>[]>(() => {
if (ctx.projectInstall.value) {
const versions =
ctx.showSnapshots.value || ctx.projectInstall.value.releaseGameVersions.size === 0
? ctx.projectInstall.value.gameVersions
: ctx.projectInstall.value.gameVersions.filter((version) =>
ctx.projectInstall.value!.releaseGameVersions.has(version),
)
return versions.map((version) => ({ value: version, label: version }))
}
const versions = ctx.showSnapshots.value
? tags.gameVersions.value
: tags.gameVersions.value.filter((v) => v.version_type === 'release')
@@ -510,6 +570,7 @@ function getLoaderVersionsForGameVersion(
watch(
() => selectedLoader.value,
async (loader) => {
if (ctx.projectInstall.value) return
await fetchLoaderMetadata(loader)
},
{ immediate: true },
@@ -525,6 +586,7 @@ watch(
loaderVersionsData.value = []
selectedLoaderVersion.value = null
if (ctx.projectInstall.value) return
if (!loader || !gameVersion || loader === 'vanilla') return
loaderVersionsLoading.value = true
@@ -1,15 +1,15 @@
<template>
<div class="flex flex-col gap-4">
<span class="font-semibold text-contrast">
{{ formatMessage(messages.knownModpackPrompt) }}
{{ formatMessage(messages.knownProjectPrompt) }}
</span>
<Combobox
v-model="ctx.modpackSearchProjectId.value"
v-model="ctx.projectSearchProjectId.value"
v-tooltip="ctx.finishDisabled.value ? ctx.finishDisabledTooltip.value : undefined"
:options="ctx.modpackSearchOptions.value"
:options="ctx.projectSearchOptions.value"
searchable
:disabled="ctx.finishDisabled.value"
:search-placeholder="formatMessage(messages.searchModpackPlaceholder)"
:search-placeholder="formatMessage(messages.searchProjectPlaceholder)"
:no-options-message="
searchLoading
? formatMessage(commonMessages.loadingLabel)
@@ -18,10 +18,20 @@
:disable-search-filter="true"
@search-input="handleSearch"
>
<template #option-suffix>
<RightArrowIcon
class="size-5 shrink-0 text-secondary opacity-0 transition-opacity group-hover/option:opacity-100 group-data-[focused=true]/option:opacity-100"
/>
<template #option-suffix="{ item }">
<div
class="flex shrink-0 items-center gap-1.5 text-sm font-semibold text-secondary opacity-0 transition-opacity group-hover/option:opacity-100 group-data-[focused=true]/option:opacity-100"
>
<span>
{{
formatMessage(
isModpackOption(item.value) ? messages.installModpack : messages.createInstance,
)
}}
</span>
<DownloadIcon v-if="isModpackOption(item.value)" class="size-5 shrink-0" />
<RightArrowIcon v-else class="size-5 shrink-0" />
</div>
</template>
</Combobox>
@@ -104,6 +114,7 @@ import {
BoxIcon,
BoxImportIcon,
CompassIcon,
DownloadIcon,
ImportIcon,
RightArrowIcon,
} from '@modrinth/assets'
@@ -126,18 +137,26 @@ const { formatMessage } = useVIntl()
const searchLoading = ref(false)
const messages = defineMessages({
knownModpackPrompt: {
id: 'creation-flow.modal.modpack.known-modpack.prompt',
defaultMessage: 'Already know the modpack you want to install?',
knownProjectPrompt: {
id: 'creation-flow.modal.project.known-project.prompt',
defaultMessage: 'Already know what you want to play?',
},
searchModpackPlaceholder: {
id: 'creation-flow.modal.modpack.search.placeholder',
defaultMessage: 'Search for modpack',
searchProjectPlaceholder: {
id: 'creation-flow.modal.project.search.placeholder',
defaultMessage: 'Search mods, modpacks, and more...',
},
noResultsFound: {
id: 'creation-flow.modal.modpack.search.no-results',
id: 'creation-flow.modal.project.search.no-results',
defaultMessage: 'No results found',
},
installModpack: {
id: 'creation-flow.modal.project.search.install-modpack',
defaultMessage: 'Install modpack',
},
createInstance: {
id: 'creation-flow.modal.project.search.create-instance',
defaultMessage: 'Create instance',
},
instanceTypeTitle: {
id: 'creation-flow.modal.setup-type.title.instance',
defaultMessage: 'Choose instance type',
@@ -206,6 +225,10 @@ const setupTypeTitle = computed(() => {
return formatMessage(messages.worldTypeTitle)
})
function isModpackOption(projectId: string) {
return ctx.projectSearchHits.value[projectId]?.projectType === 'modpack'
}
function setSetupType(type: 'custom' | 'vanilla') {
debug('selected:', type)
_setSetupType(type)
@@ -249,18 +272,23 @@ async function triggerFileInput() {
async function search(query: string) {
try {
const results = await ctx.searchModpacks(query.trim(), 10)
if (!query.trim()) {
ctx.projectSearchOptions.value = []
return
}
const results = await ctx.searchProjects(query.trim(), 10)
ctx.modpackSearchHits.value = {}
ctx.projectSearchHits.value = {}
for (const hit of results.hits) {
ctx.modpackSearchHits.value[hit.project_id] = {
ctx.projectSearchHits.value[hit.project_id] = {
title: hit.title,
iconUrl: hit.icon_url,
latestVersion: hit.latest_version,
projectType: hit.project_type ?? 'modpack',
}
}
ctx.modpackSearchOptions.value = results.hits.map((hit) => ({
ctx.projectSearchOptions.value = results.hits.map((hit) => ({
label: hit.title,
value: hit.project_id,
icon: defineAsyncComponent(() =>
@@ -275,8 +303,8 @@ async function search(query: string) {
),
}))
} catch (error) {
debug('modpack search failed:', error)
ctx.modpackSearchOptions.value = []
debug('project search failed:', error)
ctx.projectSearchOptions.value = []
} finally {
searchLoading.value = false
}
@@ -288,23 +316,26 @@ async function handleSearch(query: string) {
}
onMounted(() => {
ctx.modpackSearchProjectId.value = undefined
ctx.projectSearchProjectId.value = undefined
search('')
})
watch(
() => ctx.modpackSearchProjectId.value,
() => ctx.projectSearchProjectId.value,
async (projectId, oldProjectId) => {
if (projectId === oldProjectId) return
ctx.modpackSearchVersionId.value = undefined
ctx.modpackVersionOptions.value = []
if (!projectId) return
const hit = ctx.projectSearchHits.value[projectId]
if (ctx.flowType === 'instance') {
void ctx.selectProject(projectId, hit?.projectType ?? 'mod')
return
}
const hit = ctx.modpackSearchHits.value[projectId]
try {
const versions = await ctx.getProjectVersions(projectId)
if (ctx.modpackSearchProjectId.value !== projectId || versions.length === 0) return
if (ctx.projectSearchProjectId.value !== projectId || versions.length === 0) return
selectModpack()
ctx.modpackSelection.value = {
@@ -315,7 +346,7 @@ watch(
}
proceedWithModpack()
} catch (error) {
debug('failed to load modpack versions:', error)
debug('failed to load project versions:', error)
}
},
)
@@ -12,7 +12,7 @@ import {
} from '#ui/composables/i18n'
import { formatLoaderLabel } from '#ui/utils/loaders'
import { createContext, injectModrinthClient } from '../../../providers'
import { createContext, injectModrinthClient, injectNotificationManager } from '../../../providers'
import type { ImportableLauncher } from '../../../providers/instance-import'
import type { MultiStageModal, StageConfigInput } from '../../base'
import type { ComboboxOption } from '../../base/Combobox.vue'
@@ -97,15 +97,17 @@ export interface ModpackSelection {
iconUrl?: string
}
export interface ModpackSearchHit {
export interface ProjectSearchHit {
title: string
iconUrl?: string
latestVersion?: string
projectType: string
}
export interface ModpackSearchResult {
export interface ProjectSearchResult {
hits: {
project_id: string
project_type?: string
title: string
icon_url: string
latest_version?: string
@@ -115,6 +117,30 @@ export interface ModpackSearchResult {
limit: number
}
export interface ProjectInstallSelection {
projectId: string
title: string
iconUrl?: string | null
link: string
owner?: {
name: string
iconUrl?: string
circle?: boolean
link: string | (() => void)
} | null
compatibleLoaders: string[]
gameVersions: string[]
releaseGameVersions: Set<string>
}
export interface ProjectInstallCreateData {
name: string
iconPath: string | null
iconPreviewUrl: string | null
loader: string
gameVersion: string
}
export interface CreationFlowContextValue {
// Flow
flowType: FlowType
@@ -165,13 +191,12 @@ export interface CreationFlowContextValue {
modpackSelection: Ref<ModpackSelection | null>
modpackFile: Ref<File | null>
modpackFilePath: Ref<string | null>
projectInstall: Ref<ProjectInstallSelection | null>
// Modpack search state (persisted across stage navigation)
modpackSearchProjectId: Ref<string | undefined>
modpackSearchVersionId: Ref<string | undefined>
modpackSearchOptions: Ref<ComboboxOption<string>[]>
modpackVersionOptions: Ref<ComboboxOption<string>[]>
modpackSearchHits: Ref<Record<string, ModpackSearchHit>>
// Project search state (persisted across stage navigation)
projectSearchProjectId: Ref<string | undefined>
projectSearchOptions: Ref<ComboboxOption<string>[]>
projectSearchHits: Ref<Record<string, ProjectSearchHit>>
// Import state (instance flow only)
importLaunchers: Ref<ImportableLauncher[]>
@@ -202,13 +227,14 @@ export interface CreationFlowContextValue {
setSetupType: (type: SetupType) => void
setImportMode: () => void
browseModpacks: () => void
selectProject: (projectId: string, projectType: string) => Promise<void>
finish: () => void
buildProperties: () => Archon.Content.v1.PropertiesFields
fetchLoaderMetadata: (loader?: string | null) => Promise<void>
prefetchLoaderMetadata: () => Promise<void>
// Platform-provided search
searchModpacks: (query: string, limit?: number) => Promise<ModpackSearchResult>
searchProjects: (query: string, limit?: number) => Promise<ProjectSearchResult>
getProjectVersions: (projectId: string) => Promise<{ id: string }[]>
getLoaderManifest: LoaderManifestResolver | null
}
@@ -228,7 +254,12 @@ export interface CreationFlowOptions {
initialGameVersion?: string
fetchExistingInstanceNames?: () => Promise<string[]>
onBack?: () => void
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
searchProjects?: (query: string, limit?: number) => Promise<ProjectSearchResult>
prepareProjectInstall?: (
projectId: string,
projectType: string,
) => Promise<ProjectInstallSelection | null>
createProjectInstall?: (data: ProjectInstallCreateData) => Promise<void>
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
getLoaderManifest?: LoaderManifestResolver
finishDisabled?: ComputedRef<boolean>
@@ -246,6 +277,7 @@ export function createCreationFlowContext(
): CreationFlowContextValue {
const debug = useDebugLogger('CreationFlow')
const client = injectModrinthClient()
const { handleError } = injectNotificationManager()
const queryClient = useQueryClient()
const { formatMessage } = useVIntl()
const availableLoaders = options.availableLoaders ?? ['fabric', 'neoforge', 'forge', 'quilt']
@@ -255,7 +287,9 @@ export function createCreationFlowContext(
const initialLoader = options.initialLoader ?? null
const initialGameVersion = options.initialGameVersion ?? null
const onBack = options.onBack ?? null
const searchModpacks = options.searchModpacks!
const searchProjects = options.searchProjects!
const prepareProjectInstall = options.prepareProjectInstall
const createProjectInstall = options.createProjectInstall
const getProjectVersions = options.getProjectVersions!
const getLoaderManifest = options.getLoaderManifest ?? null
const finishDisabled = options.finishDisabled ?? computed(() => false)
@@ -317,13 +351,12 @@ export function createCreationFlowContext(
const modpackSelection = ref<ModpackSelection | null>(null)
const modpackFile = ref<File | null>(null)
const modpackFilePath = ref<string | null>(null)
const projectInstall = ref<ProjectInstallSelection | null>(null)
// Modpack search state (persisted across stage navigation)
const modpackSearchProjectId = ref<string | undefined>()
const modpackSearchVersionId = ref<string | undefined>()
const modpackSearchOptions = ref<ComboboxOption<string>[]>([])
const modpackVersionOptions = ref<ComboboxOption<string>[]>([])
const modpackSearchHits = ref<Record<string, ModpackSearchHit>>({})
// Project search state (persisted across stage navigation)
const projectSearchProjectId = ref<string | undefined>()
const projectSearchOptions = ref<ComboboxOption<string>[]>([])
const projectSearchHits = ref<Record<string, ProjectSearchHit>>({})
// Import state (instance flow only)
const importLaunchers = ref<ImportableLauncher[]>([])
@@ -340,7 +373,10 @@ export function createCreationFlowContext(
// hideLoaderVersion: hides the loader version section (vanilla world type OR vanilla selected as loader chip)
const hideLoaderVersion = computed(
() => setupType.value === 'vanilla' || selectedLoader.value === 'vanilla',
() =>
setupType.value === 'vanilla' ||
selectedLoader.value === 'vanilla' ||
projectInstall.value !== null,
)
function toApiLoaderName(loader: string): string {
@@ -450,11 +486,10 @@ export function createCreationFlowContext(
modpackSelection.value = null
modpackFile.value = null
modpackFilePath.value = null
modpackSearchProjectId.value = undefined
modpackSearchVersionId.value = undefined
modpackSearchOptions.value = []
modpackVersionOptions.value = []
modpackSearchHits.value = {}
projectInstall.value = null
projectSearchProjectId.value = undefined
projectSearchOptions.value = []
projectSearchHits.value = {}
// Import state
importLaunchers.value = []
@@ -470,6 +505,7 @@ export function createCreationFlowContext(
function setSetupType(type: SetupType) {
debug('setSetupType:', type)
isImportMode.value = false
projectInstall.value = null
setupType.value = type
if (type === 'modpack') {
selectedLoader.value = null
@@ -501,6 +537,37 @@ export function createCreationFlowContext(
emit.browseModpacks()
}
async function selectProject(projectId: string, projectType: string) {
if (!prepareProjectInstall) return
try {
const selection = await prepareProjectInstall(projectId, projectType)
if (selection) {
setProjectInstall(selection)
} else {
modal.value?.hide()
}
} catch (error) {
projectSearchProjectId.value = undefined
handleError(error as Error)
}
}
function setProjectInstall(selection: ProjectInstallSelection) {
projectInstall.value = selection
projectSearchProjectId.value = undefined
setupType.value = 'custom'
isImportMode.value = false
modpackSelection.value = null
instanceName.value = selection.title
selectedLoader.value = selection.compatibleLoaders[0] ?? null
selectedGameVersion.value =
selection.gameVersions.find((version) => selection.releaseGameVersions.has(version)) ??
selection.gameVersions[0] ??
null
modal.value?.setStage('custom-setup')
}
function finish() {
if (finishDisabled.value) return
@@ -513,6 +580,22 @@ export function createCreationFlowContext(
hasModpackFile: !!modpackFile.value,
})
loading.value = true
if (
projectInstall.value &&
createProjectInstall &&
selectedLoader.value &&
selectedGameVersion.value
) {
modal.value?.hide()
void createProjectInstall({
name: instanceName.value.trim() || projectInstall.value.title,
iconPath: instanceIconPath.value,
iconPreviewUrl: instanceIconUrl.value,
loader: selectedLoader.value,
gameVersion: selectedGameVersion.value,
}).catch(handleError)
return
}
emit.create(contextValue)
}
@@ -577,11 +660,10 @@ export function createCreationFlowContext(
modpackSelection,
modpackFile,
modpackFilePath,
modpackSearchProjectId,
modpackSearchVersionId,
modpackSearchOptions,
modpackVersionOptions,
modpackSearchHits,
projectInstall,
projectSearchProjectId,
projectSearchOptions,
projectSearchHits,
importLaunchers,
importSelectedInstances,
importSearchQuery,
@@ -598,11 +680,12 @@ export function createCreationFlowContext(
setSetupType,
setImportMode,
browseModpacks,
selectProject,
finish,
buildProperties,
fetchLoaderMetadata,
prefetchLoaderMetadata,
searchModpacks,
searchProjects,
getProjectVersions,
getLoaderManifest,
}
@@ -19,7 +19,9 @@ import {
type CreationFlowContextValue,
type FlowType,
type LoaderManifestResolver,
type ModpackSearchResult,
type ProjectInstallCreateData,
type ProjectInstallSelection,
type ProjectSearchResult,
provideCreationFlowContext,
} from './creation-flow-context'
@@ -35,7 +37,12 @@ const props = withDefaults(
fetchExistingInstanceNames?: () => Promise<string[]>
onBack?: (() => void) | null
fade?: 'standard' | 'warning' | 'danger'
searchModpacks?: (query: string, limit?: number) => Promise<ModpackSearchResult>
searchProjects?: (query: string, limit?: number) => Promise<ProjectSearchResult>
prepareProjectInstall?: (
projectId: string,
projectType: string,
) => Promise<ProjectInstallSelection | null>
createProjectInstall?: (data: ProjectInstallCreateData) => Promise<void>
getProjectVersions?: (projectId: string) => Promise<{ id: string }[]>
getLoaderManifest?: LoaderManifestResolver
finishDisabled?: boolean
@@ -77,7 +84,9 @@ const ctx = createCreationFlowContext(
initialGameVersion: props.initialGameVersion,
fetchExistingInstanceNames: props.fetchExistingInstanceNames,
onBack: props.onBack ?? undefined,
searchModpacks: props.searchModpacks,
searchProjects: props.searchProjects,
prepareProjectInstall: props.prepareProjectInstall,
createProjectInstall: props.createProjectInstall,
getProjectVersions: props.getProjectVersions,
getLoaderManifest: props.getLoaderManifest,
finishDisabled: computed(() => props.finishDisabled ?? false),
@@ -9,7 +9,7 @@
:initial-loader="initialLoader"
:initial-game-version="initialGameVersion"
:fade="props.initialSetup ? undefined : 'danger'"
:search-modpacks="searchModpacks"
:search-projects="searchModpacks"
:get-project-versions="getProjectVersions"
:finish-disabled="!canCompleteSetup"
:finish-disabled-tooltip="!canCompleteSetup ? permissionDeniedMessage : undefined"
@@ -6,7 +6,7 @@ export type {
Gamemode,
GeneratorSettingsMode,
LoaderVersionType,
ModpackSearchResult,
ProjectSearchResult,
SetupType,
} from '../../flows/creation-flow-modal/creation-flow-context'
export { default as CreationFlowModal } from '../../flows/creation-flow-modal/index.vue'
@@ -65,7 +65,7 @@
type="server-onboarding"
:available-loaders="['vanilla', 'fabric', 'neoforge', 'forge', 'quilt', 'paper', 'purpur']"
:show-snapshot-toggle="true"
:search-modpacks="searchModpacks"
:search-projects="searchModpacks"
:get-project-versions="getProjectVersions"
:finish-disabled="!canSetup"
:finish-disabled-tooltip="!canSetup ? permissionDeniedMessage : undefined"
-10
View File
@@ -779,15 +779,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Zrušit vše"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Už víš modpack, který chceš nainstalovat?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Nenalezeny žádné výsledky"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Hledat modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Instance je nastavení Minecraftu s konkrétním loaderem, verzí a módy."
},
@@ -4551,4 +4542,3 @@
"defaultMessage": "Typ"
}
}
-7
View File
@@ -491,12 +491,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Ryd alle"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Ingen resultater fundet"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Søg efter modpack"
},
"creation-flow.modal.setup-type.option.modpack-base.title": {
"defaultMessage": "Installer modpack"
},
@@ -2241,4 +2235,3 @@
"defaultMessage": "Vanilla Shader"
}
}
-10
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Alle entfernen"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Weisst du schon, welches Modpack du installieren möchtest?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Keine Ergebnisse gefunden"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Nach Modpack suchen"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Eine Instanz ist eine Einrichtung von Minecraft mit einem bestimmten Loader, einer bestimmten Version und Mods."
},
@@ -5931,4 +5922,3 @@
"defaultMessage": "Typ"
}
}
-10
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Alle entfernen"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Weißt du schon, welches Modpack du installieren möchtest?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Keine Ergebnisse gefunden"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Nach Modpack suchen"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Eine Instanz ist eine Einrichtung von Minecraft mit einem bestimmten Loader, einer bestimmten Version und Mods."
},
@@ -5931,4 +5922,3 @@
"defaultMessage": "Typ"
}
}
+11 -5
View File
@@ -884,14 +884,20 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Clear all"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Already know the modpack you want to install?"
"creation-flow.modal.project.known-project.prompt": {
"defaultMessage": "Already know what you want to play?"
},
"creation-flow.modal.modpack.search.no-results": {
"creation-flow.modal.project.search.create-instance": {
"defaultMessage": "Create instance"
},
"creation-flow.modal.project.search.install-modpack": {
"defaultMessage": "Install modpack"
},
"creation-flow.modal.project.search.no-results": {
"defaultMessage": "No results found"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Search for modpack"
"creation-flow.modal.project.search.placeholder": {
"defaultMessage": "Search mods, modpacks, and more..."
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "An instance is a Minecraft setup with a specific loader, version, and mods."
-10
View File
@@ -851,15 +851,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Limpiar todo"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "¿Ya sabes qué modpack quieres instalar?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "No se encontraron resultados"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Buscar un modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Una instancia es una configuración de Minecraft con un loader, una versión y unos mods específicos."
},
@@ -5778,4 +5769,3 @@
"defaultMessage": "Tipo"
}
}
-9
View File
@@ -788,15 +788,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Borrar todo"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "¿Ya sabes cuál es el modpack que quieres instalar?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Ningún resultado encontrado"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Buscar por modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Una instancia es una configuración de Minecraft con un específico loader, versión, y mods."
},
-9
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Effacer tout"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Vous avez déjà le modpack en tête ?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Aucun résultat"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Rechercher un modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Une instance est un setup Minecraft avec un mod loader, une version, et des mods spécifiques."
},
-9
View File
@@ -776,15 +776,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Összes törlése"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Már tudod, melyik modcsomagot szeretnéd telepíteni?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Nincs találat"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Modcsomag keresése"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "A játékpéldány egy külön Minecraft-környezet egy adott modbetöltővel, verzióval és modokkal."
},
-4
View File
@@ -740,9 +740,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Kosongkan semua"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Hasil pencarian tidak ditemukan"
},
"creation-flow.modal.setup-type.option.vanilla-minecraft.description": {
"defaultMessage": "Minecraft klasik tanpa mod atau pasang-masuk."
},
@@ -2988,4 +2985,3 @@
"defaultMessage": "Anda memiliki perubahan yang belum tersimpan."
}
}
-9
View File
@@ -860,15 +860,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Deseleziona tutto"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Sai già quale pacchetto di mod installare?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Nessun risultato trovato"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Cerca tra i pacchetti"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Un'istanza è una combinazione di loader, versione e mod per Minecraft."
},
-9
View File
@@ -854,15 +854,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "全ての選択を解除"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "インストールしたいMODパックは既に決まっていますか?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "見つかりません"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Modパックを検索"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "インスタンスとは、特定のローダー、バージョン、およびModを備えたMinecraftの起動構成のことです"
},
-9
View File
@@ -851,15 +851,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "모두 해제"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "설치하고 싶은 모드팩을 이미 알고 계신가요?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "결과 없음"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "모드팩 검색"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "인스턴스란 특정 로더, 버전 및 모드가 적용된 Minecraft 환경을 의미합니다."
},
-10
View File
@@ -731,15 +731,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Kosongkan semua"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Sudah tahu pek mod yang ingin anda pasang?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Tiada hasil dijumpai"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Cari pek mod"
},
"creation-flow.modal.setup-type.option.custom-setup.title": {
"defaultMessage": "Persediaan tersuai"
},
@@ -5028,4 +5019,3 @@
"defaultMessage": "Jenis"
}
}
-9
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Alles wissen"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Weet je al welk modpack je wilt installeren?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Geen resultaten gevonden"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Modpack zoeken"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Een instantie is een Minecraft-installatie met een specifieke loader, versie en mods."
},
-7
View File
@@ -473,12 +473,6 @@
"creation-flow.modal.import-instance.search.placeholder": {
"defaultMessage": "Søk etter instansnavn"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Ingen resultater funnet"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Søk etter modpakke"
},
"external-files.permissions-card.add-files-modal.selected-count": {
"defaultMessage": "{count, plural, one {# fil} other {# filer}} valgt"
},
@@ -2781,4 +2775,3 @@
"defaultMessage": "Du har ulagrede endringer."
}
}
-9
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Wyczyść wszystko"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Wiesz już którą paczkę modów chcesz zainstalować?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Brak wyników wyszukiwania"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Szukaj paczki modów"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Instancja to instalacja Minecraft w danej wersji, z danym loaderem i modami."
},
-9
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Limpar tudo"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Já sabe qual pacote de mods deseja instalar?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Nenhum resultado encontrado"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Buscar pacotes de mods"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Uma instância é uma configuração do Minecraft com um loader, versão e mods específicos."
},
-9
View File
@@ -824,15 +824,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Очистить всë"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Уже знаете, какую сборку хотите скачать?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Ничего не найдено"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Поиск сборки"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Сборка — это установка Minecraft с определённым загрузчиком модов, версией и модами."
},
-10
View File
@@ -851,15 +851,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Obriši sve"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Već znaš koji modpak želiš da instaliraš?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Nema pronađenih rezultata"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Potraži modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Instanca je podešavanje Mínecrafta sa određenim programom za učitavanje, verzijom i modovima."
},
@@ -5778,4 +5769,3 @@
"defaultMessage": "Tip"
}
}
-9
View File
@@ -815,15 +815,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Töm allt"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Vet du redan modpaketet du vill installera?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Inga resultat hittades"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Sök efter modpaket"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "En instans är ett Minecraft-upplägg med en specifik laddare, version och moddar."
},
-9
View File
@@ -851,15 +851,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Tümünü temizle"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Yüklemek istediğiniz mod paketini zaten biliyor musunuz?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Sonuç bulunamadı"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Mod paketi ara"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Her bir 'kurulum', kendine has yükleyicisi (modloader), sürümü ve modları olan bir Minecraft kurulumudur."
},
-9
View File
@@ -806,15 +806,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Очистити всі"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Уже знаєте яку збірку хочете встановити?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Результатів не знайдено"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Пошук збірок"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Профіль — це налаштування Minecraft із певним завантажувачем, версією та модами."
},
-10
View File
@@ -785,15 +785,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "Xóa hết"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "Biết modpack bạn định cài là gì rồi?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "Không tìm thấy"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "Tìm modpack"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "Cấu hình là một bản cài Minecraft với mod, phiên bản và loader của riêng nó."
},
@@ -5076,4 +5067,3 @@
"defaultMessage": "Đội ngũ Modrinth"
}
}
-10
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "清除所有"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "已经知道你想安装哪个整合包?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "未找到结果"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "搜索整合包"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "实例是带有特定加载器、游戏版本和模组的一套 Minecraft 配置。"
},
@@ -5931,4 +5922,3 @@
"defaultMessage": "类型"
}
}
-10
View File
@@ -884,15 +884,6 @@
"creation-flow.modal.import-instance.selection.clear-all": {
"defaultMessage": "清除全部"
},
"creation-flow.modal.modpack.known-modpack.prompt": {
"defaultMessage": "已經選好想要安裝的模組包了嗎?"
},
"creation-flow.modal.modpack.search.no-results": {
"defaultMessage": "找不到結果"
},
"creation-flow.modal.modpack.search.placeholder": {
"defaultMessage": "搜尋模組包"
},
"creation-flow.modal.setup-type.instance.description": {
"defaultMessage": "實例是指具有特定載入器、版本及模組的 Minecraft 設定。"
},
@@ -5931,4 +5922,3 @@
"defaultMessage": "類型"
}
}
@@ -2,7 +2,10 @@ import type { Meta, StoryObj } from '@storybook/vue3-vite'
import { ref } from 'vue'
import ButtonStyled from '../../components/base/ButtonStyled.vue'
import type { CreationFlowContextValue } from '../../components/flows/creation-flow-modal/creation-flow-context'
import type {
CreationFlowContextValue,
ProjectInstallCreateData,
} from '../../components/flows/creation-flow-modal/creation-flow-context'
import CreationFlowModal from '../../components/flows/creation-flow-modal/index.vue'
const meta = {
@@ -106,7 +109,50 @@ export const Instance: Story = {
const onCreate = (config: CreationFlowContextValue) => {
lastEvent.value = `create emitted — loader: ${config.selectedLoader.value}, version: ${config.selectedGameVersion.value}`
}
return { modalRef, openModal, lastEvent, onCreate }
const prepareProjectInstall = async (projectId: string, projectType: string) => {
lastEvent.value = `project selected — ${projectId}`
if (projectType === 'modpack') return null
return {
projectId,
title: 'Fabric API',
iconUrl: 'https://cdn.modrinth.com/data/P7dR8mSH/icon.png',
link: '/project/fabric-api',
compatibleLoaders: ['fabric'],
gameVersions: ['1.21.5', '1.21.4'],
releaseGameVersions: new Set(['1.21.5', '1.21.4']),
}
}
const createProjectInstall = async (data: ProjectInstallCreateData) => {
lastEvent.value = `project instance created — ${data.name}`
}
const searchProjects = async () => ({
hits: [
{
project_id: 'fabric-api',
project_type: 'mod',
title: 'Fabric API',
icon_url: 'https://cdn.modrinth.com/data/P7dR8mSH/icon.png',
},
{
project_id: 'adrenaline',
project_type: 'modpack',
title: 'Adrenaline',
icon_url: 'https://cdn.modrinth.com/data/RPYx1T0D/icon.png',
},
],
offset: 0,
limit: 10,
total_hits: 2,
})
return {
modalRef,
openModal,
lastEvent,
onCreate,
prepareProjectInstall,
createProjectInstall,
searchProjects,
}
},
template: /*html*/ `
<div class="flex flex-col gap-4 items-center">
@@ -118,6 +164,9 @@ export const Instance: Story = {
ref="modalRef"
type="instance"
:show-snapshot-toggle="true"
:search-projects="searchProjects"
:prepare-project-install="prepareProjectInstall"
:create-project-install="createProjectInstall"
@hide="() => {}"
@browse-modpacks="() => console.log('browse-modpacks emitted')"
@create="onCreate"