mirror of
https://github.com/MadeBaruna/paimon-moe.git
synced 2025-03-13 11:18:28 +01:00
Add export and import data
This commit is contained in:
parent
4b4e4ad252
commit
03fc5c3399
8 changed files with 156 additions and 14 deletions
|
@ -39,9 +39,11 @@
|
|||
|
||||
<button
|
||||
{disabled}
|
||||
class={`${textColor} border-2 border-white border-opacity-25 ${
|
||||
rounded ? 'rounded-xl' : 'rounded-none'
|
||||
} ${px} ${py} transition duration-100
|
||||
hover:${borderColor} focus:outline-none focus:${borderColor} disabled:opacity-50 disabled:border-gray-600 ${className}`}
|
||||
on:click><slot /></button
|
||||
class="{textColor} border-2 border-white border-opacity-25 {rounded
|
||||
? 'rounded-xl'
|
||||
: 'rounded-none'} {px} {py} transition duration-100
|
||||
hover:{borderColor} focus:outline-none focus:{borderColor} disabled:opacity-50 disabled:border-gray-600 {className}"
|
||||
on:click
|
||||
>
|
||||
<slot />
|
||||
</button>
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import dayjs from 'dayjs';
|
||||
import { onMount, getContext, setContext } from 'svelte';
|
||||
import { driveSignedIn, driveError, driveLoading, saveId, synced } from '../stores/dataSync';
|
||||
import { driveSignedIn, driveError, driveLoading, saveId, synced, driveEmail } from '../stores/dataSync';
|
||||
import { getLocalSaveJson, updateSave, updateTime, UPDATE_TIME_KEY } from '../stores/saveManager';
|
||||
|
||||
import SyncConflictModal from '../components/SyncConflictModal.svelte';
|
||||
|
@ -81,6 +81,8 @@
|
|||
driveLoading.set(false);
|
||||
|
||||
if (finalStatus) {
|
||||
const email = gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile().getEmail();
|
||||
driveEmail.set(email);
|
||||
getFiles();
|
||||
} else {
|
||||
synced.set(true);
|
||||
|
@ -194,6 +196,12 @@
|
|||
const data = await getData();
|
||||
remoteSave = data;
|
||||
|
||||
console.log(JSON.stringify(remoteSave));
|
||||
const remoteSize = new Blob([JSON.stringify(remoteSave)]).size / 1000;
|
||||
|
||||
const localSave = await getLocalSaveJson();
|
||||
const localSize = new Blob([localSave]).size / 1000;
|
||||
|
||||
const remoteTime = dayjs(data[UPDATE_TIME_KEY]);
|
||||
if ($updateTime !== null && remoteTime.diff($updateTime) !== 0) {
|
||||
console.log('DRIVE SYNC CONFLICT!');
|
||||
|
@ -202,6 +210,8 @@
|
|||
{
|
||||
remoteTime: remoteTime,
|
||||
localTime: $updateTime,
|
||||
remoteSize,
|
||||
localSize,
|
||||
downloadBackup: exportData,
|
||||
useRemote: useRemoteData,
|
||||
useLocal: useLocalData,
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
<script>
|
||||
import { mdiCloudAlert, mdiContentSave, mdiDownload, mdiFile, mdiGoogleDrive, mdiLoading, mdiUpload } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
|
||||
|
||||
import Button from './Button.svelte';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
export let remoteTime;
|
||||
export let localTime;
|
||||
export let remoteSize;
|
||||
export let localSize;
|
||||
export let downloadBackup = () => {};
|
||||
export let useRemote = () => {};
|
||||
export let useLocal = () => {};
|
||||
|
||||
const numberFormat = Intl.NumberFormat('en', {
|
||||
maximumFractionDigits: 1,
|
||||
minimumFractionDigits: 0,
|
||||
});
|
||||
|
||||
let loading = false;
|
||||
|
||||
function useLocalData() {
|
||||
|
@ -38,7 +45,10 @@
|
|||
<p class="font-bold">
|
||||
<Icon path={mdiGoogleDrive} className="mr-2" />
|
||||
{$t('sync.googleDriveData')} -
|
||||
<span class={remoteNewer ? 'text-green-400' : 'text-red-400'}>{remoteNewer ? $t('sync.newer') : $t('sync.older')}</span>
|
||||
<span class={remoteNewer ? 'text-green-400' : 'text-red-400'}>
|
||||
{remoteNewer ? $t('sync.newer') : $t('sync.older')}
|
||||
</span>
|
||||
<span>- {numberFormat.format(remoteSize)} KB</span>
|
||||
</p>
|
||||
<p class="text-gray-400 mt-1">{$t('sync.lastModified')}: {remoteFormatted}</p>
|
||||
<Button disabled={loading} className="mt-2 w-full" on:click={useRemoteData}>
|
||||
|
@ -50,7 +60,10 @@
|
|||
<p class="font-bold">
|
||||
<Icon path={mdiFile} className="mr-2" />
|
||||
{$t('sync.localData')} -
|
||||
<span class={remoteNewer ? 'text-red-400' : 'text-green-400'}>{remoteNewer ? $t('sync.older') : $t('sync.newer')}
|
||||
<span class={remoteNewer ? 'text-red-400' : 'text-green-400'}>
|
||||
{remoteNewer ? $t('sync.older') : $t('sync.newer')}
|
||||
</span>
|
||||
<span>- {numberFormat.format(localSize)} KB</span>
|
||||
</p>
|
||||
<p class="text-gray-400 mt-1">{$t('sync.lastModified')}: {localFormatted}</p>
|
||||
<Button disabled={loading} className="mt-2 w-full" on:click={useLocalData}>
|
||||
|
|
|
@ -564,6 +564,7 @@
|
|||
"synced": "Synced",
|
||||
"waiting": "Waiting...",
|
||||
"syncing": "Syncing...",
|
||||
"syncStatus": "Sync Status:",
|
||||
"lastSync": "Last Sync:",
|
||||
"feedback": "If you found any bugs, wrong data, or any other feedback, please leave a message on",
|
||||
"or": "or",
|
||||
|
@ -575,7 +576,18 @@
|
|||
"delete": "Delete",
|
||||
"reset": "Reset"
|
||||
},
|
||||
"changelog": "Changelog"
|
||||
"changelog": "Changelog",
|
||||
"exportDescription": "You can export & import your Paimon.moe data here",
|
||||
"exportButton": "Export & Import Data",
|
||||
"export": "Export Data",
|
||||
"download": "Download Data",
|
||||
"import": "Import Data",
|
||||
"selectFile": "Select JSON File",
|
||||
"exportInfo": "Download a copy of all your data saved in Paimon.moe",
|
||||
"importWarning": "Remember to backup your data by exporting first! All data will be replaced!",
|
||||
"importContinue": "Continue",
|
||||
"importSuccess": "Import Success! Reloading in 5 seconds...",
|
||||
"importFailed": "Import Failed!"
|
||||
},
|
||||
"privacypolicy": {
|
||||
"title": "Privacy Policy",
|
||||
|
|
|
@ -13,6 +13,8 @@
|
|||
- Update weapons
|
||||
- Update achievements
|
||||
- Update wish tally
|
||||
- Add file size to Google Drive sync conflict popup
|
||||
- Add export and import data function
|
||||
|
||||
2021/09/30
|
||||
- Add Fishing book
|
||||
|
|
73
src/routes/settings/_importExportModal.svelte
Normal file
73
src/routes/settings/_importExportModal.svelte
Normal file
|
@ -0,0 +1,73 @@
|
|||
<script>
|
||||
import { mdiLoading } from '@mdi/js';
|
||||
|
||||
import { t } from 'svelte-i18n';
|
||||
import Button from '../../components/Button.svelte';
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
import { getLocalSaveJson, updateSave } from '../../stores/saveManager';
|
||||
import { pushToast } from '../../stores/toast';
|
||||
|
||||
let input;
|
||||
let files = null;
|
||||
let loading = false;
|
||||
|
||||
async function exportData() {
|
||||
downloadData(await getLocalSaveJson(), 'paimon-moe-local-data');
|
||||
}
|
||||
|
||||
function downloadData(data, name) {
|
||||
const fileLink = document.createElement('a');
|
||||
|
||||
const filename = `${name}.json`;
|
||||
const dataStr = encodeURIComponent(data);
|
||||
|
||||
fileLink.setAttribute('href', `data:text/json;charset=utf-8,${dataStr}`);
|
||||
fileLink.setAttribute('download', filename);
|
||||
document.body.appendChild(fileLink);
|
||||
fileLink.click();
|
||||
}
|
||||
|
||||
async function importData() {
|
||||
loading = true;
|
||||
const reader = new FileReader();
|
||||
reader.onload = async () => {
|
||||
try {
|
||||
const data = JSON.parse(reader.result);
|
||||
for (const key in data) {
|
||||
await updateSave(key, data[key], true);
|
||||
}
|
||||
} catch (err) {
|
||||
pushToast($t('settings.importFailed'), 'error');
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsText(files[0]);
|
||||
loading = false;
|
||||
pushToast($t('settings.importSuccess'));
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 5000);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="bg-background rounded-xl p-4 mb-4">
|
||||
<p class="text-white font-bold">{$t('settings.export')}</p>
|
||||
<p class="text-gray-400 mb-2">{$t('settings.exportInfo')}</p>
|
||||
<Button on:click={exportData}>{$t('settings.download')}</Button>
|
||||
</div>
|
||||
<div class="bg-background rounded-xl p-4">
|
||||
<p class="text-white font-bold">{$t('settings.import')}</p>
|
||||
<p class="text-red-400 mb-2">{$t('settings.importWarning')}</p>
|
||||
{#if !loading}
|
||||
<div class="flex">
|
||||
<Button className="mr-2 overflow-hidden whitespace-no-wrap" on:click={() => input.click()}>
|
||||
{files !== null && files[0] ? files[0].name : $t('settings.selectFile')}
|
||||
</Button>
|
||||
{#if files !== null && files[0]}
|
||||
<Button on:click={importData}>{$t('settings.importContinue')}</Button>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
{#if loading}<Icon path={mdiLoading} color="white" spin />{/if}
|
||||
<input bind:this={input} bind:files type="file" class="hidden" />
|
||||
</div>
|
|
@ -2,9 +2,8 @@
|
|||
import { t } from 'svelte-i18n';
|
||||
import localforage from 'localforage';
|
||||
|
||||
import { mdiCheckCircleOutline, mdiChevronDown, mdiDiscord, mdiGithub, mdiGoogleDrive, mdiLoading } from '@mdi/js';
|
||||
import { mdiCheckCircleOutline, mdiDiscord, mdiGithub, mdiGoogleDrive, mdiLoading, mdiSwapVertical } from '@mdi/js';
|
||||
import { getContext, onMount } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
|
||||
import Button from '../../components/Button.svelte';
|
||||
import Icon from '../../components/Icon.svelte';
|
||||
|
@ -13,8 +12,17 @@
|
|||
import DeleteAccountModal from './_deleteAccount.svelte';
|
||||
import ResetAccountModal from './_resetAccount.svelte';
|
||||
import DonateModal from '../../components/DonateModal.svelte';
|
||||
import ExportImportModal from './_importExportModal.svelte';
|
||||
|
||||
import { driveSignedIn, driveError, driveLoading, synced, localModified, lastSyncTime } from '../../stores/dataSync';
|
||||
import {
|
||||
driveSignedIn,
|
||||
driveError,
|
||||
driveLoading,
|
||||
synced,
|
||||
localModified,
|
||||
lastSyncTime,
|
||||
driveEmail,
|
||||
} from '../../stores/dataSync';
|
||||
import { server, ar, wl } from '../../stores/server';
|
||||
import { accounts, getAccountPrefix, selectedAccount } from '../../stores/account';
|
||||
import { pushToast } from '../../stores/toast';
|
||||
|
@ -240,6 +248,17 @@
|
|||
);
|
||||
}
|
||||
|
||||
function openImportExportModal() {
|
||||
openModal(
|
||||
ExportImportModal,
|
||||
{},
|
||||
{
|
||||
closeButton: false,
|
||||
styleWindow: { background: '#25294A', width: '500px' },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
mounted = true;
|
||||
});
|
||||
|
@ -319,8 +338,9 @@
|
|||
<Icon path={mdiGoogleDrive} className="mr-2" />
|
||||
{$t('settings.driveSignOut')}
|
||||
</Button>
|
||||
<p class="text-white mt-4">{$driveEmail}</p>
|
||||
<p class="text-white mt-4">
|
||||
Sync Status:
|
||||
{$t('settings.syncStatus')}
|
||||
<span class={`font-bold ${isSynced ? 'text-green-400' : 'text-yellow-400'}`}>
|
||||
{isSynced
|
||||
? $t('settings.synced')
|
||||
|
@ -339,6 +359,15 @@
|
|||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="bg-item rounded-xl mb-4 p-4 text-white">
|
||||
<p class="text-white mb-2">{$t('settings.exportDescription')}</p>
|
||||
<div class="flex">
|
||||
<Button className="mr-4" on:click={openImportExportModal}>
|
||||
<Icon path={mdiSwapVertical} className="mr-2" />
|
||||
{$t('settings.exportButton')}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-item rounded-xl mb-4 p-4 text-white">
|
||||
{$t('settings.feedback')}
|
||||
<a
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { writable } from 'svelte/store';
|
||||
|
||||
export const driveSignedIn = writable(false);
|
||||
export const driveEmail = writable('');
|
||||
export const driveLoading = writable(true);
|
||||
export const driveError = writable(false);
|
||||
export const lastSyncTime = writable(null);
|
||||
|
|
Loading…
Add table
Reference in a new issue