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
+ }
0 commit comments