Skip to content

Commit 9ad7985

Browse files
committed
Cloud Sync Preparation #60
1 parent 8655415 commit 9ad7985

File tree

6 files changed

+361
-0
lines changed

6 files changed

+361
-0
lines changed

src/lib/helpers/dataAPI/google-api.js

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { API_HOST, GOOGLE_API_KEY, GOOGLE_CLIENT_ID } from '$lib/env';
2+
import { cookie } from '$lib/store/cookie';
3+
import { isSignedIn, userEmail } from '$lib/store/drive-store';
4+
5+
let gapi = {};
6+
let google = {};
7+
let gsiToken = {};
8+
9+
let TOKEN = '';
10+
const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'];
11+
const SCOPES = 'https://www.googleapis.com/auth/drive.appdata';
12+
13+
// export const GAPI = {
14+
// async _initClient() {
15+
// await gapi.client.init({
16+
// apiKey: GOOGLE_API_KEY,
17+
// discoveryDocs: DISCOVERY_DOCS
18+
// });
19+
20+
// const gapiToken = gapi.client.getToken();
21+
// console.log({ gapiToken });
22+
// if (gapiToken === null) return;
23+
// return gsiToken.requestAccessToken({ prompt: '' });
24+
// },
25+
26+
// async _getToken(err) {
27+
// // Errors unrelated to authorization: server errors, exceeding quota, bad requests, and so on.
28+
// const { code, status } = err.result.error;
29+
// const isError = code === 401 || (code === 403 && status == 'PERMISSION_DENIED');
30+
// if (isError) throw new Error(err);
31+
32+
// // The access token is missing, invalid, or expired, prompt for user consent to obtain one.
33+
// await new Promise((resolve, reject) => {
34+
// try {
35+
// // Settle this promise in the response callback for requestAccessToken()
36+
// gsiToken.callback = (response) => {
37+
// if (response.error !== undefined) return reject(response);
38+
// console.log('gapi.client access token: ' + JSON.stringify(gapi.client.getToken()));
39+
// resolve(response);
40+
// };
41+
// gsiToken.requestAccessToken();
42+
// } catch (err) {
43+
// console.log(err);
44+
// }
45+
// });
46+
// },
47+
48+
// async fileHandle(response) {
49+
// if (response.error !== undefined) throw response;
50+
// console.log('rrr', response);
51+
// GAPI._getFile();
52+
// },
53+
54+
// async _getFile() {
55+
// try {
56+
// const { result = {} } = await gapi.client.drive.files.list({
57+
// spaces: 'appDataFolder',
58+
// q: "name = 'Genshin-WishSimulatorApp.json'"
59+
// });
60+
61+
// const { files } = result;
62+
// if (!files || files.length == 0) {
63+
// console.log('no Files');
64+
// return;
65+
// }
66+
// } catch (err) {
67+
// console.error(err);
68+
// }
69+
// },
70+
71+
// scriptLoaded() {
72+
// gapi = window.gapi;
73+
// gapi.load('client', GAPI._initClient);
74+
// },
75+
76+
// scriptError() {
77+
// console.log('error');
78+
// }
79+
// };
80+
81+
const GSI = {
82+
scriptLoaded() {
83+
google = window.google;
84+
gsiToken = google.accounts.oauth2.initCodeClient({
85+
client_id: GOOGLE_CLIENT_ID,
86+
scope: SCOPES,
87+
ux_mode: 'popup',
88+
callback: GSI._getToken
89+
});
90+
91+
GSI._refreshToken();
92+
},
93+
94+
async _getToken(response) {
95+
try {
96+
if (response.error !== undefined) return;
97+
98+
const { code } = response;
99+
const tokenData = await fetch(API_HOST + '/auth/signin', {
100+
method: 'POST',
101+
body: JSON.stringify({ code })
102+
});
103+
104+
const { access_token, refresh_token, userData } = await tokenData.json();
105+
cookie.set('rToken', refresh_token);
106+
TOKEN = access_token;
107+
108+
isSignedIn.set(true);
109+
userEmail.set(userData.email);
110+
} catch (e) {
111+
console.error(e);
112+
}
113+
},
114+
115+
async _refreshToken() {
116+
try {
117+
const refresh_token = cookie.get('rToken');
118+
if (!refresh_token) return;
119+
120+
const tokenData = await fetch(API_HOST + '/auth/refresh', {
121+
method: 'POST',
122+
body: JSON.stringify({ refresh_token })
123+
});
124+
const { access_token, userData } = await tokenData.json();
125+
TOKEN = access_token;
126+
127+
isSignedIn.set(true);
128+
userEmail.set(userData.email);
129+
} catch (e) {
130+
console.error(e);
131+
}
132+
}
133+
};
134+
135+
export const DRIVEAPI = {
136+
sync() {
137+
//
138+
}
139+
};
140+
141+
export const signIn = () => {
142+
gsiToken.requestCode({ prompt: 'consent' });
143+
return;
144+
};
145+
146+
export const signOut = async () => {
147+
try {
148+
const token = cookie.get('rToken');
149+
if (token === null) return;
150+
151+
await google.accounts.oauth2.revoke(token);
152+
cookie.remove('rToken');
153+
isSignedIn.set(false);
154+
userEmail.set('');
155+
} catch (e) {
156+
console.error(e);
157+
}
158+
};
159+
160+
export const initializeDriveAPI = () => {
161+
const { scriptLoaded: gsiLoaded } = GSI;
162+
const gsiScript = document.createElement('script');
163+
gsiScript.src = 'https://accounts.google.com/gsi/client';
164+
gsiScript.onload = gsiLoaded;
165+
// gsiScript.onerror = gapiError;
166+
document.head.appendChild(gsiScript);
167+
168+
// const { scriptError: gapiError, scriptLoaded: gapiLoaded } = GAPI;
169+
// const gapiScript = document.createElement('script');
170+
// gapiScript.src = 'https://apis.google.com/js/api.js';
171+
// gapiScript.onload = gapiLoaded;
172+
// gapiScript.onerror = gapiError;
173+
// document.head.appendChild(gapiScript);
174+
};
175+

