11<script setup>
2- import { PlusIcon , XIcon } from ' @modrinth/assets'
2+ import { WrenchIcon , XIcon } from ' @modrinth/assets'
33import {
4+ Accordion ,
45 ButtonStyled ,
56 Checkbox ,
67 commonMessages ,
@@ -9,7 +10,7 @@ import {
910 StyledInput ,
1011 useVIntl ,
1112} from ' @modrinth/ui'
12- import { open } from ' @tauri-apps/plugin-dialog'
13+ import { save } from ' @tauri-apps/plugin-dialog'
1314import { ref } from ' vue'
1415
1516import { PackageIcon , VersionIcon } from ' @/assets/icons'
@@ -40,9 +41,10 @@ const messages = defineMessages({
4041 },
4142 selectFilesLabel: {
4243 id: ' app.export-modal.select-files-label' ,
43- defaultMessage: ' Select files and folders to include in pack ' ,
44+ defaultMessage: ' Configure which files are included in this export ' ,
4445 },
4546 exportButton: { id: ' app.export-modal.export-button' , defaultMessage: ' Export' },
47+ includeFile: { id: ' app.export-modal.include-file-accessibility-label' , defaultMessage: ' Include "{file}"?' },
4648})
4749
4850const props = defineProps ({
@@ -65,7 +67,6 @@ const exportDescription = ref('')
6567const versionInput = ref (' 1.0.0' )
6668const files = ref ([])
6769const folders = ref ([])
68- const showingFiles = ref (false )
6970
7071const initFiles = async () => {
7172 const newFolders = new Map ()
@@ -87,7 +88,12 @@ const initFiles = async () => {
8788 folder .startsWith (' modrinth_logs' ) ||
8889 folder .startsWith (' .fabric' ),
8990 }))
90- .filter ((pathData ) => ! pathData .path .includes (' .DS_Store' ))
91+ .filter (
92+ (pathData ) =>
93+ ! pathData .path .includes (' .DS_Store' ) &&
94+ pathData .path !== ' mods/.connector' &&
95+ ! pathData .path .startsWith (' mods/.connector/' ),
96+ )
9197 .forEach ((pathData ) => {
9298 const parent = pathData .path .split (sep).slice (0 , - 1 ).join (sep)
9399 if (parent !== ' ' ) {
@@ -121,15 +127,20 @@ const exportPack = async () => {
121127 }
122128 })
123129 })
124- const outputPath = await open ({
125- directory: true ,
126- multiple: false ,
130+ const outputPath = await save ({
131+ defaultPath: ` ${ nameInput .value } ${ versionInput .value } .mrpack` ,
132+ filters: [
133+ {
134+ name: ' Modrinth Modpack' ,
135+ extensions: [' mrpack' ],
136+ },
137+ ],
127138 })
128139
129140 if (outputPath) {
130141 export_profile_mrpack (
131142 props .instance .path ,
132- outputPath + ` / ${ nameInput . value } ${ versionInput . value } .mrpack ` ,
143+ outputPath,
133144 filesToExport,
134145 versionInput .value ,
135146 exportDescription .value ,
@@ -142,97 +153,81 @@ const exportPack = async () => {
142153
143154<template >
144155 <ModalWrapper ref =" exportModal" :header =" formatMessage(messages.header)" >
145- <div class =" modal-body" >
146- <div class =" labeled_input" >
147- <p >{{ formatMessage(messages.modpackNameLabel) }}</p >
148- <StyledInput
149- v-model =" nameInput"
150- :icon =" PackageIcon"
151- type =" text"
152- :placeholder =" formatMessage(messages.modpackNamePlaceholder)"
153- clearable
154- />
155- </div >
156- <div class =" labeled_input" >
157- <p >{{ formatMessage(messages.versionNumberLabel) }}</p >
158- <StyledInput
159- v-model =" versionInput"
160- :icon =" VersionIcon"
161- type =" text"
162- :placeholder =" formatMessage(messages.versionNumberPlaceholder)"
163- clearable
164- />
165- </div >
166- <div class =" adjacent-input" >
156+ <div class =" flex flex-col gap-4 w-[40rem]" >
157+ <div class =" grid grid-cols-2 gap-4" >
167158 <div class =" labeled_input" >
168- <p >{{ formatMessage(commonMessages.descriptionLabel) }}</p >
169-
159+ <p >{{ formatMessage(messages.modpackNameLabel) }}</p >
170160 <StyledInput
171- v-model =" exportDescription"
172- multiline
173- :placeholder =" formatMessage(messages.descriptionPlaceholder)"
161+ v-model =" nameInput"
162+ :icon =" PackageIcon"
163+ type =" text"
164+ :placeholder =" formatMessage(messages.modpackNamePlaceholder)"
165+ clearable
174166 />
175167 </div >
176- </div >
177-
178- <div class =" table" >
179- <div class =" table-head" >
180- <div class =" table-cell row-wise" >
181- {{ formatMessage(messages.selectFilesLabel) }}
182- <ButtonStyled circular >
183- <button @click =" () => (showingFiles = !showingFiles)" >
184- <PlusIcon v-if =" !showingFiles" />
185- <XIcon v-else />
186- </button >
187- </ButtonStyled >
188- </div >
168+ <div class =" labeled_input" >
169+ <p >{{ formatMessage(messages.versionNumberLabel) }}</p >
170+ <StyledInput
171+ v-model =" versionInput"
172+ :icon =" VersionIcon"
173+ type =" text"
174+ :placeholder =" formatMessage(messages.versionNumberPlaceholder)"
175+ clearable
176+ />
189177 </div >
190- <div v-if =" showingFiles" class =" table-content" >
191- <div v-for =" [path, children] in folders" :key =" path.name" class =" table-row" >
192- <div class =" table-cell file-entry" >
193- <div class =" file-primary" >
178+ </div >
179+ <div class =" flex flex-col gap-2" >
180+ <p class =" m-0" >{{ formatMessage(commonMessages.descriptionLabel) }}</p >
181+ <StyledInput
182+ v-model =" exportDescription"
183+ multiline
184+ :placeholder =" formatMessage(messages.descriptionPlaceholder)"
185+ />
186+ </div >
187+ <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" >
188+ <template #title >
189+ <span class =" flex items-center gap-3 text-contrast group-active:scale-[0.98]" >
190+ <WrenchIcon aria-hidden =" true" class =" size-5 text-secondary" />
191+ Configure which files are included in this export
192+ </span >
193+ </template >
194+ <div class =" flex flex-col [& >*:nth-child(even)]:bg-surface-3" >
195+ <div v-for =" [path, children] in folders" :key =" path.name" class =" flex flex-col" >
196+ <Accordion class =" flex flex-col" button-class =" flex gap-3 pr-4 hover:bg-surface-5 group" >
197+ <template #title >
194198 <Checkbox
195199 :model-value =" children.every((child) => child.selected)"
196- :label =" path.name"
197- class =" select-checkbox"
200+ :indeterminate =" !children.every((child) => child.selected) && children.some((child) => child.selected)"
201+ :description =" formatMessage(messages.includeFile, { file: path.name })"
202+ class =" pl-4 py-2"
198203 :disabled =" children.every((x) => x.disabled)"
199204 @update:model-value ="
200205 (newValue) => children.forEach((child) => (child.selected = newValue))
201206 "
207+ @click.stop
202208 />
209+ <span class =" ml-2 group-active:scale-95" >{{ path.name }}/</span >
210+ </template >
211+ <div v-for =" child in children" :key =" child.path" >
203212 <Checkbox
204- v-model =" path.showingMore"
205- class =" select-checkbox dropdown"
206- collapsing-toggle-style
213+ v-model =" child.selected"
214+ :label =" child.name"
215+ class =" w-full px-8 py-2 hover:bg-surface-4 text-primary"
216+ :disabled =" child.disabled"
207217 />
208218 </div >
209- <div v-if =" path.showingMore" class =" file-secondary" >
210- <div v-for =" child in children" :key =" child.path" class =" file-secondary-row" >
211- <Checkbox
212- v-model =" child.selected"
213- :label =" child.name"
214- class =" select-checkbox"
215- :disabled =" child.disabled"
216- />
217- </div >
218- </div >
219- </div >
220- </div >
221- <div v-for =" file in files" :key =" file.path" class =" table-row" >
222- <div class =" table-cell file-entry" >
223- <div class =" file-primary" >
224- <Checkbox
225- v-model =" file.selected"
226- :label =" file.name"
227- :disabled =" file.disabled"
228- class =" select-checkbox"
229- />
230- </div >
231- </div >
219+ </Accordion >
232220 </div >
221+ <Checkbox
222+ v-for =" file in files" :key =" file.path"
223+ v-model =" file.selected"
224+ :label =" file.name"
225+ :disabled =" file.disabled"
226+ class =" w-full px-4 py-2 hover:bg-surface-4 text-primary"
227+ />
233228 </div >
234- </div >
235- <div class =" button-row push-right " >
229+ </Accordion >
230+ <div class =" flex items-center justify-end gap-2 " >
236231 <ButtonStyled type =" outlined" >
237232 <button @click =" exportModal.hide" >
238233 <XIcon />
@@ -249,83 +244,3 @@ const exportPack = async () => {
249244 </div >
250245 </ModalWrapper >
251246</template >
252-
253- <style scoped lang="scss">
254- .modal-body {
255- display : flex ;
256- flex-direction : column ;
257- gap : var (--gap-md );
258- }
259-
260- .labeled_input {
261- display : flex ;
262- flex-direction : column ;
263- gap : var (--gap-sm );
264-
265- p {
266- margin : 0 ;
267- }
268- }
269-
270- .select-checkbox {
271- gap : var (--gap-sm );
272-
273- button .checkbox {
274- border : none ;
275- }
276-
277- & .dropdown {
278- margin-left : auto ;
279- }
280- }
281-
282- .table-content {
283- max-height : 18rem ;
284- overflow-y : auto ;
285- }
286-
287- .table {
288- border : 1px solid var (--color-bg );
289- }
290-
291- .file-entry {
292- display : flex ;
293- flex-direction : column ;
294- gap : var (--gap-sm );
295- }
296-
297- .file-primary {
298- display : flex ;
299- align-items : center ;
300- gap : var (--gap-sm );
301- }
302-
303- .file-secondary {
304- margin-left : var (--gap-xl );
305- display : flex ;
306- flex-direction : column ;
307- gap : var (--gap-sm );
308- height : 100% ;
309- vertical-align : center ;
310- }
311-
312- .file-secondary-row {
313- display : flex ;
314- align-items : center ;
315- gap : var (--gap-sm );
316- }
317-
318- .button-row {
319- display : flex ;
320- gap : var (--gap-sm );
321- align-items : center ;
322- }
323-
324- .row-wise {
325- display : flex ;
326- flex-direction : row ;
327- justify-content : space-between ;
328- align-items : center ;
329- gap : 1rem ;
330- }
331- </style >
0 commit comments