Basic google drive functionality

This commit is contained in:
Benjamin Kyd
2023-09-27 15:30:02 +01:00
parent 7c41119b0f
commit b38a5aaf43
3 changed files with 283 additions and 5 deletions

View File

@@ -19,6 +19,13 @@
<label for="by">Designed by</label> <label for="by">Designed by</label>
<p id="by" contenteditable>edit</p> <p id="by" contenteditable>edit</p>
</div> </div>
<div>
<label for="sign-in">Sync with Google Drive</label>
<button class="google-button" id="sign-in">Sign in</button>
<button class="google-button" id="sign-out">Sign out</button>
<button class="google-button" id="sync-load">Load From Google Drive</button>
<button class="google-button" id="sync-save">Save to Google Drive</button>
</div>
<img id="help" src="i/help.svg" alt="help"> <img id="help" src="i/help.svg" alt="help">
</header> </header>
@@ -50,3 +57,6 @@
<p><span class="key">Ctrl</span>+<span class="key">Escape</span> opens a dialog that will allow you to remove all your text from the page.</p> <p><span class="key">Ctrl</span>+<span class="key">Escape</span> opens a dialog that will allow you to remove all your text from the page.</p>
<button id="usage-close">OK</button> <button id="usage-close">OK</button>
</dialog> </dialog>
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>

256
script.js
View File

