274 lines
8.7 KiB
TypeScript
274 lines
8.7 KiB
TypeScript
"use client";
|
|
|
|
import {
|
|
CloudFile,
|
|
CloudProvider,
|
|
GooglePickerData,
|
|
GooglePickerDocument,
|
|
} from "./types";
|
|
|
|
export class GoogleDriveHandler {
|
|
private accessToken: string;
|
|
private onPickerStateChange?: (isOpen: boolean) => void;
|
|
|
|
constructor(
|
|
accessToken: string,
|
|
onPickerStateChange?: (isOpen: boolean) => void,
|
|
) {
|
|
this.accessToken = accessToken;
|
|
this.onPickerStateChange = onPickerStateChange;
|
|
}
|
|
|
|
async loadPickerApi(): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
if (typeof window !== "undefined" && window.gapi) {
|
|
window.gapi.load("picker", {
|
|
callback: () => resolve(true),
|
|
onerror: () => resolve(false),
|
|
});
|
|
} else {
|
|
// Load Google API script
|
|
const script = document.createElement("script");
|
|
script.src = "https://apis.google.com/js/api.js";
|
|
script.async = true;
|
|
script.defer = true;
|
|
script.onload = () => {
|
|
window.gapi.load("picker", {
|
|
callback: () => resolve(true),
|
|
onerror: () => resolve(false),
|
|
});
|
|
};
|
|
script.onerror = () => resolve(false);
|
|
document.head.appendChild(script);
|
|
}
|
|
});
|
|
}
|
|
|
|
openPicker(onFileSelected: (files: CloudFile[]) => void): void {
|
|
if (!window.google?.picker) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.onPickerStateChange?.(true);
|
|
|
|
// Create a view for regular documents
|
|
const docsView = new window.google.picker.DocsView()
|
|
.setIncludeFolders(true)
|
|
.setSelectFolderEnabled(true);
|
|
|
|
const picker = new window.google.picker.PickerBuilder()
|
|
.addView(docsView)
|
|
.setOAuthToken(this.accessToken)
|
|
.enableFeature(window.google.picker.Feature.MULTISELECT_ENABLED)
|
|
.setTitle("Select files or folders from Google Drive")
|
|
.setCallback((data) => this.pickerCallback(data, onFileSelected))
|
|
.build();
|
|
|
|
picker.setVisible(true);
|
|
|
|
// Apply z-index fix
|
|
setTimeout(() => {
|
|
const pickerElements = document.querySelectorAll(
|
|
".picker-dialog, .goog-modalpopup",
|
|
);
|
|
pickerElements.forEach((el) => {
|
|
(el as HTMLElement).style.zIndex = "10000";
|
|
});
|
|
const bgElements = document.querySelectorAll(
|
|
".picker-dialog-bg, .goog-modalpopup-bg",
|
|
);
|
|
bgElements.forEach((el) => {
|
|
(el as HTMLElement).style.zIndex = "9999";
|
|
});
|
|
}, 100);
|
|
} catch (error) {
|
|
console.error("Error creating picker:", error);
|
|
this.onPickerStateChange?.(false);
|
|
}
|
|
}
|
|
|
|
private async pickerCallback(
|
|
data: GooglePickerData,
|
|
onFileSelected: (files: CloudFile[]) => void,
|
|
): Promise<void> {
|
|
if (data.action === window.google.picker.Action.PICKED) {
|
|
const files: CloudFile[] = data.docs.map((doc: GooglePickerDocument) => ({
|
|
id: doc[window.google.picker.Document.ID],
|
|
name: doc[window.google.picker.Document.NAME],
|
|
mimeType: doc[window.google.picker.Document.MIME_TYPE],
|
|
webViewLink: doc[window.google.picker.Document.URL],
|
|
iconLink: doc[window.google.picker.Document.ICON_URL],
|
|
size: doc["sizeBytes"] ? parseInt(doc["sizeBytes"]) : undefined,
|
|
modifiedTime: doc["lastEditedUtc"],
|
|
isFolder:
|
|
doc[window.google.picker.Document.MIME_TYPE] ===
|
|
"application/vnd.google-apps.folder",
|
|
}));
|
|
|
|
// Enrich with additional file data if needed
|
|
if (files.some((f) => !f.size && !f.isFolder)) {
|
|
try {
|
|
const enrichedFiles = await Promise.all(
|
|
files.map(async (file) => {
|
|
if (!file.size && !file.isFolder) {
|
|
try {
|
|
const response = await fetch(
|
|
`https://www.googleapis.com/drive/v3/files/${file.id}?fields=size,modifiedTime`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${this.accessToken}`,
|
|
},
|
|
},
|
|
);
|
|
if (response.ok) {
|
|
const fileDetails = await response.json();
|
|
return {
|
|
...file,
|
|
size: fileDetails.size
|
|
? parseInt(fileDetails.size)
|
|
: undefined,
|
|
modifiedTime:
|
|
fileDetails.modifiedTime || file.modifiedTime,
|
|
};
|
|
}
|
|
} catch (error) {
|
|
console.warn("Failed to fetch file details:", error);
|
|
}
|
|
}
|
|
return file;
|
|
}),
|
|
);
|
|
onFileSelected(enrichedFiles);
|
|
} catch (error) {
|
|
console.warn("Failed to enrich file data:", error);
|
|
onFileSelected(files);
|
|
}
|
|
} else {
|
|
onFileSelected(files);
|
|
}
|
|
}
|
|
|
|
this.onPickerStateChange?.(false);
|
|
}
|
|
}
|
|
|
|
export class OneDriveHandler {
|
|
private accessToken: string;
|
|
private clientId: string;
|
|
private provider: CloudProvider;
|
|
private baseUrl?: string;
|
|
|
|
constructor(
|
|
accessToken: string,
|
|
clientId: string,
|
|
provider: CloudProvider = "onedrive",
|
|
baseUrl?: string,
|
|
) {
|
|
this.accessToken = accessToken;
|
|
this.clientId = clientId;
|
|
this.provider = provider;
|
|
this.baseUrl = baseUrl;
|
|
}
|
|
|
|
async loadPickerApi(): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
const script = document.createElement("script");
|
|
script.src = "https://js.live.net/v7.2/OneDrive.js";
|
|
script.onload = () => resolve(true);
|
|
script.onerror = () => resolve(false);
|
|
document.head.appendChild(script);
|
|
});
|
|
}
|
|
|
|
openPicker(onFileSelected: (files: CloudFile[]) => void): void {
|
|
if (!window.OneDrive) {
|
|
return;
|
|
}
|
|
|
|
window.OneDrive.open({
|
|
clientId: this.clientId,
|
|
action: "query",
|
|
multiSelect: true,
|
|
advanced: {
|
|
endpointHint: "api.onedrive.com",
|
|
accessToken: this.accessToken,
|
|
},
|
|
success: (response: any) => {
|
|
const newFiles: CloudFile[] =
|
|
response.value?.map((item: any) => {
|
|
// Extract mimeType from file object or infer from name
|
|
let mimeType = item.file?.mimeType;
|
|
if (!mimeType && item.name) {
|
|
// Infer from extension if mimeType not provided
|
|
const ext = item.name.split('.').pop()?.toLowerCase();
|
|
const mimeTypes: { [key: string]: string } = {
|
|
pdf: 'application/pdf',
|
|
doc: 'application/msword',
|
|
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
|
xls: 'application/vnd.ms-excel',
|
|
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
ppt: 'application/vnd.ms-powerpoint',
|
|
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
|
txt: 'text/plain',
|
|
csv: 'text/csv',
|
|
json: 'application/json',
|
|
xml: 'application/xml',
|
|
html: 'text/html',
|
|
jpg: 'image/jpeg',
|
|
jpeg: 'image/jpeg',
|
|
png: 'image/png',
|
|
gif: 'image/gif',
|
|
svg: 'image/svg+xml',
|
|
};
|
|
mimeType = mimeTypes[ext || ''] || 'application/octet-stream';
|
|
}
|
|
|
|
return {
|
|
id: item.id,
|
|
name: item.name || `${this.getProviderName()} File`,
|
|
mimeType: mimeType || "application/octet-stream",
|
|
webUrl: item.webUrl || "",
|
|
downloadUrl: item["@microsoft.graph.downloadUrl"] || "",
|
|
size: item.size,
|
|
modifiedTime: item.lastModifiedDateTime,
|
|
isFolder: !!item.folder,
|
|
};
|
|
}) || [];
|
|
|
|
onFileSelected(newFiles);
|
|
},
|
|
cancel: () => {
|
|
console.log("Picker cancelled");
|
|
},
|
|
error: (error: any) => {
|
|
console.error("Picker error:", error);
|
|
},
|
|
});
|
|
}
|
|
|
|
private getProviderName(): string {
|
|
return this.provider === "sharepoint" ? "SharePoint" : "OneDrive";
|
|
}
|
|
}
|
|
|
|
export const createProviderHandler = (
|
|
provider: CloudProvider,
|
|
accessToken: string,
|
|
onPickerStateChange?: (isOpen: boolean) => void,
|
|
clientId?: string,
|
|
baseUrl?: string,
|
|
) => {
|
|
switch (provider) {
|
|
case "google_drive":
|
|
return new GoogleDriveHandler(accessToken, onPickerStateChange);
|
|
case "onedrive":
|
|
case "sharepoint":
|
|
if (!clientId) {
|
|
throw new Error("Client ID required for OneDrive/SharePoint");
|
|
}
|
|
return new OneDriveHandler(accessToken, clientId, provider, baseUrl);
|
|
default:
|
|
throw new Error(`Unsupported provider: ${provider}`);
|
|
}
|
|
};
|