From 9362bee73ba0eacd1479a8667e09b155dfa20e53 Mon Sep 17 00:00:00 2001 From: Connor Yoh Date: Wed, 26 Mar 2025 18:00:14 +0000 Subject: [PATCH] Basic prototype --- .../software/SPDF/config/AppConfig.java | 21 ++++ .../SPDF/model/ApplicationProperties.java | 21 ++++ src/main/resources/settings.yml.template | 5 + src/main/resources/static/css/fileSelect.css | 11 ++ .../resources/static/images/google-drive.svg | 1 + src/main/resources/static/js/fileInput.js | 18 +++ .../resources/static/js/googleFilePicker.js | 111 ++++++++++++++++++ .../resources/templates/fragments/common.html | 16 +++ 8 files changed, 204 insertions(+) create mode 100644 src/main/resources/static/images/google-drive.svg create mode 100644 src/main/resources/static/js/googleFilePicker.js diff --git a/src/main/java/stirling/software/SPDF/config/AppConfig.java b/src/main/java/stirling/software/SPDF/config/AppConfig.java index f741a05a4..3c9f7b8b4 100644 --- a/src/main/java/stirling/software/SPDF/config/AppConfig.java +++ b/src/main/java/stirling/software/SPDF/config/AppConfig.java @@ -195,4 +195,25 @@ public class AppConfig { public String uuid() { return applicationProperties.getAutomaticallyGenerated().getUUID(); } + + @Bean(name = "GoogleDriveEnabled") + public boolean googleDriveEnabled() { + return applicationProperties.getPremium().isEnabled() + && applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled(); + } + + @Bean(name = "GoogleDriveClientId") + public String googleDriveClientId() { + return applicationProperties.getPremium().getProFeatures().getGoogleDrive().getClientId(); + } + + @Bean(name = "GoogleDriveApiKey") + public String googleDriveApiKey() { + return applicationProperties.getPremium().getProFeatures().getGoogleDrive().getApiKey(); + } + + @Bean(name = "GoogleDriveAppId") + public String googleDriveAppId() { + return applicationProperties.getPremium().getProFeatures().getGoogleDrive().getAppId(); + } } diff --git a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java index 36f6f82b7..c93f5f158 100644 --- a/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java +++ b/src/main/java/stirling/software/SPDF/model/ApplicationProperties.java @@ -431,6 +431,7 @@ public class ApplicationProperties { public static class ProFeatures { private boolean ssoAutoLogin; private CustomMetadata customMetadata = new CustomMetadata(); + private GoogleDrive googleDrive = new GoogleDrive(); @Data public static class CustomMetadata { @@ -449,6 +450,26 @@ public class ApplicationProperties { : producer; } } + + @Data + public static class GoogleDrive { + private boolean enabled; + private String clientId; + private String apiKey; + private String appId; + + public String getClientId() { + return clientId == null || clientId.trim().isEmpty() ? "" : clientId; + } + + public String getApiKey() { + return apiKey == null || apiKey.trim().isEmpty() ? "" : apiKey; + } + + public String getAppId() { + return appId == null || appId.trim().isEmpty() ? "" : appId; + } + } } @Data diff --git a/src/main/resources/settings.yml.template b/src/main/resources/settings.yml.template index b7306861c..9066b29b2 100644 --- a/src/main/resources/settings.yml.template +++ b/src/main/resources/settings.yml.template @@ -72,6 +72,11 @@ premium: author: username creator: Stirling-PDF producer: Stirling-PDF + googleDrive: + enabled: false + clientId: '' + apiKey: '' + appId: '' legal: termsAndConditions: https://www.stirlingpdf.com/terms-and-conditions # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder diff --git a/src/main/resources/static/css/fileSelect.css b/src/main/resources/static/css/fileSelect.css index afb0b075f..7e5553443 100644 --- a/src/main/resources/static/css/fileSelect.css +++ b/src/main/resources/static/css/fileSelect.css @@ -271,3 +271,14 @@ align-items: center; z-index: 9999; } + + +.googleDriveButton { + position: absolute; + bottom: 15px; + right: 5px; + width: 5%; +} +.googleDriveButton img { + width:100% +} \ No newline at end of file diff --git a/src/main/resources/static/images/google-drive.svg b/src/main/resources/static/images/google-drive.svg new file mode 100644 index 000000000..03b2f2129 --- /dev/null +++ b/src/main/resources/static/images/google-drive.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/static/js/fileInput.js b/src/main/resources/static/js/fileInput.js index 7e89e6cc3..6e0128e87 100644 --- a/src/main/resources/static/js/fileInput.js +++ b/src/main/resources/static/js/fileInput.js @@ -80,6 +80,23 @@ function setupFileInput(chooser) { overlay = false; } + const googleDriveFileListener = function (e) { + const googleDriveFile = e.detail; + + const fileInput = document.getElementById(elementId); + if (fileInput?.hasAttribute('multiple')) { + allFiles.push(googleDriveFile); + } else if (fileInput) { + allFiles = [googleDriveFile]; + } + + const dataTransfer = new DataTransfer(); + allFiles.forEach((file) => dataTransfer.items.add(file)); + + fileInput.files = dataTransfer.files; + + fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } })); + } const dropListener = function (e) { e.preventDefault(); @@ -130,6 +147,7 @@ function setupFileInput(chooser) { document.body.addEventListener('dragenter', dragenterListener); document.body.addEventListener('dragleave', dragleaveListener); document.body.addEventListener('drop', dropListener); + document.body.addEventListener('googleDriveFilePicked', googleDriveFileListener); $('#' + elementId).on('change', async function (e) { let element = e.target; diff --git a/src/main/resources/static/js/googleFilePicker.js b/src/main/resources/static/js/googleFilePicker.js new file mode 100644 index 000000000..042242699 --- /dev/null +++ b/src/main/resources/static/js/googleFilePicker.js @@ -0,0 +1,111 @@ + const SCOPES = 'https://www.googleapis.com/auth/drive.readonly'; + const SESSION_STORAGE_ID = "googleDrivePickerAccessToken" + + let tokenClient; + let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID); + + /** + * Callback after api.js is loaded. + */ + function gapiLoaded() { + gapi.load('client:picker', initializePicker); + } + + /** + * Callback after the API client is loaded. Loads the + * discovery doc to initialize the API. + */ + async function initializePicker() { + await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'); + } + + /** + * Callback after Google Identity Services are loaded. + */ + function gisLoaded() { + tokenClient = google.accounts.oauth2.initTokenClient({ + client_id: CLIENT_ID, + scope: SCOPES, + callback: '', // defined later + }); + } + + /** + * Sign in the user upon button click. + */ + function handleAuthClick() { + + tokenClient.callback = (response) => { + if (response.error !== undefined) { + throw (response); + } + accessToken = response.access_token; + sessionStorage.setItem(SESSION_STORAGE_ID, accessToken); + createPicker(); + }; + + tokenClient.requestAccessToken({prompt: accessToken === null? 'consent' : ''}); + } + + /** + * Sign out the user upon button click. + */ + function signOut() { + if (accessToken) { + + sessionStorage.removeItem(SESSION_STORAGE_ID); + google.accounts.oauth2.revoke(accessToken); + accessToken = null; + } + } + + /** + * Create and render a Picker object for searching images. + */ + function createPicker() { + const picker = new google.picker.PickerBuilder() + .enableFeature(google.picker.Feature.MULTISELECT_ENABLED) + .setDeveloperKey(API_KEY) + .setAppId(APP_ID) + .setOAuthToken(accessToken) + .addView( + new google.picker.DocsView() + .setIncludeFolders(true) + .setMimeTypes('application/pdf,image/png,image/jpg,image/jpeg,image/svg') + ) + .addView( + new google.picker.DocsView() + .setIncludeFolders(true) + .setMimeTypes('application/pdf,image/*') + .setEnableDrives(true) + ) + .setCallback(pickerCallback) + .setTitle('Stirling PDF - Google Drive') + .build(); + picker.setVisible(true); + } + + /** + * Displays the file details of the user's selection. + * @param {object} data - Containers the user selection from the picker + */ + async function pickerCallback(data) { + if (data.action === google.picker.Action.PICKED) { + data[google.picker.Response.DOCUMENTS].forEach(async pickedFile => { + const fileId = pickedFile[google.picker.Document.ID]; + console.log(fileId); + const res = await gapi.client.drive.files.get({ + 'fileId': fileId, + 'alt': 'media', + }); + + var file = new File([new Uint8Array(res.body.length).map((_, i) => res.body.charCodeAt(i))], + pickedFile.name, { + type: pickedFile.mimeType, + lastModified: pickedFile.lastModified, + endings: pickedFile.endings + } ) + document.body.dispatchEvent(new CustomEvent("googleDriveFilePicked", {detail: file})); + }); + } + } \ No newline at end of file diff --git a/src/main/resources/templates/fragments/common.html b/src/main/resources/templates/fragments/common.html index 66fa94b58..2f95ae8b2 100644 --- a/src/main/resources/templates/fragments/common.html +++ b/src/main/resources/templates/fragments/common.html @@ -245,6 +245,9 @@
+ +
+ google drive
@@ -258,4 +261,17 @@ + +
+ + + + + +
\ No newline at end of file