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>
|
<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
256
script.js
@@ -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);
|
||||||
|
|
||||||
|
|||||||
22
styles.css
22
styles.css
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user