Allowed for multiple pickers on the same webpage

Customisable MimeTypes
Customisable multifile select
This commit is contained in:
Connor Yoh 2025-03-28 17:00:27 +00:00
parent ebd6aac715
commit eed2029133
4 changed files with 124 additions and 83 deletions

View File

@ -273,10 +273,10 @@
} }
.googleDriveButton { .google-drive-button {
width: 2.5rem; width: 2.5rem;
pointer-events: auto; pointer-events: auto;
} }
.googleDriveButton img { .google-drive-button img {
width:100% width:100%
} }

View File

@ -35,6 +35,7 @@ function setupFileInput(chooser) {
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt'); const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
const inputContainerId = chooser.getAttribute('data-bs-element-container-id'); const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
const showUploads = chooser.getAttribute('data-bs-show-uploads') === "true"; const showUploads = chooser.getAttribute('data-bs-show-uploads') === "true";
const name = chooser.getAttribute('data-bs-unique-id')
let inputContainer = document.getElementById(inputContainerId); let inputContainer = document.getElementById(inputContainerId);
const input = document.getElementById(elementId); const input = document.getElementById(elementId);
@ -145,7 +146,7 @@ function setupFileInput(chooser) {
document.body.addEventListener('dragenter', dragenterListener); document.body.addEventListener('dragenter', dragenterListener);
document.body.addEventListener('dragleave', dragleaveListener); document.body.addEventListener('dragleave', dragleaveListener);
document.body.addEventListener('drop', dropListener); document.body.addEventListener('drop', dropListener);
document.body.addEventListener('googleDriveFilePicked', googleDriveFileListener); document.body.addEventListener(name + 'GoogleDriveDrivePicked', googleDriveFileListener);
$('#' + elementId).on('change', async function (e) { $('#' + elementId).on('change', async function (e) {
let element = e.target; let element = e.target;

View File

@ -1,54 +1,94 @@
const SCOPES = 'https://www.googleapis.com/auth/drive.readonly'; const SCOPES = "https://www.googleapis.com/auth/drive.readonly";
const SESSION_STORAGE_ID = "googleDrivePickerAccessToken" const SESSION_STORAGE_ID = "googleDrivePickerAccessToken";
let tokenClient; let tokenClient;
let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID); let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID);
document.getElementById("google-drive-button").addEventListener('click', onGoogleDriveButtonClick); let isScriptExecuted = false;
if (!isScriptExecuted) {
isScriptExecuted = true;
document.addEventListener("DOMContentLoaded", function () {
document.querySelectorAll(".google-drive-button").forEach(setupGoogleDrivePicker);
});
}
/** function gisLoaded() {
* Callback after api.js is loaded. tokenClient = google.accounts.oauth2.initTokenClient({
*/ client_id: window.stirlingPDF.GoogleDriveClientId,
function gapiLoaded() { scope: SCOPES,
gapi.load('client:picker', initializePicker); callback: "", // defined later
});
}
// add more as needed.
// Google picker is limited on what mimeTypes are supported
// Wild card are not supported
const expandableMimeTypes = {
"image/*" : ["image/jpeg", "image/png","image/svg+xml" ]
}
function fileInputToGooglePickerMimeTypes(accept) {
if(accept == null || accept == "" || accept.includes("*/*")){
// Setting null will accept all supported mimetypes
return null;
} }
/** let mimeTypes = [];
* Callback after the API client is loaded. Loads the accept.split(',').forEach(part => {
* discovery doc to initialize the API. if(!(part in expandableMimeTypes)){
*/ mimeTypes.push(part);
async function initializePicker() { return;
await gapi.client.load('https://www.googleapis.com/discovery/v1/apis/drive/v3/rest'); }
}
/** expandableMimeTypes[part].forEach(mimeType => {
* Callback after Google Identity Services are loaded. mimeTypes.push(mimeType);
*/
function gisLoaded() {
tokenClient = google.accounts.oauth2.initTokenClient({
client_id: CLIENT_ID,
scope: SCOPES,
callback: '', // defined later
}); });
} });
const mimeString = mimeTypes.join(",").replace(/\s+/g, '');
console.log([accept, "became", mimeString]);
return mimeString;
}
/**
* 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");
}
function setupGoogleDrivePicker(picker) {
const name = picker.getAttribute('data-name');
const accept = picker.getAttribute('data-accept');
const multiple = picker.getAttribute('data-multiple') === "true";
const mimeTypes = fileInputToGooglePickerMimeTypes(accept);
picker.addEventListener("click", onGoogleDriveButtonClick);
/**
* Sign in the user upon button click.
*/
function onGoogleDriveButtonClick(e) { function onGoogleDriveButtonClick(e) {
e.stopPropagation(); e.stopPropagation();
tokenClient.callback = (response) => { tokenClient.callback = (response) => {
if (response.error !== undefined) { if (response.error !== undefined) {
throw (response); throw response;
} }
accessToken = response.access_token; accessToken = response.access_token;
sessionStorage.setItem(SESSION_STORAGE_ID, accessToken); sessionStorage.setItem(SESSION_STORAGE_ID, accessToken);
createPicker(); createGooglePicker();
}; };
tokenClient.requestAccessToken({prompt: accessToken === null? 'consent' : ''}); tokenClient.requestAccessToken({ prompt: accessToken === null ? "consent" : "" });
} }
/** /**
@ -56,36 +96,35 @@
*/ */
function signOut() { function signOut() {
if (accessToken) { if (accessToken) {
sessionStorage.removeItem(SESSION_STORAGE_ID); sessionStorage.removeItem(SESSION_STORAGE_ID);
google.accounts.oauth2.revoke(accessToken); google.accounts.oauth2.revoke(accessToken);
accessToken = null; accessToken = null;
} }
} }
/** function createGooglePicker() {
* Create and render a Picker object for searching images. let builder = new google.picker.PickerBuilder()
*/ .setDeveloperKey(window.stirlingPDF.GoogleDriveApiKey)
function createPicker() { .setAppId(window.stirlingPDF.GoogleDriveAppId)
const picker = new google.picker.PickerBuilder() .setOAuthToken(accessToken)
.enableFeature(google.picker.Feature.MULTISELECT_ENABLED) .addView(
.setDeveloperKey(API_KEY) new google.picker.DocsView()
.setAppId(APP_ID) .setIncludeFolders(true)
.setOAuthToken(accessToken) .setMimeTypes(mimeTypes)
.addView( )
new google.picker.DocsView() .addView(
.setIncludeFolders(true) new google.picker.DocsView()
.setMimeTypes('application/pdf,image/png,image/jpg,image/jpeg,image/svg') .setIncludeFolders(true)
) .setEnableDrives(true)
.addView( .setMimeTypes(mimeTypes)
new google.picker.DocsView() )
.setIncludeFolders(true) .setCallback(pickerCallback);
.setMimeTypes('application/pdf,image/*')
.setEnableDrives(true) if(multiple) {
) builder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
.setCallback(pickerCallback) }
.setTitle('Stirling PDF - Google Drive') const picker = builder.build();
.build();
picker.setVisible(true); picker.setVisible(true);
} }
@ -95,23 +134,25 @@
*/ */
async function pickerCallback(data) { async function pickerCallback(data) {
if (data.action === google.picker.Action.PICKED) { if (data.action === google.picker.Action.PICKED) {
const files = await Promise.all(data[google.picker.Response.DOCUMENTS].map(async pickedFile => { const files = await Promise.all(
const fileId = pickedFile[google.picker.Document.ID]; data[google.picker.Response.DOCUMENTS].map(async (pickedFile) => {
console.log(fileId); const fileId = pickedFile[google.picker.Document.ID];
const res = await gapi.client.drive.files.get({ console.log(fileId);
'fileId': fileId, const res = await gapi.client.drive.files.get({
'alt': 'media', 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
} );
return file;
}));
document.body.dispatchEvent(new CustomEvent("googleDriveFilePicked", {detail: files})); let 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,
});
return file;
})
);
document.body.dispatchEvent(new CustomEvent(name+"GoogleDriveDrivePicked", { detail: files }));
} }
} }
}

