Files
Starlight_Lancher/apps/app-frontend/src/composables/useWorldDatapacks.ts
T

159 lines
4.9 KiB
TypeScript

import { type ContentItem, useDebugLogger } from '@modrinth/ui'
import { computed, ref } from 'vue'
import {
deleteDatapack,
listDatapacks,
setDatapackEnabled,
type WorldWithDatapacks,
} from '@/helpers/datapacks'
import { getPackFormatRange } from '@/helpers/pack-formats'
function datapackDescription(description: unknown): string | undefined {
if (typeof description === 'string') return description
if (Array.isArray(description)) {
return description.map(datapackDescription).filter(Boolean).join(' ')
}
if (description && typeof description === 'object') {
const record = description as Record<string, unknown>
if (typeof record.text === 'string') {
const extra = Array.isArray(record.extra)
? record.extra.map(datapackDescription).filter(Boolean).join('')
: ''
return record.text + extra
}
if (typeof record.translate === 'string') return record.translate
}
return undefined
}
export function useWorldDatapacks(getInstanceId: () => string) {
const debugState = useDebugLogger('Mods:world-datapacks')
const worldDatapacks = ref<WorldWithDatapacks[]>([])
let worldDatapackRequest = 0
async function loadWorldDatapacks() {
const request = ++worldDatapackRequest
try {
const data = await listDatapacks(getInstanceId())
if (request !== worldDatapackRequest) return
worldDatapacks.value = data ?? []
debugState('world datapacks loaded', {
worlds: worldDatapacks.value.length,
datapacks: worldDatapacks.value.reduce((total, world) => total + world.datapacks.length, 0),
})
} catch (error) {
if (request !== worldDatapackRequest) return
worldDatapacks.value = []
console.warn('Could not load world datapacks:', error)
}
}
/**
* World datapacks (files inside `saves/<world>/datapacks/`) are surfaced as
* plain content items so the content tab treats them exactly like mods,
* resource packs, shaders and schematics: the type filter pill and its count
* come from real items, rows render in the content table grouped per save,
* and delete goes through the world-datapack command.
*/
const worldDatapackItems = computed<ContentItem[]>(() =>
worldDatapacks.value.flatMap((entry) =>
entry.datapacks.map((datapack) => {
const id = `local:world-datapack:${entry.path}:${datapack.file_name}`
const versionRange = getPackFormatRange(datapack.pack_format)
const versionNumber =
versionRange?.min ?? (datapack.pack_format != null ? String(datapack.pack_format) : '')
return {
id,
file_name: datapack.file_name,
file_path: `saves/${entry.path}/datapacks/${datapack.file_name}`,
project_type: 'datapack',
update: null,
origin_provider: null,
enabled: datapack.enabled !== false,
external: true,
source_kind: 'world_datapack',
groupMeta: {
icon_url: entry.icon,
title: entry.name,
last_played: entry.last_played,
game_mode: entry.game_mode,
hardcore: entry.hardcore,
},
instanceOwnershipKind: 'local_discovered',
instanceMaterializationState: 'present',
instanceCapabilities: {
canToggle: true,
canDelete: true,
canUpdate: false,
canChangeVersion: false,
canRestorePackDefault: false,
},
project: {
id,
slug: datapack.display_name,
title: datapack.display_name,
icon_url: datapack.icon,
description: datapackDescription(datapack.description),
},
version: {
id: datapack.file_name,
version_number: versionNumber,
file_name: datapack.file_name,
},
provider_refs: [],
} satisfies ContentItem
}),
),
)
function isWorldDatapackItem(item: ContentItem) {
return item.project_type === 'datapack' && item.source_kind === 'world_datapack'
}
async function deleteWorldDatapackItem(item: ContentItem) {
const segments = (item.file_path ?? '').split('/')
const worldPath = segments[1]
const fileName = segments[3]
if (!worldPath || !fileName) {
throw new Error('Invalid world datapack path')
}
await deleteDatapack(getInstanceId(), worldPath, fileName)
await loadWorldDatapacks()
}
async function toggleWorldDatapackItem(item: ContentItem, enabled: boolean) {
const segments = (item.file_path ?? '').split('/')
const worldPath = segments[1]
const fileName = segments[3]
if (!worldPath || !fileName) {
throw new Error('Invalid world datapack path')
}
const world = worldDatapacks.value.find((entry) => entry.path === worldPath)
const datapack = world?.datapacks.find((entry) => entry.file_name === fileName)
const previousEnabled = datapack?.enabled
if (datapack) {
datapack.enabled = enabled
}
try {
await setDatapackEnabled(getInstanceId(), worldPath, fileName, enabled)
} catch (error) {
if (datapack) {
datapack.enabled = previousEnabled
}
throw error
}
}
return {
worldDatapacks,
loadWorldDatapacks,
worldDatapackItems,
isWorldDatapackItem,
deleteWorldDatapackItem,
toggleWorldDatapackItem,
}
}