Skip to content

Commit 68e4d1b

Browse files
author
DK09876
committed
Refactor picker
1 parent be61bec commit 68e4d1b

File tree

3 files changed

+394
-251
lines changed

3 files changed

+394
-251
lines changed

src/baseOAuth/ui/picker.ts

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { OAuthResponse, OAuthConfig } from '../types';
2+
3+
/**
4+
* Base Picker class providing common file selection functionality
5+
* Can be extended by specific connector implementations
6+
*/
7+
export abstract class BasePicker {
8+
/**
9+
* Abstract method to create HTML template for the picker page
10+
* Must be implemented by subclasses for connector-specific picker UI
11+
*
12+
* @param tokens OAuth tokens for API access
13+
* @param config Configuration with necessary credentials
14+
* @param refreshToken Refresh token to include in selection data
15+
* @param preSelectedFiles Optional map of files to initialize as selected
16+
* @returns HTML string for the picker interface
17+
*/
18+
abstract createPickerHTML(
19+
tokens: OAuthResponse,
20+
config: OAuthConfig,
21+
refreshToken: string,
22+
preSelectedFiles?: Record<string, { name: string; mimeType: string }>
23+
): string;
24+
25+
/**
26+
* Generates common UI elements for the picker
27+
* This can be used by subclass implementations to maintain a consistent look
28+
*
29+
* @returns Object containing HTML template strings
30+
*/
31+
protected getCommonUIElements(): {
32+
header: string;
33+
warning: string;
34+
fileListContainer: string;
35+
submitButtonContainer: string;
36+
scripts: {
37+
basePickerScript: (tokens: any, config: any, refreshToken: string, preSelectedFiles: any) => string;
38+
}
39+
} {
40+
return {
41+
header: `
42+
<div class="flex justify-between items-center">
43+
<h1 class="text-2xl font-bold">Selected Files and Folders</h1>
44+
<button
45+
onclick="handleSelectMore()"
46+
class="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors"
47+
>
48+
Select Files/Folders
49+
</button>
50+
</div>
51+
`,
52+
warning: `
53+
<div class="bg-yellow-50 border-l-4 border-yellow-400 p-4 my-4">
54+
<div class="flex">
55+
<div class="flex-shrink-0">
56+
<svg class="h-5 w-5 text-yellow-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
57+
<path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd" />
58+
</svg>
59+
</div>
60+
<div class="ml-3">
61+
<p class="text-sm text-yellow-700">
62+
<span class="font-medium">Important:</span> Some files might have limitations when accessed through the API. Please check the file compatibility with this connector.
63+
</p>
64+
</div>
65+
</div>
66+
</div>
67+
`,
68+
fileListContainer: `
69+
<div id="fileList" class="space-y-4">
70+
<p>No files selected</p>
71+
</div>
72+
`,
73+
submitButtonContainer: `
74+
<div id="submitButton" class="flex justify-end mt-6" style="display: none;">
75+
<button
76+
onclick="finishSelection()"
77+
class="bg-green-500 text-white px-6 py-3 rounded-lg hover:bg-green-600 transition-colors"
78+
>
79+
Finish Selection
80+
</button>
81+
</div>
82+
`,
83+
scripts: {
84+
basePickerScript: (tokens, config, refreshToken, preSelectedFiles) => `
85+
const tokens = ${JSON.stringify(tokens)};
86+
const config = ${JSON.stringify(config)};
87+
const refreshToken = ${JSON.stringify(refreshToken)};
88+
const preSelectedFiles = ${JSON.stringify(preSelectedFiles || {})};
89+
let selectedFiles = [];
90+
91+
// Initialize selected files from pre-selected ones if provided
92+
if (preSelectedFiles && Object.keys(preSelectedFiles).length > 0) {
93+
selectedFiles = Object.entries(preSelectedFiles).map(([id, details]) => ({
94+
id,
95+
name: details.name,
96+
mimeType: details.mimeType
97+
}));
98+
}
99+
100+
function handleError(error) {
101+
const errorObj = new (window.opener.OAuthError || Error)(
102+
error.message || 'An error occurred in the picker',
103+
error.code || 'PICKER_ERROR',
104+
error.details
105+
);
106+
window.opener.__oauthHandler.onError(errorObj);
107+
window.close();
108+
}
109+
110+
function updateFileList() {
111+
const fileList = document.getElementById('fileList');
112+
const submitButton = document.getElementById('submitButton');
113+
114+
if (selectedFiles.length === 0) {
115+
fileList.innerHTML = '<p>No files selected</p>';
116+
submitButton.style.display = 'none';
117+
return;
118+
}
119+
120+
fileList.innerHTML = selectedFiles.map(file =>
121+
\`<div class="group p-4 border rounded-lg flex justify-between items-center hover:bg-gray-50">
122+
<div>
123+
<p class="font-medium text-gray-800">
124+
\${file.name}
125+
</p>
126+
<p class="text-sm text-gray-500">
127+
Type: \${file.mimeType}
128+
</p>
129+
</div>
130+
<button
131+
onclick="removeFile('\${file.id}')"
132+
class="p-2 text-gray-500 hover:text-red-500 hover:bg-red-50 rounded-full transition-colors"
133+
aria-label="Remove file"
134+
>
135+
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
136+
<path d="M3 6h18M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>
137+
</svg>
138+
</button>
139+
</div>\`
140+
).join('');
141+
142+
submitButton.style.display = 'flex';
143+
}
144+
145+
function removeFile(fileId) {
146+
selectedFiles = selectedFiles.filter(file => file.id !== fileId);
147+
updateFileList();
148+
}
149+
150+
async function finishSelection() {
151+
try {
152+
if (!selectedFiles.length) {
153+
throw new Error('No files selected');
154+
}
155+
156+
// Create a map of fileId -> {name, mimeType}
157+
const fileMap = {};
158+
selectedFiles.forEach(file => {
159+
fileMap[file.id] = {
160+
name: file.name,
161+
mimeType: file.mimeType
162+
};
163+
});
164+
165+
const bodyData = {
166+
selectedFiles: fileMap,
167+
refreshToken: refreshToken
168+
};
169+
170+
window.opener.__oauthHandler.onSuccess(bodyData);
171+
window.close();
172+
} catch (error) {
173+
handleError({
174+
message: error.message || 'Failed to complete file selection',
175+
code: 'SELECTION_ERROR',
176+
details: error
177+
});
178+
}
179+
}
180+
181+
// Initialize file list with pre-selected files if any
182+
if (selectedFiles.length > 0) {
183+
updateFileList();
184+
}
185+
`
186+
}
187+
};
188+
}
189+
190+
/**
191+
* Utility method to generate the base HTML structure
192+
*
193+
* @param title Page title
194+
* @param styles Additional CSS styles to include
195+
* @param head Additional head content (scripts, meta tags)
196+
* @param body Body content
197+
* @param scripts JavaScript to include at the end of body
198+
* @returns Complete HTML string
199+
*/
200+
protected generateHTMLTemplate(
201+
title: string,
202+
styles: string = '',
203+
head: string = '',
204+
body: string,
205+
scripts: string
206+
): string {
207+
return `
208+
<!DOCTYPE html>
209+
<html>
210+
<head>
211+
<title>${title}</title>
212+
<meta charset="utf-8">
213+
<meta name="viewport" content="width=device-width, initial-scale=1">
214+
<script src="https://cdn.tailwindcss.com"></script>
215+
${styles}
216+
${head}
217+
</head>
218+
<body>
219+
<div class="p-6">
220+
<div class="space-y-6">
221+
${body}
222+
</div>
223+
</div>
224+
<script>
225+
${scripts}
226+
</script>
227+
</body>
228+
</html>
229+
`;
230+
}
231+
}

src/googleDriveOAuth/core/apiFunctions.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,24 @@ export async function manageGDriveUser(
8787
action: UserAction,
8888
platformUrl: string = "https://api.vectorize.io/v1",
8989
): Promise<Response> {
90+
// Validate required parameters for add/edit actions
91+
if (action === "add" || action === "edit") {
92+
if (!selectedFiles || Object.keys(selectedFiles).length === 0) {
93+
throw new Error(`Selected files are required for ${action} action`);
94+
}
95+
96+
if (!refreshToken) {
97+
throw new Error(`Refresh token is required for ${action} action`);
98+
}
99+
}
100+
90101
// Create the Google Drive specific payload
91102
const payload: Record<string, any> = {};
92103

93104
// Only include selectedFiles and refreshToken for add/edit, not for remove
94105
if (action !== "remove") {
95-
if (selectedFiles) {
96-
payload.selectedFiles = selectedFiles;
97-
}
98-
if (refreshToken) {
99-
payload.refreshToken = refreshToken;
100-
}
106+
payload.selectedFiles = selectedFiles;
107+
payload.refreshToken = refreshToken;
101108
}
102109

103110
return manageUser(config, connectorId, userId, action, payload, platformUrl);

0 commit comments

Comments
 (0)