mirror of
https://github.com/modrinth/code.git
synced 2026-08-30 11:36:05 +00:00
feat: app onboarding and library (#6923)
* refactor: remove unused legacy onboarded flag * sqlx prepare * feat: add onboarding checklist app db and add vue provider * feat: add welcome page * feat: add onboarding checklist component * feat: move checklist visibility source of truth in app db * feat: split up import modpack button * feat-small: style tweak * feat: remove discover section from homepage and add library * refactor: move welcome screen to component * refactor: move grid display into /library * refactor: rename existing components * refactor: split up the current library page files * feat: add create group modal * feat: add instance group definitions * feat: update accordian for instance groups * feat: add delete group * feat: implement rename instance groups * refactor: move instance group api out * feat: add context menu for groups * feat: style instance items in library * feat: add selected instance item state * feat: implement action bar for selection * feat: drag and drop to move between groups * feat: show ungrouped with header * feat: improve edit group name * small fix * small style fixes * feat: improve selection behaviour * fix: dont toggle group when context menu open * feat: drag and drop multiple selected * small * feat: add remove from group in action bar * feat: add delay on icon hiding in inline input * feat: improve icon hiding delay * fix: bad routes.js * feat: implement sort and filter options * refactor: dont prefix filenames * feat: change open/collapse group target * feat: improve ungrouped behaviour * feat: add shift to select multiple instances * fix: icon disappear transition * feat: add esc while dragging to cancel * feat: update sorting and moved label * feat: update context menu * fix: multiple context menus can be opened * refactor: instance groups to be identified by ID instead of name, names become display data * feat: creating new group always adds instance * feat: create group button, fix instance selection, and increase group name max length * feat: strokes * fix: show getting started checklist in all sidebar * feat: set size on microsoft login screen * fix: use ButtonStyled in Chips component * feat: creating groups with selected instances and fix some moving selection and duplication bugs * pnpm prepr * feat: add tooltip for instance already in group * 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) * feat: update instance card styles * refactor: separate out instance card content * update styles * refactor: instance card * fix: width of instance * feat: update empty state * feat: update sort and grouping dropdowns * feat: better context menu options * feat: add group action buttons * feat: add/remove favourites group * remove manual sort * feat: improve the "New group" jank * feat: add group by instance type and change group sorting * feat: welcome screen styles match figma * feat: not signed in skins demo * feat: new hosting empty state * feat: new instance card style * feat: add tooltip and spice up animation more * feat: improve the transitions for groups after dropping many instances * feat: set instance group membership for bulk moving * feat: remove fade out * fix: allow dragging from buttons inside instance card * fix: library page load in behaviour * small fix * feat: add group ordering and reordering * feat: add drag to reorder groups * fix: switching page not clearing selected instances * style fixes * style fix * fix spacing * feat: jump in changes * feat: add styled newly created instances * feat: improve jump in cards styles * fix: welcome screen style * fix: empty state styles * feat: max 5 jump in items * feat: max new instances to 3 * fix: search icon * feat: clear selection after move * feat: skins list use check icon styles * feat: list will be deleted instances and default sort last played * fix: stroke * remove: no more group settings in instance general settings * fix: resolving modpack server install * feat: icon editor * feat: add three hue shifted variants * fix: surprise me doesnt pick symbol * feat: add customize icon to create instance modal * feat: skins loading state style * fix: welcome logo * fix: copy * fix: left over usage of ButtonStyled from main's refactor * feat: some leaked merge stuff * feat: server empty state styles * remove: instance helper text * remove: auto link in set up stage * feat: auto focus on search when modal open * fix: delete instance modal style * remove: modpack meta repeating * fix: icon * fix: font weight * fix: dropdown filter preview heights * pnpm prepr * fix: sign into modrinth flashing change in position * feat: add avatar transparent corner gets padding * feat: drag and hold over group opens group * feat: add random icon for custom instance * feat: update loading icon * polish: empty state for search * polish: jump in cards * fix: avatar could skip checking for image if tauri loads it first * feat: add icon customize and randomizer in content install modal for add to new instances * fix: avatar cannot read image pixels due to cors * refactor: popup notifications toast type for instance sharing notifications * feat: apply random icons modal and add popup notification for it * polish: style * fix: set_webview_visible * QA * qa * feat: add inner white 15 opacity stroke around avatar * add to catalog * pnpm prepr * fix: app event import * feat: add new icons and backgrounds for icon editor * feat: recents list generate from randomized icons and save icon in editor + icon editor modal fixes * qa * refactor: recipe -> config * fix: event bus * polish * feat: add safe guard against randomizing empty icons * feat: navigate to instance after creation * fix: icon file names * i18n on welcome screen * i18n pass on PR * fix: inconsistent max group name length * refactor: use sqlx macro * feat: add instance icon background selection drag * feat: update catalog order * fix: remove gradient for welcome screen * remove: frog * fix: min width * feat: update corner size * fix: context menu styles * prepr * fix: instance name trimming * reorder * fix: instance card loading state stayed until hovering * feat: add icon randomizer blacklist * blog * prepr --------- Co-authored-by: Prospector <6166773+Prospector@users.noreply.github.com>
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
||||
getLatestMatchingInstallVersion,
|
||||
useVIntl,
|
||||
} from '@modrinth/ui'
|
||||
import { convertFileSrc } from '@tauri-apps/api/core'
|
||||
import { openUrl } from '@tauri-apps/plugin-opener'
|
||||
import dayjs from 'dayjs'
|
||||
import { nextTick, type Ref, ref } from 'vue'
|
||||
@@ -30,6 +29,7 @@ import {
|
||||
get,
|
||||
get_install_candidates,
|
||||
get_projects,
|
||||
getInstanceIconUrl,
|
||||
install_project_with_dependencies,
|
||||
list,
|
||||
remove_project,
|
||||
@@ -145,6 +145,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
|
||||
@@ -424,7 +425,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
|
||||
@@ -434,6 +440,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,
|
||||
@@ -441,7 +448,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
|
||||
@@ -458,7 +465,7 @@ export function createContentInstall(opts: {
|
||||
})
|
||||
.catch(() => {})
|
||||
} else if (project.team) {
|
||||
get_team(project.team)
|
||||
projectInfoPromise = get_team(project.team)
|
||||
.then(
|
||||
(
|
||||
members: {
|
||||
@@ -509,10 +516,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[] = []
|
||||
@@ -541,7 +550,7 @@ export function createContentInstall(opts: {
|
||||
return {
|
||||
id: instance.id,
|
||||
name: instance.name,
|
||||
iconUrl: instance.icon_path ? convertFileSrc(instance.icon_path) : null,
|
||||
iconUrl: getInstanceIconUrl(instance.icon_path),
|
||||
installed: instance.installed,
|
||||
compatible: instance.compatible,
|
||||
installing: false,
|
||||
@@ -559,6 +568,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[]) {
|
||||
@@ -940,6 +967,7 @@ export function createContentInstall(opts: {
|
||||
projectInfo,
|
||||
handleInstallToInstance,
|
||||
handleCreateAndInstall,
|
||||
prepareNewInstance,
|
||||
handleNavigate,
|
||||
handleCancel,
|
||||
setContentInstallModal(ref: ModalRef) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import { createContext } from '@modrinth/ui'
|
||||
import { computed, type ComputedRef, ref } from 'vue'
|
||||
|
||||
import { useAppEvent } from '@/composables/use-app-event'
|
||||
import { getOnboardingChecklist, type OnboardingChecklist } from '@/helpers/onboarding-checklist'
|
||||
import type { AppEvents } from '@/providers/app-events'
|
||||
|
||||
export interface OnboardingChecklistContext {
|
||||
hasCreatedInstance: ComputedRef<boolean>
|
||||
hasLoggedIntoMinecraft: ComputedRef<boolean>
|
||||
hasLoggedIntoModrinth: ComputedRef<boolean>
|
||||
isReady: ComputedRef<boolean>
|
||||
showChecklist: ComputedRef<boolean>
|
||||
}
|
||||
|
||||
export interface OnboardingChecklistProvider extends OnboardingChecklistContext {
|
||||
initialize: () => Promise<void>
|
||||
}
|
||||
|
||||
export const [injectOnboardingChecklist, provideOnboardingChecklist] =
|
||||
createContext<OnboardingChecklistContext>('root', 'onboardingChecklist')
|
||||
|
||||
export function setupOnboardingChecklistProvider(
|
||||
appEvents: AppEvents,
|
||||
): OnboardingChecklistProvider {
|
||||
const checklist = ref<OnboardingChecklist>()
|
||||
|
||||
const context: OnboardingChecklistContext = {
|
||||
hasCreatedInstance: computed(() => checklist.value?.has_created_instance ?? false),
|
||||
hasLoggedIntoMinecraft: computed(() => checklist.value?.has_logged_into_minecraft ?? false),
|
||||
hasLoggedIntoModrinth: computed(() => checklist.value?.has_logged_into_modrinth ?? false),
|
||||
isReady: computed(() => checklist.value !== undefined),
|
||||
showChecklist: computed(() => checklist.value?.show_checklist ?? false),
|
||||
}
|
||||
|
||||
provideOnboardingChecklist(context)
|
||||
|
||||
const applyChecklist = (nextChecklist: OnboardingChecklist) => {
|
||||
checklist.value = {
|
||||
has_created_instance:
|
||||
(checklist.value?.has_created_instance ?? false) || nextChecklist.has_created_instance,
|
||||
has_logged_into_minecraft:
|
||||
(checklist.value?.has_logged_into_minecraft ?? false) ||
|
||||
nextChecklist.has_logged_into_minecraft,
|
||||
has_logged_into_modrinth:
|
||||
(checklist.value?.has_logged_into_modrinth ?? false) ||
|
||||
nextChecklist.has_logged_into_modrinth,
|
||||
show_checklist: (checklist.value?.show_checklist ?? true) && nextChecklist.show_checklist,
|
||||
}
|
||||
}
|
||||
|
||||
useAppEvent('onboarding_checklist', applyChecklist, appEvents)
|
||||
|
||||
const initialize = async () => applyChecklist(await getOnboardingChecklist())
|
||||
|
||||
return { ...context, initialize }
|
||||
}
|
||||
@@ -152,6 +152,7 @@ export function createServerInstall(opts: {
|
||||
|
||||
function showModpackInstallSuccess(project: GameInstance, serverAddress: string | null) {
|
||||
opts.popupNotificationManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Install complete',
|
||||
text: `${project.name} is installed and ready to play.`,
|
||||
type: 'success',
|
||||
@@ -187,6 +188,7 @@ export function createServerInstall(opts: {
|
||||
|
||||
function showUpdateSuccess(instance: GameInstance, serverAddress: string | null) {
|
||||
opts.popupNotificationManager.addPopupNotification({
|
||||
contentType: 'standard',
|
||||
title: 'Update complete',
|
||||
text: `${instance.name} has been updated and is ready to play.`,
|
||||
type: 'success',
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import type { AbstractModrinthClient } from '@modrinth/api-client'
|
||||
import type { AbstractPopupNotificationManager, AbstractWebNotificationManager } from '@modrinth/ui'
|
||||
|
||||
import type { InstanceIconConfig } from '@/helpers/types'
|
||||
|
||||
import type { AppEvents } from './app-events'
|
||||
import { setupOnboardingChecklistProvider } from './onboarding-checklist'
|
||||
import { setupCreationModal } from './setup/creation-modal'
|
||||
import { setupFileDropProvider } from './setup/file-drop'
|
||||
import { setupFilePickerProvider } from './setup/file-picker'
|
||||
@@ -12,14 +16,18 @@ export function setupProviders(
|
||||
client: AbstractModrinthClient,
|
||||
notificationManager: AbstractWebNotificationManager,
|
||||
_popupNotificationManager: AbstractPopupNotificationManager,
|
||||
appEvents: AppEvents,
|
||||
getGeneratedIconConfig?: (iconPath: string) => InstanceIconConfig | null,
|
||||
) {
|
||||
setupUserCountryProvider(client)
|
||||
setupTagsProvider(notificationManager)
|
||||
setupFileDropProvider()
|
||||
setupFilePickerProvider()
|
||||
setupInstanceImportProvider(notificationManager)
|
||||
const onboardingChecklist = setupOnboardingChecklistProvider(appEvents)
|
||||
|
||||
return {
|
||||
...setupCreationModal(notificationManager),
|
||||
...setupCreationModal(notificationManager, getGeneratedIconConfig),
|
||||
onboardingChecklist,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,6 +220,10 @@ export function decodeAppEvent(payload: ArrayBuffer): AppEvent {
|
||||
return { type: event.tag, payload: normalizeProcessPayload(event.value) }
|
||||
case 'instance':
|
||||
return { type: event.tag, payload: normalizeInstancePayload(event.value) }
|
||||
case 'instance_groups_changed':
|
||||
return { type: event.tag, payload: wireObject(event.value) }
|
||||
case 'onboarding_checklist':
|
||||
return { type: event.tag, payload: wireObject(event.value) }
|
||||
case 'instance_bulk_update_progress':
|
||||
return { type: event.tag, payload: normalizeBulkUpdatePayload(event.value) }
|
||||
case 'install_job':
|
||||
|
||||
@@ -10,20 +10,24 @@ 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,
|
||||
install_create_instance,
|
||||
install_create_modpack_instance,
|
||||
install_get_modpack_preview,
|
||||
installJobInstanceId,
|
||||
} from '@/helpers/install'
|
||||
import { list } from '@/helpers/instance'
|
||||
import { get_loader_versions as getLoaderManifest } from '@/helpers/metadata.js'
|
||||
import type { InstanceLoader } from '@/helpers/types'
|
||||
import type { InstanceIconConfig, InstanceLoader } from '@/helpers/types'
|
||||
import { useTheming } from '@/store/state'
|
||||
|
||||
export function setupCreationModal(notificationManager: AbstractWebNotificationManager) {
|
||||
export function setupCreationModal(
|
||||
notificationManager: AbstractWebNotificationManager,
|
||||
getGeneratedIconConfig?: (iconPath: string) => InstanceIconConfig | null,
|
||||
) {
|
||||
const { handleError } = notificationManager
|
||||
const router = useRouter()
|
||||
const themeStore = useTheming()
|
||||
@@ -48,6 +52,19 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
provide('showCreationModal', () => {
|
||||
installationModal.value?.show()
|
||||
})
|
||||
provide('showImportModal', async () => {
|
||||
await installationModal.value?.show()
|
||||
installationModal.value?.ctx.setImportMode()
|
||||
})
|
||||
|
||||
async function navigateToCreatedInstance(
|
||||
job: Awaited<ReturnType<typeof install_create_instance>>,
|
||||
) {
|
||||
const instanceId = installJobInstanceId(job)
|
||||
if (instanceId) {
|
||||
await router.push(`/instance/${encodeURIComponent(instanceId)}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function proceedWithModpackCreation(
|
||||
projectId: string,
|
||||
@@ -55,16 +72,23 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
name: string,
|
||||
iconUrl?: string,
|
||||
) {
|
||||
await install_create_modpack_instance({
|
||||
const job = await install_create_modpack_instance({
|
||||
type: 'fromVersionId',
|
||||
project_id: projectId,
|
||||
version_id: versionId,
|
||||
title: name,
|
||||
icon_url: iconUrl,
|
||||
}).catch(handleError)
|
||||
})
|
||||
await navigateToCreatedInstance(job)
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalModpack' })
|
||||
}
|
||||
|
||||
async function proceedWithModpackFileCreation(location: CreatePackLocation) {
|
||||
const job = await install_create_modpack_instance(location)
|
||||
await navigateToCreatedInstance(job)
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalModpackFile' })
|
||||
}
|
||||
|
||||
async function handleCreate(config: CreationFlowContextValue) {
|
||||
try {
|
||||
if (config.modpackSelection.value) {
|
||||
@@ -84,16 +108,25 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
installationModal.value?.hide()
|
||||
|
||||
if (config.isImportMode.value) {
|
||||
let importCount = 0
|
||||
let importedInstanceId: string | null = null
|
||||
for (const [launcherName, instanceSet] of Object.entries(
|
||||
config.importSelectedInstances.value,
|
||||
)) {
|
||||
const launcher = config.importLaunchers.value.find((l) => l.name === launcherName)
|
||||
if (!launcher || instanceSet.size === 0) continue
|
||||
for (const name of instanceSet) {
|
||||
await import_instance(launcher.name, launcher.path, name).catch(handleError)
|
||||
importCount += 1
|
||||
const job = await import_instance(launcher.name, launcher.path, name).catch(handleError)
|
||||
if (job) {
|
||||
importedInstanceId = installJobInstanceId(job)
|
||||
}
|
||||
}
|
||||
}
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalImport' })
|
||||
if (importCount === 1 && importedInstanceId) {
|
||||
await router.push(`/instance/${encodeURIComponent(importedInstanceId)}`)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -117,17 +150,16 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
: config.modpackFilePath.value
|
||||
if (unknownPackWarningModal.value) {
|
||||
unknownPackWarningModal.value?.show(
|
||||
() => install_create_modpack_instance(location).then(() => undefined),
|
||||
() => proceedWithModpackFileCreation(location).catch(handleError),
|
||||
fileName,
|
||||
preview.externalFilesInModpack,
|
||||
)
|
||||
} else {
|
||||
await install_create_modpack_instance(location)
|
||||
await proceedWithModpackFileCreation(location)
|
||||
}
|
||||
} else {
|
||||
await install_create_modpack_instance(location)
|
||||
await proceedWithModpackFileCreation(location)
|
||||
}
|
||||
trackEvent('InstanceCreate', { source: 'CreationModalModpackFile' })
|
||||
return
|
||||
}
|
||||
|
||||
@@ -141,13 +173,15 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
const iconPath = config.instanceIconPath.value ?? null
|
||||
const name = config.instanceName.value.trim() || config.autoInstanceName.value
|
||||
|
||||
await install_create_instance({
|
||||
const job = await install_create_instance({
|
||||
name,
|
||||
gameVersion: config.selectedGameVersion.value!,
|
||||
loader: loader as InstanceLoader,
|
||||
loaderVersion,
|
||||
iconPath,
|
||||
}).catch(handleError)
|
||||
iconConfig: iconPath ? getGeneratedIconConfig?.(iconPath) : null,
|
||||
})
|
||||
await navigateToCreatedInstance(job)
|
||||
|
||||
trackEvent('InstanceCreate', {
|
||||
source: 'CreationModal',
|
||||
@@ -168,7 +202,11 @@ export function setupCreationModal(notificationManager: AbstractWebNotificationM
|
||||
if (!pendingModpackCreation.value) return
|
||||
const { projectId, versionId, name, iconUrl } = pendingModpackCreation.value
|
||||
pendingModpackCreation.value = null
|
||||
await proceedWithModpackCreation(projectId, versionId, name, iconUrl)
|
||||
try {
|
||||
await proceedWithModpackCreation(projectId, versionId, name, iconUrl)
|
||||
} catch (error) {
|
||||
handleError(error as Error)
|
||||
}
|
||||
}
|
||||
|
||||
function handleModpackDuplicateGoToInstance(instanceId: string) {
|
||||
@@ -181,8 +219,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)}`)
|
||||
}
|
||||
@@ -191,19 +231,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,
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
getTargetInstallPreferences,
|
||||
injectModrinthClient,
|
||||
injectNotificationManager,
|
||||
type ModpackSearchResult,
|
||||
type ProjectSearchResult,
|
||||
readStoredServerInstallQueue,
|
||||
resolveServerAddonInstallPlans,
|
||||
useServerContextRuntime,
|
||||
@@ -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>>
|
||||
|
||||
Reference in New Issue
Block a user