[+] Export maimai userdata

This commit is contained in:
Clansty
2024-08-01 06:56:31 +08:00
parent b32b0e970c
commit 7c4f887ef4
7 changed files with 76 additions and 51 deletions

View File

@@ -21,26 +21,24 @@
} }
</script> </script>
<main> <div class="fields">
<div class="fields"> {#each gameFields as field}
{#each gameFields as field} <div class="field">
<div class="field"> {#if field.type === "Boolean"}
{#if field.type === "Boolean"} <div class="bool">
<div class="bool"> <input id={field.key} type="checkbox" bind:checked={field.value}
<input id={field.key} type="checkbox" bind:checked={field.value} on:change={() => submitGameOption(field.key, field.value)}/>
on:change={() => submitGameOption(field.key, field.value)}/> <label for={field.key}>
<label for={field.key}> <span class="name">{ts(`settings.fields.${field.key}.name`)}</span>
<span class="name">{ts(`settings.fields.${field.key}.name`)}</span> <span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span>
<span class="desc">{ts(`settings.fields.${field.key}.desc`)}</span> </label>
</label> </div>
</div> {/if}
{/if} </div>
</div> {/each}
{/each} </div>
</div>
<StatusOverlays {error} loading={!gameFields.length && !!submitting}/> <StatusOverlays {error} loading={!gameFields.length && !!submitting}/>
</main>
<style lang="sass"> <style lang="sass">
.fields .fields

View File

@@ -34,34 +34,53 @@
break break
} }
} }
function exportData() {
submitting = "export"
GAME.export('mai2')
.then(data => download(JSON.stringify(data), `AquaDX_maimai2_export_${values[0]}.json`))
.catch(e => error = e.message)
.finally(() => submitting = "")
}
function download(data: string, filename: string) {
const blob = new Blob([data]);
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = filename;
link.click();
}
</script> </script>
<main> <div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}>
<div class="fields" out:fade={FADE_OUT} in:fade={FADE_IN}> {#each profileFields as [field, name], i (field)}
{#each profileFields as [field, name], i (field)} <div class="field">
<div class="field"> <label for={field}>{name}</label>
<label for={field}>{name}</label> <div>
<div> <input id={field} type="text"
<input id={field} type="text" bind:value={values[i]} on:input={() => changed = [...changed, field]}
bind:value={values[i]} on:input={() => changed = [...changed, field]} placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/>
placeholder={field === 'password' ? t('settings.profile.unchanged') : t('settings.profile.unset')}/> {#if changed.includes(field) && values[i]}
{#if changed.includes(field) && values[i]} <button transition:slide={{axis: 'x'}} on:click={() => submit(field, values[i])}>
<button transition:slide={{axis: 'x'}} on:click={() => submit(field, values[i])}> {#if submitting === field}
{#if submitting === field} <Icon icon="line-md:loading-twotone-loop"/>
<Icon icon="line-md:loading-twotone-loop"/> {:else}
{:else} {t('settings.profile.save')}
{t('settings.profile.save')} {/if}
{/if} </button>
</button> {/if}
{/if}
</div>
</div> </div>
{/each} </div>
<GameSettingFields game="mai2"/> {/each}
</div> <GameSettingFields game="mai2"/>
<button class="exportButton" on:click={exportData}>
<Icon icon="bxs:file-export"/>
{t('settings.export')}
</button>
</div>
<StatusOverlays {error} loading={!values[0] || !!submitting}/> <StatusOverlays {error} loading={!values[0] || !!submitting}/>
</main>
<style lang="sass"> <style lang="sass">
.fields .fields
@@ -84,4 +103,10 @@
> input > input
flex: 1 flex: 1
.exportButton
display: flex
justify-content: center
align-items: center
gap: 5px
</style> </style>

View File

@@ -4,8 +4,6 @@
import GameSettingFields from "./GameSettingFields.svelte"; import GameSettingFields from "./GameSettingFields.svelte";
</script> </script>
<main> <div out:fade={FADE_OUT} in:fade={FADE_IN}>
<div out:fade={FADE_OUT} in:fade={FADE_IN}> <GameSettingFields game="wacca"/>
<GameSettingFields game="wacca"/> </div>
</div>
</main>

View File

@@ -141,6 +141,7 @@ export const EN_REF_SETTINGS = {
'settings.profile.bio': 'Bio', 'settings.profile.bio': 'Bio',
'settings.profile.unset': 'Unset', 'settings.profile.unset': 'Unset',
'settings.profile.unchanged': 'Unchanged', 'settings.profile.unchanged': 'Unchanged',
'settings.export': 'Export Player Data',
} }
export const EN_REF_USERBOX = { export const EN_REF_USERBOX = {

View File

@@ -150,6 +150,7 @@ const zhSettings: typeof EN_REF_SETTINGS = {
'settings.profile.bio': '简介', 'settings.profile.bio': '简介',
'settings.profile.unset': '未设置', 'settings.profile.unset': '未设置',
'settings.profile.unchanged': '未更改', 'settings.profile.unchanged': '未更改',
'settings.export': '导出玩家数据',
} }
export const ZH = { ...zhUser, ...zhWelcome, ...zhGeneral, export const ZH = { ...zhUser, ...zhWelcome, ...zhGeneral,

View File

@@ -46,7 +46,7 @@ export function fetchWithParams(input: URL | RequestInfo, init?: RequestInitWith
const cache: { [index: string]: any } = {} const cache: { [index: string]: any } = {}
export async function post(endpoint: string, params: any, init?: RequestInitWithParams): Promise<any> { export async function post(endpoint: string, params: Record<string, any> = {}, init?: RequestInitWithParams): Promise<any> {
// Add token if exists // Add token if exists
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (token && !('token' in params)) params = { ...(params ?? {}), token } if (token && !('token' in params)) params = { ...(params ?? {}), token }
@@ -301,6 +301,8 @@ export const GAME = {
post(`/api/v2/game/${game}/ranking`, { }), post(`/api/v2/game/${game}/ranking`, { }),
changeName: (game: GameName, newName: string): Promise<{ newName: string }> => changeName: (game: GameName, newName: string): Promise<{ newName: string }> =>
post(`/api/v2/game/${game}/change-name`, { newName }), post(`/api/v2/game/${game}/change-name`, { newName }),
export: (game: GameName): Promise<Record<string, any>> =>
post(`/api/v2/game/${game}/export`),
} }
export const DATA = { export const DATA = {

View File

@@ -108,11 +108,11 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
// Check existing data // Check existing data
userDataRepo.findByCard(u.ghostCard)?.also { gu -> userDataRepo.findByCard(u.ghostCard)?.also { gu ->
// Store a backup of the old data // Store a backup of the old data
val fl = "mai2-backup-${u.auId}-${LocalDateTime.now().urlSafeStr()}.json" val fl = "${game}-backup-${u.auId}-${LocalDateTime.now().urlSafeStr()}.json"
(Path(netProps.importBackupPath) / fl).writeText(export(u).toJson()) (Path(netProps.importBackupPath) / fl).writeText(export(u).toJson())
// Delete the old data (After migration v1000.7, all user-linked entities have ON DELETE CASCADE) // Delete the old data (After migration v1000.7, all user-linked entities have ON DELETE CASCADE)
log.info("Mai2 Import: Deleting old data for user ${u.auId}") log.info("$game Import: Deleting old data for user ${u.auId}")
userDataRepo.delete(gu) userDataRepo.delete(gu)
userDataRepo.flush() userDataRepo.flush()
} }