Skip to content

Commit fd6f090

Browse files
authored
Merge branch 'main' into change_img_pos
2 parents 9f9083f + caf3ecc commit fd6f090

File tree

8 files changed

+263
-5
lines changed

8 files changed

+263
-5
lines changed

src/main/java/stirling/software/SPDF/EE/EEAppConfig.java

+12
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import stirling.software.SPDF.model.ApplicationProperties;
1212
import stirling.software.SPDF.model.ApplicationProperties.EnterpriseEdition;
1313
import stirling.software.SPDF.model.ApplicationProperties.Premium;
14+
import stirling.software.SPDF.model.ApplicationProperties.Premium.ProFeatures.GoogleDrive;
1415

1516
@Configuration
1617
@Order(Ordered.HIGHEST_PRECEDENCE)
@@ -43,6 +44,17 @@ public boolean ssoAutoLogin() {
4344
return applicationProperties.getPremium().getProFeatures().isSsoAutoLogin();
4445
}
4546

47+
@Bean(name = "GoogleDriveEnabled")
48+
public boolean googleDriveEnabled() {
49+
return runningProOrHigher()
50+
&& applicationProperties.getPremium().getProFeatures().getGoogleDrive().isEnabled();
51+
}
52+
53+
@Bean(name = "GoogleDriveConfig")
54+
public GoogleDrive googleDriveConfig() {
55+
return applicationProperties.getPremium().getProFeatures().getGoogleDrive();
56+
}
57+
4658
// TODO: Remove post migration
4759
public void migrateEnterpriseSettingsToPremium(ApplicationProperties applicationProperties) {
4860
EnterpriseEdition enterpriseEdition = applicationProperties.getEnterpriseEdition();

src/main/java/stirling/software/SPDF/model/ApplicationProperties.java

+21
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,7 @@ public static class Premium {
430430
public static class ProFeatures {
431431
private boolean ssoAutoLogin;
432432
private CustomMetadata customMetadata = new CustomMetadata();
433+
private GoogleDrive googleDrive = new GoogleDrive();
433434

434435
@Data
435436
public static class CustomMetadata {
@@ -448,6 +449,26 @@ public String getProducer() {
448449
: producer;
449450
}
450451
}
452+
453+
@Data
454+
public static class GoogleDrive {
455+
private boolean enabled;
456+
private String clientId;
457+
private String apiKey;
458+
private String appId;
459+
460+
public String getClientId() {
461+
return clientId == null || clientId.trim().isEmpty() ? "" : clientId;
462+
}
463+
464+
public String getApiKey() {
465+
return apiKey == null || apiKey.trim().isEmpty() ? "" : apiKey;
466+
}
467+
468+
public String getAppId() {
469+
return appId == null || appId.trim().isEmpty() ? "" : appId;
470+
}
471+
}
451472
}
452473

453474
@Data

src/main/resources/settings.yml.template

+5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ premium:
7272
author: username
7373
creator: Stirling-PDF
7474
producer: Stirling-PDF
75+
googleDrive:
76+
enabled: false
77+
clientId: ''
78+
apiKey: ''
79+
appId: ''
7580

7681
legal:
7782
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

src/main/resources/static/css/fileSelect.css

+25
Original file line numberDiff line numberDiff line change
@@ -271,3 +271,28 @@
271271
align-items: center;
272272
z-index: 9999;
273273
}
274+
275+
.google-drive-button {
276+
width: 2.5rem;
277+
pointer-events: auto;
278+
cursor: pointer;
279+
transition-duration: 0.4s;
280+
border-radius: 0.5rem;
281+
box-shadow: 0 0 5px var(--md-sys-color-on-surface-variant);
282+
background-color: var(--md-sys-color-on-surface-container-high)
283+
}
284+
285+
.horizontal-divider {
286+
width: 85%;
287+
border-top: 1px dashed;
288+
padding: 0px;
289+
margin: 10px;
290+
}
291+
292+
.google-drive-button img {
293+
width:100%
294+
}
295+
296+
.google-drive-button:hover {
297+
background-color: var(--md-sys-color-on-surface-variant)
298+
}
Loading

src/main/resources/static/js/fileInput.js

+17
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ function setupFileInput(chooser) {
3535
const pdfPrompt = chooser.getAttribute('data-bs-pdf-prompt');
3636
const inputContainerId = chooser.getAttribute('data-bs-element-container-id');
3737
const showUploads = chooser.getAttribute('data-bs-show-uploads') === "true";
38+
const name = chooser.getAttribute('data-bs-unique-id')
3839
const noFileSelectedPrompt = chooser.getAttribute('data-bs-no-file-selected');
3940

4041
let inputContainer = document.getElementById(inputContainerId);
@@ -87,6 +88,21 @@ function setupFileInput(chooser) {
8788
overlay = false;
8889
}
8990

91+
const googleDriveFileListener = function (e) {
92+
const googleDriveFiles = e.detail;
93+
94+
const fileInput = document.getElementById(elementId);
95+
if (fileInput?.hasAttribute('multiple')) {
96+
pushFileListTo(googleDriveFiles, allFiles);
97+
} else if (fileInput) {
98+
allFiles = [googleDriveFiles[0]];
99+
}
100+
101+
const dataTransfer = new DataTransfer();
102+
allFiles.forEach((file) => dataTransfer.items.add(file));
103+
fileInput.files = dataTransfer.files;
104+
fileInput.dispatchEvent(new CustomEvent('change', { bubbles: true, detail: { source: 'drag-drop' } }));
105+
}
90106

91107
const dropListener = function (e) {
92108
e.preventDefault();
@@ -137,6 +153,7 @@ function setupFileInput(chooser) {
137153
document.body.addEventListener('dragenter', dragenterListener);
138154
document.body.addEventListener('dragleave', dragleaveListener);
139155
document.body.addEventListener('drop', dropListener);
156+
document.body.addEventListener(name + 'GoogleDriveDrivePicked', googleDriveFileListener);
140157

141158
$('#' + elementId).on('change', async function (e) {
142159
let element = e.target;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
const SCOPES = "https://www.googleapis.com/auth/drive.readonly";
2+
const SESSION_STORAGE_ID = "googleDrivePickerAccessToken";
3+
4+
let tokenClient;
5+
let accessToken = sessionStorage.getItem(SESSION_STORAGE_ID);
6+
7+
let isScriptExecuted = false;
8+
if (!isScriptExecuted) {
9+
isScriptExecuted = true;
10+
document.addEventListener("DOMContentLoaded", function () {
11+
document.querySelectorAll(".google-drive-button").forEach(setupGoogleDrivePicker);
12+
});
13+
}
14+
15+
function gisLoaded() {
16+
tokenClient = google.accounts.oauth2.initTokenClient({
17+
client_id: window.stirlingPDF.GoogleDriveClientId,
18+
scope: SCOPES,
19+
callback: "", // defined later
20+
});
21+
}
22+
23+
// add more as needed.
24+
// Google picker is limited on what mimeTypes are supported
25+
// Wild card are not supported
26+
const expandableMimeTypes = {
27+
"image/*" : ["image/jpeg", "image/png","image/svg+xml" ]
28+
}
29+
30+
function fileInputToGooglePickerMimeTypes(accept) {
31+
32+
if(accept == null || accept == "" || accept.includes("*/*")){
33+
34+
// Setting null will accept all supported mimetypes
35+
return null;
36+
}
37+
38+
let mimeTypes = [];
39+
accept.split(',').forEach(part => {
40+
if(!(part in expandableMimeTypes)){
41+
mimeTypes.push(part);
42+
return;
43+
}
44+
45+
expandableMimeTypes[part].forEach(mimeType => {
46+
mimeTypes.push(mimeType);
47+
});
48+
});
49+
50+
const mimeString = mimeTypes.join(",").replace(/\s+/g, '');
51+
console.log([accept, "became", mimeString]);
52+
return mimeString;
53+
}
54+
55+
/**
56+
* Callback after api.js is loaded.
57+
*/
58+
function gapiLoaded() {
59+
gapi.load("client:picker", initializePicker);
60+
}
61+
62+
/**
63+
* Callback after the API client is loaded. Loads the
64+
* discovery doc to initialize the API.
65+
*/
66+
async function initializePicker() {
67+
await gapi.client.load("https://www.googleapis.com/discovery/v1/apis/drive/v3/rest");
68+
}
69+
70+
function setupGoogleDrivePicker(picker) {
71+
72+
const name = picker.getAttribute('data-name');
73+
const accept = picker.getAttribute('data-accept');
74+
const multiple = picker.getAttribute('data-multiple') === "true";
75+
const mimeTypes = fileInputToGooglePickerMimeTypes(accept);
76+
77+
picker.addEventListener("click", onGoogleDriveButtonClick);
78+
79+
function onGoogleDriveButtonClick(e) {
80+
e.stopPropagation();
81+
82+
tokenClient.callback = (response) => {
83+
if (response.error !== undefined) {
84+
throw response;
85+
}
86+
accessToken = response.access_token;
87+
sessionStorage.setItem(SESSION_STORAGE_ID, accessToken);
88+
createGooglePicker();
89+
};
90+
91+
tokenClient.requestAccessToken({ prompt: accessToken === null ? "consent" : "" });
92+
}
93+
94+
/**
95+
* Sign out the user upon button click.
96+
*/
97+
function signOut() {
98+
if (accessToken) {
99+
sessionStorage.removeItem(SESSION_STORAGE_ID);
100+
google.accounts.oauth2.revoke(accessToken);
101+
accessToken = null;
102+
}
103+
}
104+
105+
function createGooglePicker() {
106+
let builder = new google.picker.PickerBuilder()
107+
.setDeveloperKey(window.stirlingPDF.GoogleDriveApiKey)
108+
.setAppId(window.stirlingPDF.GoogleDriveAppId)
109+
.setOAuthToken(accessToken)
110+
.addView(
111+
new google.picker.DocsView()
112+
.setIncludeFolders(true)
113+
.setMimeTypes(mimeTypes)
114+
)
115+
.addView(
116+
new google.picker.DocsView()
117+
.setIncludeFolders(true)
118+
.setEnableDrives(true)
119+
.setMimeTypes(mimeTypes)
120+
)
121+
.setCallback(pickerCallback);
122+
123+
if(multiple) {
124+
builder.enableFeature(google.picker.Feature.MULTISELECT_ENABLED);
125+
}
126+
const picker = builder.build();
127+
128+
picker.setVisible(true);
129+
}
130+
131+
/**
132+
* Displays the file details of the user's selection.
133+
* @param {object} data - Containers the user selection from the picker
134+
*/
135+
async function pickerCallback(data) {
136+
if (data.action === google.picker.Action.PICKED) {
137+
const files = await Promise.all(
138+
data[google.picker.Response.DOCUMENTS].map(async (pickedFile) => {
139+
const fileId = pickedFile[google.picker.Document.ID];
140+
console.log(fileId);
141+
const res = await gapi.client.drive.files.get({
142+
fileId: fileId,
143+
alt: "media",
144+
});
145+
146+
let file = new File([new Uint8Array(res.body.length).map((_, i) => res.body.charCodeAt(i))], pickedFile.name, {
147+
type: pickedFile.mimeType,
148+
lastModified: pickedFile.lastModified,
149+
endings: pickedFile.endings,
150+
});
151+
return file;
152+
})
153+
);
154+
155+
document.body.dispatchEvent(new CustomEvent(name+"GoogleDriveDrivePicked", { detail: files }));
156+
}
157+
}
158+
}

src/main/resources/templates/fragments/common.html

+24-5
Original file line numberDiff line numberDiff line change
@@ -225,8 +225,9 @@
225225
loading: '[[#{loading}]]'
226226
};</script>
227227
<div class="custom-file-chooser mb-3"
228+
228229
th:attr="data-bs-unique-id=${name}, data-bs-element-id=${name+'-input'}, data-bs-element-container-id=${name+'-input-container'}, data-bs-show-uploads=${showUploads}, data-bs-files-selected=#{filesSelected}, data-bs-pdf-prompt=#{pdfPrompt}, data-bs-no-file-selected=#{noFileSelected}">
229-
<div class="mb-3 d-flex flex-row justify-content-center align-items-center flex-wrap input-container"
230+
<div class="mb-3 d-flex flex-column justify-content-center align-items-center flex-wrap input-container"
230231
th:name="${name}+'-input'" th:id="${name}+'-input-container'" th:data-text="#{fileChooser.hoveredDragAndDrop}">
231232
<label class="file-input-btn d-none">
232233
<input type="file" class="form-control"
@@ -237,10 +238,16 @@
237238
th:required="${notRequired} ? null : 'required'">
238239
Browse
239240
</label>
240-
<div class="d-flex justify-content-start align-items-center" id="fileInputText">
241-
<div th:text="#{fileChooser.click}" style="margin-right: 5px"></div>
242-
<div th:text="#{fileChooser.or}" style="margin-right: 5px"></div>
243-
<div th:text="#{fileChooser.dragAndDrop}" id="dragAndDrop"></div>
241+
<div class="d-flex flex-column align-items-center">
242+
<div class="d-flex justify-content-start align-items-center" id="fileInputText">
243+
<div th:text="#{fileChooser.click}" style="margin-right: 5px"></div>
244+
<div th:text="#{fileChooser.or}" style="margin-right: 5px"></div>
245+
<div th:text="#{fileChooser.dragAndDrop}" id="dragAndDrop"></div>
246+
</div>
247+
<hr th:if="${@GoogleDriveEnabled == true}" class="horizontal-divider" >
248+
</div>
249+
<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}" >
250+
<img th:src="@{'/images/google-drive.svg'}" alt="google drive">
244251
</div>
245252
</div>
246253
<div class="selected-files flex-wrap"></div>
@@ -255,4 +262,16 @@
255262
</div>
256263
</div>
257264
<script th:src="@{'/js/fileInput.js'}" type="module"></script>
265+
<div th:if="${@GoogleDriveEnabled == true}" >
266+
<script type="text/javascript" th:src="@{'/js/googleFilePicker.js'}"></script>
267+
<script async defer src="https://apis.google.com/js/api.js" onload="gapiLoaded()"></script>
268+
<script async defer src="https://accounts.google.com/gsi/client" onload="gisLoaded()"></script>
269+
270+
<script th:inline="javascript">
271+
window.stirlingPDF.GoogleDriveClientId = /*[[${@GoogleDriveConfig.getClientId()}]]*/ null;
272+
window.stirlingPDF.GoogleDriveApiKey = /*[[${@GoogleDriveConfig.getApiKey()}]]*/ null;
273+
window.stirlingPDF.GoogleDriveAppId = /*[[${@GoogleDriveConfig.getAppId()}]]*/ null;
274+
</script>
275+
</div>
258276
</th:block>
277+

0 commit comments

Comments
 (0)