@@ -7,7 +7,7 @@ function toggleItem(event) {
event.preventDefault(); event.preventDefault();
const gridItem = event.target.closest('.grid-item'); const gridItem = event.target.closest('.grid-item');
if (event.metaKey || event.shiftKey) { if (event.metaKey || event.shiftKey) {
gridItem?.classList.toggle('lo'); gridItem?.classList.toggle('lo');
} }
const editableChild = gridItem.querySelector('[contenteditable]'); const editableChild = gridItem.querySelector('[contenteditable]');
@@ -25,7 +25,7 @@ function updateURL() {
} }
} }
let fragment = '' let fragment = ''
if (inverseIds.length > 0 && inverseIds.length < 9 ) { if (inverseIds.length > 0 && inverseIds.length < 9) {
fragment = inverseIds.join('-'); fragment = inverseIds.join('-');
} }
setFragment(fragment); setFragment(fragment);
@@ -37,6 +37,7 @@ function setFragment(fragment) {
} }
function saveContent() { function saveContent() {
// save time
mmc.current = []; mmc.current = [];
for (const element of el.editableElements) { for (const element of el.editableElements) {
const parentWithId = element.closest('[id]'); const parentWithId = element.closest('[id]');
@@ -69,6 +70,10 @@ function loadContent() {
handleFragment(); handleFragment();
} }
function isThereContent() {
return mmc.current.length > 0;
}
function handleFragment() { function handleFragment() {
const fragment = window.location.hash.slice(1); const fragment = window.location.hash.slice(1);
if (fragment) { if (fragment) {
@@ -89,7 +94,7 @@ function handleFragment() {
function keyboardHandler(event) { function keyboardHandler(event) {
if (event.ctrlKey && event.key === 'Escape') { if (event.ctrlKey && event.key === 'Escape') {
const confirmed = confirm('Are you sure you want to remove all text from this page?'); const confirmed = confirm('Are you sure you want to remove all text from this page?\nTHIS WILL ALSO DELETE YOUR SYNCED DATA FROM GOOGLE DRIVE!');
if (confirmed) { if (confirmed) {
for (const element of el.editableElements) { for (const element of el.editableElements) {
element.innerHTML = ''; element.innerHTML = '';
@@ -127,7 +132,18 @@ function prep() {
el.help.addEventListener('click', openUsageDialog); el.help.addEventListener('click', openUsageDialog);
el.googleLogin = document.querySelector('#sign-in');
el.googleLogout = document.querySelector('#sign-out');
el.googleSyncLoad = document.querySelector('#sync-load');
el.googleSyncSave = document.querySelector('#sync-save');
el.googleLogin.style.visibility = 'hidden';
el.googleLogout.style.visibility = 'hidden';
el.googleSyncLoad.style.visibility = 'hidden';
el.googleSyncSave.style.visibility = 'hidden';
el.googleLogin.addEventListener('click', handleAuthClick);
el.googleLogout.addEventListener('click', handleSignoutClick);
el.googleSyncLoad.addEventListener('click', loadFromDrive);
el.googleSyncSave.addEventListener('click', saveToDrive);
} }
function openUsageDialog() { function openUsageDialog() {
@@ -138,5 +154,237 @@ function openUsageDialog() {
}); });
} }
/** GOOGLE DRIVE STUFFS **
* What can happen with the sync:
* User has data in local storage, but not in google drive
* User clicks sync
* User has data in google drive, but not in local storage
* google drive data is loaded into local storage
* User has data in both local storage and google drive
* User is prompted to choose which to keep
* User has no data in either local storage or google drive
* User starts a new localStorage session which is synced to google drive
* User has data in both local storage and google drive, but they are the same
* Nothing happens
**/
const CLIENT_ID = '18857136519-p50s7t4q7os98eijoo94g37mcdl9oj66.apps.googleusercontent.com';
const API_KEY = 'AIzaSyC6BHVwaIAMnP5itX3frfPJLgR10s2S-3w';
const DISCOVERY_DOC = 'https://www.googleapis.com/discovery/v1/apis/drive/v3/rest';
const SCOPES = 'https://www.googleapis.com/auth/drive'
let tokenClient;
let gapiInited = false;
let gisInited = false;
/**
* Callback after api.js is loaded.
*/
function gapiLoaded() {
gapi.load('client', initializeGapiClient);
}
/**
* Callback after the API client is loaded. Loads the
* discovery doc to initialize the API.
*/
async function initializeGapiClient() {
await gapi.client.init({
apiKey: API_KEY,
discoveryDocs: [DISCOVERY_DOC],
});
gapiInited = true;
maybeEnableButtons();
}
/**
* Callback after Google Identity Services are loaded.
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
});
gisInited = true;
}
maybeEnableButtons();
/**
* Enables user interaction after all libraries are loaded.
*/
function maybeEnableButtons() {
if (gapiInited && gisInited) {
el.googleLogin.style.visibility = 'visible';
}
}
/**
* Sign in the user upon button click.
*/
function handleAuthClick() {
tokenClient.callback = async (resp) => {
if (resp.error !== undefined) {
throw (resp);
}
el.googleSyncLoad.style.visibility = 'visible';
el.googleSyncSave.style.visibility = 'visible';
el.googleLogout.style.visibility = 'visible';
el.googleLogin.style.visibility = 'hidden';
await loadFromDrive();
};
if (gapi.client.getToken() === null) {
// Prompt the user to select a Google Account and ask for consent to share their data
// when establishing a new session.
tokenClient.requestAccessToken({ prompt: 'consent' });
} else {
// Skip display of account chooser and consent dialog for an existing session.
tokenClient.requestAccessToken({ prompt: '' });
}
}
/**
* Sign out the user upon button click.
*/
function handleSignoutClick() {
const token = gapi.client.getToken();
if (token !== null) {
google.accounts.oauth2.revoke(token.access_token);
gapi.client.setToken('');
document.getElementById('content').innerText = '';
el.googleSyncLoad.style.visibility = 'hidden';
el.googleSyncSave.style.visibility = 'hidden';
el.googleLogin.style.visibility = 'visible';
el.googleLogout.style.visibility = 'hidden';
}
}
/**
* Discover files
*/
async function loadFromDrive() {
// load .mmc file and prompt user to choose which to keep
// check & load .mmc file exists in drive
let response;
try {
response = await gapi.client.drive.files.list({
'fields': 'files(id, name)',
});
} catch (err) {
console.log(err.message);
return;
}
const files = response.result.files;
if (!files || files.length == 0) {
console.log('No files found.');
return;
}
const mmcFiles = files.filter(file => file.name === '.mmc');
// there should only be one .mmc file
// so we'll just ignore any others for now :)
if (mmcFiles.length == 0) {
newToDrive();
return;
}
const mmcFile = mmcFiles[0];
// load .mmc file
try {
response = await gapi.client.drive.files.get({
'fileId': mmcFile.id,
'alt': 'media',
});
} catch (err) {
console.log(err.message);
return;
}
const content = response.body;
const confirmed = confirm('Would you like to load your synced data from Google Drive?\nTHIS WILL OVERWRITE YOUR LOCAL DATA!');
if (confirmed) {
console.log(content);
localStorage.setItem('mmc', content);
Object.assign(mmc, JSON.parse(content));
loadContent();
}
}
async function saveToDrive() {
// save .mmc file and prompt user to choose which to keep
let response;
try {
response = await gapi.client.drive.files.list({
'fields': 'files(id, name)',
});
} catch (err) {
console.log(err.message);
return;
}
const files = response.result.files;
if (!files || files.length == 0) {
console.log('No files found.');
return;
}
const mmcFiles = files.filter(file => file.name === '.mmc');
// there should only be one .mmc file
// so we'll just ignore any others for now :)
if (mmcFiles.length == 0) {
newToDrive();
return;
}
const mmcFile = mmcFiles[0];
const confirmed = confirm('Would you like to save your data to Google Drive?\nTHIS WILL OVERWRITE YOUR SYNCED DATA!');
if (confirmed) {
saveContent();
// save .mmc file
try {
// we have to do this manually because the GAPI does not support BLOB upload
response = await gapi.client.request({
'path': `/upload/drive/v3/files/${mmcFile.id}`,
'method': 'PATCH',
'params': {
'uploadType': 'media',
},
'body': localStorage.getItem('mmc'),
});
} catch (err) {
console.log(err.message);
return;
}
}
}
async function newToDrive() {
// save new .mmc file to drive
let response;
try {
response = await gapi.client.drive.files.create({
'name': '.mmc',
'mimeType': 'text/plain',
});
} catch (err) {
console.log(err.message);
return;
}
mmc.syncid = response.result.id;
}
async function deleteFromDrive() {
// delete .mmc file from drive
let response;
try {
response = await gapi.client.drive.files.delete({
'fileId': mmc.syncid,
});
} catch (err) {
console.log(err.message);
return;
}
}
window.addEventListener('load', prep); window.addEventListener('load', prep);

View File

@@ -246,4 +246,24 @@ dialog button {
strong { strong {
color: var(--superblack); color: var(--superblack);
} }
.google-button {
background: var(--white);
border: 1px solid var(--darkgrey);
border-radius: 0.2em;
padding: 0.1em;
margin: 0.1em;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 0.2em;
cursor: pointer;
transition: all 0.2s linear;
}
.google-button:hover {
transform: scale(1.1);
}