mirror of
https://github.com/MadeBaruna/paimon-moe.git
synced 2025-03-11 02:39:18 +01:00
WIP: Drive sync flow
This commit is contained in:
parent
d850e0b921
commit
333a7b6928
10 changed files with 247 additions and 38 deletions
|
@ -27,6 +27,7 @@
|
||||||
"@rollup/plugin-replace": "^2.3.4",
|
"@rollup/plugin-replace": "^2.3.4",
|
||||||
"@rollup/plugin-url": "^5.0.0",
|
"@rollup/plugin-url": "^5.0.0",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
|
"dayjs": "^1.9.4",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"postcss": "^8.1.2",
|
"postcss": "^8.1.2",
|
||||||
"postcss-load-config": "^3.0.0",
|
"postcss-load-config": "^3.0.0",
|
||||||
|
@ -37,6 +38,7 @@
|
||||||
"sapper": "^0.28.0",
|
"sapper": "^0.28.0",
|
||||||
"svelte": "^3.17.3",
|
"svelte": "^3.17.3",
|
||||||
"svelte-preprocess": "^4.5.1",
|
"svelte-preprocess": "^4.5.1",
|
||||||
|
"svelte-simple-modal": "^0.6.1",
|
||||||
"tailwindcss": "^1.9.5"
|
"tailwindcss": "^1.9.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount } from 'svelte';
|
// doc: /static/images.save_sync_flow.png
|
||||||
import { driveSignedIn, driveLoading } from '../stores/dataSync';
|
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { onMount, getContext } from 'svelte';
|
||||||
|
import { driveSignedIn, driveLoading, saveId } from '../stores/dataSync';
|
||||||
|
import { getLocalSaveJson, updateSave, updateTime, UPDATE_TIME_KEY } from '../stores/saveManager';
|
||||||
|
|
||||||
|
import SyncConflictModal from '../components/SyncConflictModal.svelte';
|
||||||
|
|
||||||
|
const { open: openModal } = getContext('simple-modal');
|
||||||
|
|
||||||
const CLIENT_ID = __paimon.env.GOOGLE_DRIVE_CLIENT_ID;
|
const CLIENT_ID = __paimon.env.GOOGLE_DRIVE_CLIENT_ID;
|
||||||
const API_KEY = __paimon.env.GOOGLE_DRIVE_API_KEY;
|
const API_KEY = __paimon.env.GOOGLE_DRIVE_API_KEY;
|
||||||
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'];
|
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'];
|
||||||
const SCOPES = 'https://www.googleapis.com/auth/drive.appdata';
|
const SCOPES = 'https://www.googleapis.com/auth/drive.appdata';
|
||||||
|
|
||||||
|
$: localSaveExists = $updateTime !== null;
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const script = document.createElement('script');
|
const script = document.createElement('script');
|
||||||
script.onload = handleClientLoad;
|
script.onload = handleClientLoad;
|
||||||
|
@ -19,7 +29,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSigninStatus(status) {
|
function updateSigninStatus(status) {
|
||||||
console.log('DRIVE signed in:', status);
|
console.log('update drive signed in status:', status);
|
||||||
driveSignedIn.set(status);
|
driveSignedIn.set(status);
|
||||||
driveLoading.set(false);
|
driveLoading.set(false);
|
||||||
|
|
||||||
|
@ -28,7 +38,46 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function copyRemoteToLocal() {
|
||||||
|
try {
|
||||||
|
const remoteSaveData = await getData();
|
||||||
|
for (const k in remoteSaveData) {
|
||||||
|
updateSave(k, remoteSaveData[k], true);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function compareLocalSave() {
|
||||||
|
try {
|
||||||
|
const data = await getData();
|
||||||
|
const remoteTime = dayjs(data[UPDATE_TIME_KEY]);
|
||||||
|
if ($updateTime !== null && remoteTime.diff($updateTime) !== 0) {
|
||||||
|
console.log('DRIVE SYNC CONFLICT!');
|
||||||
|
openModal(
|
||||||
|
SyncConflictModal,
|
||||||
|
{
|
||||||
|
remoteTime: remoteTime,
|
||||||
|
localTime: $updateTime,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeButton: false,
|
||||||
|
closeOnEsc: false,
|
||||||
|
closeOnOuterClick: false,
|
||||||
|
styleWindow: { background: '#25294A' },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if remote save.json exists
|
||||||
async function getFiles() {
|
async function getFiles() {
|
||||||
|
console.log('checking remote file');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { result } = await gapi.client.drive.files.list({
|
const { result } = await gapi.client.drive.files.list({
|
||||||
spaces: 'appDataFolder',
|
spaces: 'appDataFolder',
|
||||||
|
@ -36,14 +85,17 @@
|
||||||
});
|
});
|
||||||
console.log(result);
|
console.log(result);
|
||||||
|
|
||||||
|
// create save.json on remote if not exists
|
||||||
if (result.files.length === 0) {
|
if (result.files.length === 0) {
|
||||||
createFile();
|
await createFile();
|
||||||
} else {
|
} else {
|
||||||
const data = await gapi.client.drive.files.get({
|
saveId.set(result.files[0].id);
|
||||||
fileId: result.files[0].id,
|
|
||||||
alt: 'media',
|
if (localSaveExists) {
|
||||||
});
|
await compareLocalSave();
|
||||||
console.log(data);
|
} else {
|
||||||
|
await copyRemoteToLocal();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
@ -51,6 +103,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFile() {
|
async function createFile() {
|
||||||
|
console.log('creating remote file');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { result } = await gapi.client.drive.files.create({
|
const { result } = await gapi.client.drive.files.create({
|
||||||
resource: {
|
resource: {
|
||||||
|
@ -60,16 +114,45 @@
|
||||||
fields: 'id',
|
fields: 'id',
|
||||||
});
|
});
|
||||||
|
|
||||||
const data = await gapi.client.request({
|
saveId.set(result.id);
|
||||||
path: `/upload/drive/v3/files/${result.id}`,
|
|
||||||
|
if (localSaveExists) {
|
||||||
|
await saveData(getLocalSaveJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getData() {
|
||||||
|
console.log('reading remote file');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { result } = await gapi.client.drive.files.get({
|
||||||
|
fileId: $saveId,
|
||||||
|
alt: 'media',
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveData(data) {
|
||||||
|
console.log('saving to remote file');
|
||||||
|
|
||||||
|
try {
|
||||||
|
await gapi.client.request({
|
||||||
|
path: `/upload/drive/v3/files/${$saveId}`,
|
||||||
method: 'PATCH',
|
method: 'PATCH',
|
||||||
params: {
|
params: {
|
||||||
uploadType: 'media',
|
uploadType: 'media',
|
||||||
},
|
},
|
||||||
body: JSON.stringify({ v: __paimon.env.CURRENT_VERSION }),
|
body: data,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(data);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}
|
}
|
||||||
|
|
48
src/components/SyncConflictModal.svelte
Normal file
48
src/components/SyncConflictModal.svelte
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import { mdiCloudAlert, mdiContentSave, mdiDownload, mdiFile, mdiGoogleDrive, mdiUpload } from '@mdi/js';
|
||||||
|
import Button from './Button.svelte';
|
||||||
|
|
||||||
|
import Icon from './Icon.svelte';
|
||||||
|
|
||||||
|
export let remoteTime;
|
||||||
|
export let localTime;
|
||||||
|
|
||||||
|
const remoteFormatted = remoteTime.format('dddd, MMMM D, YYYY h:mm A');
|
||||||
|
const localFormatted = localTime.format('dddd, MMMM D, YYYY h:mm A');
|
||||||
|
const remoteNewer = remoteTime.isAfter(localTime);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex">
|
||||||
|
<Icon path={mdiCloudAlert} color="white" size={2} />
|
||||||
|
<p class="flex-1 text-white text-lg md:text-xl font-bold ml-4">
|
||||||
|
Your local data is conflicting with the ones stored in the Google Drive!
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4 p-4 bg-item rounded-xl text-white">
|
||||||
|
<p class="font-bold">
|
||||||
|
<Icon path={mdiGoogleDrive} className="mr-2" />
|
||||||
|
Google Drive Data -
|
||||||
|
{remoteNewer ? 'NEWER' : 'OLDER'}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 mt-1">Last modified: {remoteFormatted}</p>
|
||||||
|
<Button className="mt-2 w-full">
|
||||||
|
<Icon path={mdiDownload} className="mr-1" />Replace Local Data
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-white text-center">OR</p>
|
||||||
|
<div class="mt-2 p-4 bg-item rounded-xl text-white">
|
||||||
|
<p class="font-bold">
|
||||||
|
<Icon path={mdiFile} className="mr-2" />
|
||||||
|
Local Data -
|
||||||
|
{remoteNewer ? 'OLDER' : 'NEWER'}
|
||||||
|
</p>
|
||||||
|
<p class="text-gray-400 mt-1">Last modified: {localFormatted}</p>
|
||||||
|
<Button className="mt-2 w-full">
|
||||||
|
<Icon path={mdiUpload} className="mr-1" />Replace Google Drive Data
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div class="flex mt-6 justify-end">
|
||||||
|
<Button className="w-full md:w-auto">
|
||||||
|
<Icon path={mdiContentSave} className="mr-1" />Download Both Data
|
||||||
|
</Button>
|
||||||
|
</div>
|
|
@ -1,12 +1,21 @@
|
||||||
<script>
|
<script>
|
||||||
|
import { onMount } from 'svelte';
|
||||||
|
import Modal from 'svelte-simple-modal';
|
||||||
|
|
||||||
import Tailwind from '../components/Tailwindcss.svelte';
|
import Tailwind from '../components/Tailwindcss.svelte';
|
||||||
import Sidebar from '../components/Sidebar/Sidebar.svelte';
|
import Sidebar from '../components/Sidebar/Sidebar.svelte';
|
||||||
import Header from '../components/Header.svelte';
|
import Header from '../components/Header.svelte';
|
||||||
import DataSync from '../components/DataSync.svelte';
|
import DataSync from '../components/DataSync.svelte';
|
||||||
|
|
||||||
import { showSidebar } from '../stores/sidebar';
|
import { showSidebar } from '../stores/sidebar';
|
||||||
|
import { checkLocalSave } from '../stores/saveManager';
|
||||||
|
|
||||||
export let segment;
|
export let segment;
|
||||||
|
|
||||||
|
// check local storage save on load
|
||||||
|
onMount(() => {
|
||||||
|
checkLocalSave();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -20,12 +29,13 @@
|
||||||
{#if $showSidebar}
|
{#if $showSidebar}
|
||||||
<Sidebar {segment} mobile />
|
<Sidebar {segment} mobile />
|
||||||
{/if}
|
{/if}
|
||||||
|
<Modal>
|
||||||
<main style="flex: 1 0 auto;">
|
<main style="flex: 1 0 auto;">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
<DataSync />
|
||||||
|
</Modal>
|
||||||
<p class="lg:ml-64 px-8 py-4 text-gray-600">
|
<p class="lg:ml-64 px-8 py-4 text-gray-600">
|
||||||
Paimon.moe is not affiliated with miHoYo.<br />
|
Paimon.moe is not affiliated with miHoYo.<br />
|
||||||
Genshin Impact, game content and materials are trademarks and copyrights of miHoYo.
|
Genshin Impact, game content and materials are trademarks and copyrights of miHoYo.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<DataSync />
|
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { mdiGoogleDrive, mdiLoading } from '@mdi/js';
|
import { mdiCheckCircleOutline, mdiGoogleDrive, mdiLoading } from '@mdi/js';
|
||||||
|
|
||||||
import Button from '../components/Button.svelte';
|
import Button from '../components/Button.svelte';
|
||||||
import Icon from '../components/Icon.svelte';
|
import Icon from '../components/Icon.svelte';
|
||||||
import { driveSignedIn, driveLoading } from '../stores/dataSync';
|
import { driveSignedIn, driveLoading, synced } from '../stores/dataSync';
|
||||||
|
|
||||||
function signIn() {
|
function signIn() {
|
||||||
gapi.auth2.getAuthInstance().signIn();
|
gapi.auth2.getAuthInstance().signIn();
|
||||||
|
@ -15,11 +15,13 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="lg:ml-64 pt-20 px-8 lg:pt-8">
|
<div class="lg:ml-64 pt-20 px-8 lg:pt-8">
|
||||||
|
<p class="text-white mb-2">
|
||||||
|
Paimon.moe use Application Data Directory on your Google Drive to save and sync your wish counter and todo list.
|
||||||
|
</p>
|
||||||
|
<p class="text-white mb-4">Paimon.moe can only read and write file that this site create.</p>
|
||||||
{#if $driveLoading}
|
{#if $driveLoading}
|
||||||
<Icon path={mdiLoading} color="white" spin />
|
<Icon path={mdiLoading} color="white" spin />
|
||||||
{:else}
|
{:else if !$driveSignedIn}
|
||||||
<p class="text-white">Drive signed in: {$driveSignedIn}</p>
|
|
||||||
{#if !$driveSignedIn}
|
|
||||||
<Button on:click={signIn}>
|
<Button on:click={signIn}>
|
||||||
<Icon path={mdiGoogleDrive} className="mr-2" />
|
<Icon path={mdiGoogleDrive} className="mr-2" />
|
||||||
Sign in to Google Drive
|
Sign in to Google Drive
|
||||||
|
@ -29,6 +31,16 @@
|
||||||
<Icon path={mdiGoogleDrive} className="mr-2" />
|
<Icon path={mdiGoogleDrive} className="mr-2" />
|
||||||
Sign out Google Drive
|
Sign out Google Drive
|
||||||
</Button>
|
</Button>
|
||||||
|
<p class="text-white mt-4">
|
||||||
|
Sync Status:
|
||||||
|
<span class={`font-bold ${$synced ? 'text-green-400' : 'text-yellow-400'}`}>
|
||||||
|
{$synced ? 'Synced' : 'Syncing...'}
|
||||||
|
{#if $synced}
|
||||||
|
<Icon path={mdiCheckCircleOutline} className="text-green-400" />
|
||||||
|
{:else}
|
||||||
|
<Icon path={mdiLoading} className="text-yellow-400" spin />
|
||||||
{/if}
|
{/if}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import { mdiStar } from '@mdi/js';
|
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
|
import { mdiStar } from '@mdi/js';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
|
||||||
import Button from '../../components/Button.svelte';
|
import Button from '../../components/Button.svelte';
|
||||||
import Icon from '../../components/Icon.svelte';
|
import Icon from '../../components/Icon.svelte';
|
||||||
|
import { readSave, updateSave, fromRemote } from '../../stores/saveManager';
|
||||||
|
|
||||||
export let id = '';
|
export let id = '';
|
||||||
export let name = '';
|
export let name = '';
|
||||||
|
@ -12,13 +14,16 @@
|
||||||
let rare = 0;
|
let rare = 0;
|
||||||
|
|
||||||
$: path = `wish-counter-${id}`;
|
$: path = `wish-counter-${id}`;
|
||||||
|
$: if ($fromRemote) {
|
||||||
|
readLocalData();
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
readLocalData();
|
readLocalData();
|
||||||
});
|
});
|
||||||
|
|
||||||
function readLocalData() {
|
function readLocalData() {
|
||||||
const data = localStorage.getItem(path);
|
const data = readSave(path);
|
||||||
if (data !== null) {
|
if (data !== null) {
|
||||||
const counterData = JSON.parse(data);
|
const counterData = JSON.parse(data);
|
||||||
total = counterData.total;
|
total = counterData.total;
|
||||||
|
@ -27,14 +32,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveData() {
|
const saveData = debounce(() => {
|
||||||
const data = JSON.stringify({
|
const data = JSON.stringify({
|
||||||
total,
|
total,
|
||||||
legendary,
|
legendary,
|
||||||
rare,
|
rare,
|
||||||
});
|
});
|
||||||
localStorage.setItem(path, data);
|
|
||||||
}
|
updateSave(path, data);
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
function add(val) {
|
function add(val) {
|
||||||
if (total + val < 0) return;
|
if (total + val < 0) return;
|
||||||
|
|
|
@ -2,3 +2,5 @@ import { writable } from 'svelte/store';
|
||||||
|
|
||||||
export const driveSignedIn = writable(false);
|
export const driveSignedIn = writable(false);
|
||||||
export const driveLoading = writable(true);
|
export const driveLoading = writable(true);
|
||||||
|
export const synced = writable(true);
|
||||||
|
export const saveId = writable('');
|
||||||
|
|
36
src/stores/saveManager.js
Normal file
36
src/stores/saveManager.js
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
export const updateTime = writable(null);
|
||||||
|
export const fromRemote = writable(false);
|
||||||
|
|
||||||
|
export const UPDATE_TIME_KEY = 'update-time';
|
||||||
|
|
||||||
|
export const checkLocalSave = () => {
|
||||||
|
const localUpdateTime = localStorage.getItem(UPDATE_TIME_KEY);
|
||||||
|
if (localUpdateTime !== null) {
|
||||||
|
updateTime.set(dayjs(localUpdateTime));
|
||||||
|
console.log('local save update time:', localUpdateTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getLocalSaveJson = () => {
|
||||||
|
return JSON.stringify(localStorage);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateSave = (key, data, isFromRemote) => {
|
||||||
|
localStorage.setItem(key, data);
|
||||||
|
|
||||||
|
if (!isFromRemote) {
|
||||||
|
const currentTime = dayjs().toISOString();
|
||||||
|
updateTime.set(currentTime);
|
||||||
|
localStorage.setItem(UPDATE_TIME_KEY, currentTime);
|
||||||
|
} else {
|
||||||
|
fromRemote.set(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const readSave = (key) => {
|
||||||
|
const data = localStorage.getItem(key);
|
||||||
|
return data;
|
||||||
|
};
|
BIN
static/images/save_sync_flow.png
Normal file
BIN
static/images/save_sync_flow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 54 KiB |
10
yarn.lock
10
yarn.lock
|
@ -1278,6 +1278,11 @@ cssesc@^3.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee"
|
||||||
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==
|
||||||
|
|
||||||
|
dayjs@^1.9.4:
|
||||||
|
version "1.9.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.9.4.tgz#fcde984e227f4296f04e7b05720adad2e1071f1b"
|
||||||
|
integrity sha512-ABSF3alrldf7nM9sQ2U+Ln67NRwmzlLOqG7kK03kck0mw3wlSSEKv/XhKGGxUjQcS57QeiCyNdrFgtj9nWlrng==
|
||||||
|
|
||||||
debug@2.6.9:
|
debug@2.6.9:
|
||||||
version "2.6.9"
|
version "2.6.9"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
@ -2386,6 +2391,11 @@ svelte-preprocess@^4.5.1:
|
||||||
detect-indent "^6.0.0"
|
detect-indent "^6.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
|
svelte-simple-modal@^0.6.1:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/svelte-simple-modal/-/svelte-simple-modal-0.6.1.tgz#5e984f384dda16bc50f00846314dc140ad89864b"
|
||||||
|
integrity sha512-GJGYj+jymzuar105fwkZ73dtcSFCordpbHqt53iE1N1GdqhvEmSs24idRzyIcO7TrTD/V/287X1icFXp88RQHQ==
|
||||||
|
|
||||||
svelte@^3.17.3:
|
svelte@^3.17.3:
|
||||||
version "3.29.0"
|
version "3.29.0"
|
||||||
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.0.tgz#80acac4254341ad8f3301e5ef03f4127ea967d96"
|
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.29.0.tgz#80acac4254341ad8f3301e5ef03f4127ea967d96"
|
||||||
|
|
Loading…
Add table
Reference in a new issue