View File

@ -245,7 +245,7 @@
<div th:text="#{fileChooser.or}" style="margin-right: 5px"></div> <div th:text="#{fileChooser.or}" style="margin-right: 5px"></div>
<div th:text="#{fileChooser.dragAndDrop}" id="dragAndDrop"></div> <div th:text="#{fileChooser.dragAndDrop}" id="dragAndDrop"></div>
</div> </div>
<div th:if="${@GoogleDriveEnabled == true}" id="google-drive-button" class="googleDriveButton"> <div th:if="${@GoogleDriveEnabled == true}" th:id="${name}+'-google-drive-button'" class="google-drive-button" th:attr="data-name=${name}, data-multiple=${!disableMultipleFiles}, data-accept=${accept}" >
<img th:src="@{'/images/google-drive.svg'}" alt="google drive"> <img th:src="@{'/images/google-drive.svg'}" alt="google drive">
</div> </div>
</div> </div>
@ -268,10 +268,9 @@
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script> <script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
<script th:inline="javascript"> <script th:inline="javascript">
const CLIENT_ID = /*[[${@GoogleDriveConfig.getClientId()}]]*/ null; window.stirlingPDF.GoogleDriveClientId = /*[[${@GoogleDriveConfig.getClientId()}]]*/ null;
const API_KEY = /*[[${@GoogleDriveConfig.getApiKey()}]]*/ null; window.stirlingPDF.GoogleDriveApiKey = /*[[${@GoogleDriveConfig.getApiKey()}]]*/ null;
const APP_ID = /*[[${@GoogleDriveConfig.getAppId()}]]*/ null; window.stirlingPDF.GoogleDriveAppId = /*[[${@GoogleDriveConfig.getAppId()}]]*/ null;
</script> </script>
</div> </div>
</th:block> </th:block>