Add export and import data

pull/1/head
Made Baruna 2021-10-14 05:40:46 +07:00
parent 4b4e4ad252
commit 03fc5c3399
No known key found for this signature in database
GPG Key ID: 5AA5DA16AA5DCEAD
8 changed files with 156 additions and 14 deletions

View File

@ -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>

View File

@ -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,

View File

@ -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}>

View File

@ -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",

View File

@ -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

View 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>

View File

@ -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

View File

@ -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);