mirror of
https://github.com/modrinth/code.git
synced 2026-07-31 13:16:38 +00:00
* feat: base of instances v2 * feat: use old profiles with compat layer * prototype: instances v2 * fix: install_from using profile * fix: skins migration fix * fix: frontend still using profile path * fix: add update proj multiselect guard * fix: cargo fmt * fix: content missing fields * feat: break up app-lib/api/instance.rs * fix: check_content_updates mismatch * fix: updater modal cleanup w/new structure * feat: better update all handling * fix: remove preview_update_all * fix: feedback on bulk update + lint * fix: rem transitions * fix: change to jsonb * feat: app db backup after update * fix: lint * fix: sqlx prepare + use sqlx macros * fix: lint * fix: bugs * feat: defuck the installing process up * fix: bug of hell * fix: shear * fix: fmt * fix: install progress spacing + change mc/content/overrides to bytes * fix: lint * fix: prepr * fix: navtabs anim not working in app * fix: worlds.vue improvements + browse page fixes * feat: optimise queries + adapter fns * fix: lint * fix: lint * feat: shared modrinth-content-management crate (#6469) * feat: disable warnings setting * feat: add instances shortcuts (#6329) * Add modrinth://launch deep link to start a profile Support external profile launching via modrinth://launch/{profile_path} for integrations such as Stream Deck. * Change route to /launch/profile/{id} for future extensibility * fix: ensure profile path is url decoded * fix: URL-decode profile path from deep link * fix: use urlencoding crate for URL decoding * feat: implement app instance shortcuts * feat: change windows shortcut creation to use windows api instead * feat: implement creating a shortcut launching world/server * format * fmt * fix multiline inline tables * pnpm prepr * feat: move create shortcut to last item * refactor: split up shortcuts.rs for individual platforms * refactor: turn profile launch url into url type * use string literal and add safety comment * pt2 * refactor: rename anything that's profile into instance * update mac shortcut --------- Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com> --------- Co-authored-by: Truman Gao <106889354+tdgao@users.noreply.github.com> Co-authored-by: DJCheesusReal <134006619+DJCheesusReal@users.noreply.github.com>
255 lines
7.1 KiB
Vue
255 lines
7.1 KiB
Vue
<script setup>
|
|
import { WrenchIcon, XIcon } from '@modrinth/assets'
|
|
import {
|
|
Accordion,
|
|
ButtonStyled,
|
|
Checkbox,
|
|
commonMessages,
|
|
defineMessages,
|
|
injectNotificationManager,
|
|
StyledInput,
|
|
useVIntl,
|
|
} from '@modrinth/ui'
|
|
import { save } from '@tauri-apps/plugin-dialog'
|
|
import { ref } from 'vue'
|
|
|
|
import { PackageIcon, VersionIcon } from '@/assets/icons'
|
|
import ModalWrapper from '@/components/ui/modal/ModalWrapper.vue'
|
|
import { export_instance_mrpack, get_pack_export_candidates } from '@/helpers/instance'
|
|
|
|
const { handleError } = injectNotificationManager()
|
|
const { formatMessage } = useVIntl()
|
|
|
|
const messages = defineMessages({
|
|
header: { id: 'app.export-modal.header', defaultMessage: 'Export modpack' },
|
|
modpackNameLabel: { id: 'app.export-modal.modpack-name-label', defaultMessage: 'Modpack Name' },
|
|
modpackNamePlaceholder: {
|
|
id: 'app.export-modal.modpack-name-placeholder',
|
|
defaultMessage: 'Modpack name',
|
|
},
|
|
versionNumberLabel: {
|
|
id: 'app.export-modal.version-number-label',
|
|
defaultMessage: 'Version number',
|
|
},
|
|
versionNumberPlaceholder: {
|
|
id: 'app.export-modal.version-number-placeholder',
|
|
defaultMessage: '1.0.0',
|
|
},
|
|
descriptionPlaceholder: {
|
|
id: 'app.export-modal.description-placeholder',
|
|
defaultMessage: 'Enter modpack description...',
|
|
},
|
|
selectFilesLabel: {
|
|
id: 'app.export-modal.select-files-label',
|
|
defaultMessage: 'Configure which files are included in this export',
|
|
},
|
|
exportButton: { id: 'app.export-modal.export-button', defaultMessage: 'Export' },
|
|
includeFile: {
|
|
id: 'app.export-modal.include-file-accessibility-label',
|
|
defaultMessage: 'Include "{file}"?',
|
|
},
|
|
})
|
|
|
|
const props = defineProps({
|
|
instance: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
})
|
|
|
|
defineExpose({
|
|
show: () => {
|
|
exportModal.value.show()
|
|
initFiles()
|
|
},
|
|
})
|
|
|
|
const exportModal = ref(null)
|
|
const nameInput = ref(props.instance.name)
|
|
const exportDescription = ref('')
|
|
const versionInput = ref('1.0.0')
|
|
const files = ref([])
|
|
const folders = ref([])
|
|
|
|
const initFiles = async () => {
|
|
const newFolders = new Map()
|
|
const sep = '/'
|
|
files.value = []
|
|
await get_pack_export_candidates(props.instance.id).then((filePaths) =>
|
|
filePaths
|
|
.map((folder) => ({
|
|
path: folder,
|
|
name: folder.split(sep).pop(),
|
|
selected:
|
|
folder.startsWith('mods') ||
|
|
folder.startsWith('datapacks') ||
|
|
folder.startsWith('resourcepacks') ||
|
|
folder.startsWith('shaderpacks') ||
|
|
folder.startsWith('config'),
|
|
disabled:
|
|
folder === 'profile.json' ||
|
|
folder.startsWith('modrinth_logs') ||
|
|
folder.startsWith('.fabric') ||
|
|
folder.startsWith('__MACOSX'),
|
|
}))
|
|
.forEach((pathData) => {
|
|
const parent = pathData.path.split(sep).slice(0, -1).join(sep)
|
|
if (parent !== '') {
|
|
if (newFolders.has(parent)) {
|
|
newFolders.get(parent).push(pathData)
|
|
} else {
|
|
newFolders.set(parent, [pathData])
|
|
}
|
|
} else {
|
|
files.value.push(pathData)
|
|
}
|
|
}),
|
|
)
|
|
folders.value = [...newFolders.entries()].map(([name, value]) => [
|
|
{
|
|
name,
|
|
showingMore: false,
|
|
},
|
|
value,
|
|
])
|
|
}
|
|
|
|
await initFiles()
|
|
|
|
const exportPack = async () => {
|
|
const filesToExport = files.value.filter((file) => file.selected).map((file) => file.path)
|
|
folders.value.forEach((args) => {
|
|
args[1].forEach((child) => {
|
|
if (child.selected) {
|
|
filesToExport.push(child.path)
|
|
}
|
|
})
|
|
})
|
|
const outputPath = await save({
|
|
defaultPath: `${nameInput.value} ${versionInput.value}.mrpack`,
|
|
filters: [
|
|
{
|
|
name: 'Modrinth Modpack',
|
|
extensions: ['mrpack'],
|
|
},
|
|
],
|
|
})
|
|
|
|
if (outputPath) {
|
|
export_instance_mrpack(
|
|
props.instance.id,
|
|
outputPath,
|
|
filesToExport,
|
|
versionInput.value,
|
|
exportDescription.value,
|
|
nameInput.value,
|
|
).catch((err) => handleError(err))
|
|
exportModal.value.hide()
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<ModalWrapper ref="exportModal" :header="formatMessage(messages.header)">
|
|
<div class="flex flex-col gap-4 w-[40rem]">
|
|
<div class="grid grid-cols-2 gap-4">
|
|
<div class="labeled_input">
|
|
<p>{{ formatMessage(messages.modpackNameLabel) }}</p>
|
|
<StyledInput
|
|
v-model="nameInput"
|
|
:icon="PackageIcon"
|
|
type="text"
|
|
:placeholder="formatMessage(messages.modpackNamePlaceholder)"
|
|
clearable
|
|
/>
|
|
</div>
|
|
<div class="labeled_input">
|
|
<p>{{ formatMessage(messages.versionNumberLabel) }}</p>
|
|
<StyledInput
|
|
v-model="versionInput"
|
|
:icon="VersionIcon"
|
|
type="text"
|
|
:placeholder="formatMessage(messages.versionNumberPlaceholder)"
|
|
clearable
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-col gap-2">
|
|
<p class="m-0">{{ formatMessage(commonMessages.descriptionLabel) }}</p>
|
|
<StyledInput
|
|
v-model="exportDescription"
|
|
multiline
|
|
:placeholder="formatMessage(messages.descriptionPlaceholder)"
|
|
/>
|
|
</div>
|
|
<Accordion
|
|
class="w-full bg-surface-4 border border-solid border-surface-5 rounded-2xl overflow-clip"
|
|
button-class="p-4 w-full border-b border-solid border-b-surface-5 bg-surface-2 -mb-px hover:brightness-[--hover-brightness] group"
|
|
>
|
|
<template #title>
|
|
<span class="flex items-center gap-3 text-contrast group-active:scale-[0.98]">
|
|
<WrenchIcon aria-hidden="true" class="size-5 text-secondary" />
|
|
Configure which files are included in this export
|
|
</span>
|
|
</template>
|
|
<div class="flex flex-col [&>*:nth-child(even)]:bg-surface-3">
|
|
<div v-for="[path, children] in folders" :key="path.name" class="flex flex-col">
|
|
<Accordion
|
|
class="flex flex-col"
|
|
button-class="flex gap-3 pr-4 hover:bg-surface-5 group"
|
|
>
|
|
<template #title>
|
|
<Checkbox
|
|
:model-value="children.every((child) => child.selected)"
|
|
:indeterminate="
|
|
!children.every((child) => child.selected) &&
|
|
children.some((child) => child.selected)
|
|
"
|
|
:description="formatMessage(messages.includeFile, { file: path.name })"
|
|
class="pl-4 py-2"
|
|
:disabled="children.every((x) => x.disabled)"
|
|
@update:model-value="
|
|
(newValue) => children.forEach((child) => (child.selected = newValue))
|
|
"
|
|
@click.stop
|
|
/>
|
|
<span class="ml-2 group-active:scale-95">{{ path.name }}/</span>
|
|
</template>
|
|
<div v-for="child in children" :key="child.path">
|
|
<Checkbox
|
|
v-model="child.selected"
|
|
:label="child.name"
|
|
class="w-full px-8 py-2 hover:bg-surface-4 text-primary"
|
|
:disabled="child.disabled"
|
|
/>
|
|
</div>
|
|
</Accordion>
|
|
</div>
|
|
<Checkbox
|
|
v-for="file in files"
|
|
:key="file.path"
|
|
v-model="file.selected"
|
|
:label="file.name"
|
|
:disabled="file.disabled"
|
|
class="w-full px-4 py-2 hover:bg-surface-4 text-primary"
|
|
/>
|
|
</div>
|
|
</Accordion>
|
|
<div class="flex items-center justify-end gap-2">
|
|
<ButtonStyled type="outlined">
|
|
<button @click="exportModal.hide">
|
|
<XIcon />
|
|
{{ formatMessage(commonMessages.cancelButton) }}
|
|
</button>
|
|
</ButtonStyled>
|
|
<ButtonStyled color="brand">
|
|
<button @click="exportPack">
|
|
<PackageIcon />
|
|
{{ formatMessage(messages.exportButton) }}
|
|
</button>
|
|
</ButtonStyled>
|
|
</div>
|
|
</div>
|
|
</ModalWrapper>
|
|
</template>
|