src/lib/store/drive-store.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { writable } from 'svelte/store';
2+
3+
export const isSignedIn = writable(false);
4+
export const syncProccess = writable(false);
5+
export const syncSuccess = writable(false);
6+
export const userEmail = writable('');
7+

src/routes/_menu/BackupRestore.svelte

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<script>
2+
import { fade } from 'svelte/transition';
3+
import { assets } from '$lib/store/app-stores';
4+
import { playSfx } from '$lib/helpers/audio/audio';
5+
import SyncCloud from './_sync-cloud.svelte';
6+
7+
let activeSync = 'local';
8+
const buttonNavigation = (section) => {
9+
if (activeSync === section) return;
10+
activeSync = section;
11+
playSfx('shopnav');
12+
};
13+
</script>
14+
15+
<div
16+
class="backupRestore content-container"
17+
in:fade={{ duration: 200 }}
18+
style="--bg-icon:url('{$assets['modal-bg-icon.png']}')"
19+
>
20+
<nav>
21+
<button class:active={activeSync === 'cloud'} on:click={() => buttonNavigation('cloud')}>
22+
<i class="gi-drive" />
23+
<span> Cloud Sync</span>
24+
</button>
25+
</nav>
26+
<div class="content">
27+
{#if activeSync === 'cloud'}
28+
<SyncCloud />
29+
{/if}
30+
</div>
31+
</div>
32+
33+
<style>
34+
.content-container {
35+
background-color: #fbf6ee;
36+
background-image: var(--bg-icon);
37+
background-repeat: no-repeat;
38+
background-size: 50%;
39+
background-position: 50% 60%;
40+
border-radius: 0.5rem;
41+
color: var(--text-color);
42+
overflow: hidden;
43+
}
44+
45+
nav {
46+
width: 100%;
47+
display: flex;
48+
position: relative;
49+
z-index: +1;
50+
}
51+
nav::before {
52+
content: '';
53+
position: absolute;
54+
width: 100%;
55+
height: 50%;
56+
top: 0;
57+
left: 0;
58+
background-color: rgb(51, 57, 71);
59+
z-index: -1;
60+
}
61+
62+
nav button {
63+
width: 100%;
64+
flex: 1;
65+
flex-basis: 50%;
66+
padding: 1rem;
67+
background-color: rgb(51, 57, 71);
68+
color: #fff;
69+
transition: all 0.25s;
70+
display: inline-flex;
71+
align-items: center;
72+
justify-content: center;
73+
}
74+
nav button i {
75+
font-size: larger;
76+
line-height: 0;
77+
transform: translateX(-25%);
78+
}
79+
80+
nav button.active {
81+
background-color: #fbf6ee;
82+
color: initial;
83+
}
84+
85+
nav button:nth-child(1) {
86+
border-bottom-right-radius: 1.75rem;
87+
}
88+
nav button.active:nth-child(1) {
89+
border-bottom-right-radius: unset;
90+
border-top-right-radius: 1.75rem;
91+
}
92+
93+
nav button:nth-child(2) {
94+
border-bottom-left-radius: 1.75rem;
95+
}
96+
nav button.active:nth-child(2) {
97+
border-bottom-left-radius: unset;
98+
border-top-left-radius: 1.75rem;
99+
}
100+
101+
.content {
102+
width: 100%;
103+
height: 100%;
104+
}
105+
</style>

src/routes/_menu/_sidebar.svelte

+4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@
2525
<button on:click={() => selectMenu('updates')}> {$t('menu.updates')} </button>
2626
</div>
2727

28+
<div class="menu-item" class:active={activeContent === 'backupRestore'}>
29+
<button on:click={() => selectMenu('backupRestore')}> {$t('menu.backupRestore')} </button>
30+
</div>
31+
2832
<div class="menu-item">
2933
<button on:click={chatToggle}> {$t('menu.feedback')} </button>
3034
</div>

src/routes/_menu/_sync-cloud.svelte

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
<script>
2+
import { fade } from 'svelte/transition';
3+
import { signIn } from '$lib/helpers/dataAPI/google-api';
4+
</script>
5+
6+
<div class="cloud-sync" in:fade>
7+
<caption>
8+
You don't actually need to back up your data since there is no critical information required or
9+
generated within this simulator. The Backup/Restore feature becomes more useful if you have
10+
custom banners to edit on different devices. However, the decision is ultimately yours.
11+
</caption>
12+
<button type="button" class="google-sign-in-button" disabled> Sign in with Google </button>
13+
<small style="font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;">
14+
<i>Cloud Sync is not yet available</i>
15+
</small>
16+
</div>
17+
18+
<style>
19+
.cloud-sync {
20+
width: 100%;
21+
height: 100%;
22+
display: flex;
23+
justify-content: center;
24+
align-items: center;
25+
flex-direction: column;
26+
}
27+
28+
caption {
29+
display: block;
30+
padding: 1rem;
31+
}
32+
33+
.google-sign-in-button {
34+
transition: background-color 0.3s, box-shadow 0.3s, transform 0.25s;
35+
padding: 12px 16px 12px 42px;
36+
border: none;
37+
border-radius: 3px;
38+
border: 1px solid rgba(0, 0, 0, 0.25);
39+
color: #757575;
40+
font-size: 14px;
41+
font-weight: 500;
42+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell,
43+
'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;
44+
background-image: url('data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIj48cGF0aCBkPSJNMTcuNiA5LjJsLS4xLTEuOEg5djMuNGg0LjhDMTMuNiAxMiAxMyAxMyAxMiAxMy42djIuMmgzYTguOCA4LjggMCAwIDAgMi42LTYuNnoiIGZpbGw9IiM0Mjg1RjQiIGZpbGwtcnVsZT0ibm9uemVybyIvPjxwYXRoIGQ9Ik05IDE4YzIuNCAwIDQuNS0uOCA2LTIuMmwtMy0yLjJhNS40IDUuNCAwIDAgMS04LTIuOUgxVjEzYTkgOSAwIDAgMCA4IDV6IiBmaWxsPSIjMzRBODUzIiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNNCAxMC43YTUuNCA1LjQgMCAwIDEgMC0zLjRWNUgxYTkgOSAwIDAgMCAwIDhsMy0yLjN6IiBmaWxsPSIjRkJCQzA1IiBmaWxsLXJ1bGU9Im5vbnplcm8iLz48cGF0aCBkPSJNOSAzLjZjMS4zIDAgMi41LjQgMy40IDEuM0wxNSAyLjNBOSA5IDAgMCAwIDEgNWwzIDIuNGE1LjQgNS40IDAgMCAxIDUtMy43eiIgZmlsbD0iI0VBNDMzNSIgZmlsbC1ydWxlPSJub256ZXJvIi8+PHBhdGggZD0iTTAgMGgxOHYxOEgweiIvPjwvZz48L3N2Zz4=');
45+
background-color: white;
46+
background-repeat: no-repeat;
47+
background-position: 12px 11px;
48+
}
49+
50+
.google-sign-in-button:hover {
51+
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.04), 0 2px 4px rgba(0, 0, 0, 0.25);
52+
}
53+
54+
.google-sign-in-button:not(:disabled):active {
55+
background-color: #eeeeee;
56+
transform: scale(0.95);
57+
outline: none;
58+
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.04), 0 2px 4px rgba(0, 0, 0, 0.25), 0 0 0 3px #c8dafc;
59+
}
60+
61+
.google-sign-in-button:disabled {
62+
filter: grayscale(100%);
63+
background-color: #ebebeb;
64+
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.04), 0 1px 1px rgba(0, 0, 0, 0.25);
65+
cursor: not-allowed;
66+
}
67+
</style>

src/routes/_menu/index.svelte

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import Sidebar from './_sidebar.svelte';
1414
import ProAccess from './ProAccess.svelte';
1515
import CustomBanner from './CustomBanner.svelte';
16+
import BackupRestore from './BackupRestore.svelte';
1617
1718
let activeContent = $editorMode ? 'customBanner' : 'options';
1819
@@ -54,6 +55,8 @@
5455
<ProAccess />
5556
{:else if activeContent === 'customBanner'}
5657
<CustomBanner />
58+
{:else if activeContent === 'backupRestore'}
59+
<BackupRestore />
5760
{/if}
5861

5962
{#if $isMobile && !$mobileMode}

0 commit comments

Comments
 (0)