Basic google drive functionality
This commit is contained in:
10
index.html
10
index.html
@@ -19,6 +19,13 @@
|
||||
<label for="by">Designed by</label>
|
||||
<p id="by" contenteditable>edit</p>
|
||||
</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">
|
||||
</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>
|
||||
<button id="usage-close">OK</button>
|
||||
</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
256
script.js
@@ -7,7 +7,7 @@ function toggleItem(event) {
|
||||
event.preventDefault();
|
||||
const gridItem = event.target.closest('.grid-item');
|
||||
if (event.metaKey || event.shiftKey) {
|
||||
gridItem?.classList.toggle('lo');
|
||||
gridItem?.classList.toggle('lo');
|
||||
}
|
||||
|
||||
const editableChild = gridItem.querySelector('[contenteditable]');
|
||||
@@ -25,7 +25,7 @@ function updateURL() {
|
||||
}
|
||||
}
|
||||
let fragment = ''
|
||||
if (inverseIds.length > 0 && inverseIds.length < 9 ) {
|
||||
if (inverseIds.length > 0 && inverseIds.length < 9) {
|
||||
fragment = inverseIds.join('-');
|
||||
}
|
||||
setFragment(fragment);
|
||||
@@ -37,6 +37,7 @@ function setFragment(fragment) {
|
||||
}
|
||||
|
||||
function saveContent() {
|
||||
// save time
|
||||
mmc.current = [];
|
||||
for (const element of el.editableElements) {
|
||||
const parentWithId = element.closest('[id]');
|
||||
@@ -69,6 +70,10 @@ function loadContent() {
|
||||
handleFragment();
|
||||
}
|
||||
|
||||
function isThereContent() {
|
||||
return mmc.current.length > 0;
|
||||
}
|
||||
|
||||
function handleFragment() {
|
||||
const fragment = window.location.hash.slice(1);
|
||||
if (fragment) {
|
||||
@@ -89,7 +94,7 @@ function handleFragment() {
|
||||
|
||||
function keyboardHandler(event) {
|
||||
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) {
|
||||
for (const element of el.editableElements) {
|
||||
element.innerHTML = '';
|
||||
@@ -127,7 +132,18 @@ function prep() {
|
||||
|
||||
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() {
|
||||
@@ -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);
|
||||
|
||||
|
||||
22
styles.css
22
styles.css
@@ -246,4 +246,24 @@ dialog button {
|
||||
|
||||
strong {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user