WIP: Add canvas zoom/pan/pinch event handlers - handlers deployed but zoom interaction not working yet, will debug later
This commit is contained in:
@@ -65,12 +65,14 @@
|
||||
|
||||
const getSvgIcon = (iconType: string): string => {
|
||||
const icons: {[key: string]: string} = {
|
||||
pdf: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6M9 16h6"></path></svg>`,
|
||||
doc: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>`,
|
||||
sheet: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 17v-2m3 2v-4m3 4v-6m2-10H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2z"></path></svg>`,
|
||||
presentation: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4v16m10 0V4m3-2H4a2 2 0 00-2 2v16a2 2 0 002 2h16a2 2 0 002-2V4a2 2 0 00-2-2z"></path></svg>`,
|
||||
image: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"></path></svg>`,
|
||||
file: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"></path></svg>`,
|
||||
pdf: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#D32F2F" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">PDF</text></svg>`,
|
||||
doc: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#2B579A" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">W</text></svg>`,
|
||||
sheet: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#217346" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">X</text></svg>`,
|
||||
presentation: `<svg viewBox="0 0 24 24" fill="none" style="width:16px;height:16px;"><rect fill="#D35230" width="24" height="24" rx="2"/><text x="12" y="16" font-size="10" font-weight="bold" fill="white" text-anchor="middle">P</text></svg>`,
|
||||
image: `<svg fill="none" stroke="currentColor" viewBox="0 0 24 24" style="width:16px;height:16px;color:#6b7280;"><rect x="2" y="2" width="20" height="20" rx="2"/><circle cx="8" cy="8" r="2"/><path d="M21 15l-5-5L5 21"/></svg>`,
|
||||
zip: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 16H5V5h14v14zm-5-9h-3v4h3v-4zm0-3h-3v2h3V7z"/></svg>`,
|
||||
text: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/><path d="M16 13H8M16 17H8M10 9H8"/></svg>`,
|
||||
file: `<svg viewBox="0 0 24 24" fill="currentColor" style="width:16px;height:16px;color:#6b7280;"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8l-6-6z"/></svg>`,
|
||||
};
|
||||
return icons[iconType] || icons.file;
|
||||
};
|
||||
@@ -91,6 +93,399 @@
|
||||
}
|
||||
};
|
||||
|
||||
const canPreviewInline = (name: string, contentType: string = ''): boolean => {
|
||||
const ext = (name || '').toLowerCase().split('.').pop();
|
||||
const type = (contentType || '').toLowerCase();
|
||||
return ext === 'pdf' || type.includes('pdf') ||
|
||||
['doc', 'docx'].includes(ext as string) || type.includes('word') ||
|
||||
['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp'].includes(ext as string) || type.includes('image');
|
||||
};
|
||||
|
||||
const renderPdfPage = async () => {
|
||||
if (!currentPdf || !currentPdf.doc) return;
|
||||
|
||||
const pageObj = await currentPdf.doc.getPage(currentPdf.page);
|
||||
const viewport = pageObj.getViewport({ scale: currentPdf.scale });
|
||||
const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) return;
|
||||
|
||||
canvas.height = viewport.height;
|
||||
canvas.width = viewport.width;
|
||||
|
||||
await pageObj.render({
|
||||
canvasContext: context,
|
||||
viewport: viewport,
|
||||
}).promise;
|
||||
};
|
||||
|
||||
const renderImageOnCanvas = () => {
|
||||
if (!currentPdf?.imageElement) return;
|
||||
const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
const scale = currentPdf.scale;
|
||||
canvas.width = (currentPdf.imageWidth || 0) * scale;
|
||||
canvas.height = (currentPdf.imageHeight || 0) * scale;
|
||||
|
||||
ctx.drawImage(currentPdf.imageElement, 0, 0, canvas.width, canvas.height);
|
||||
};
|
||||
|
||||
const updateZoomLabel = () => {
|
||||
const label = document.getElementById('pdfZoomLabel');
|
||||
if (label && currentPdf) {
|
||||
label.textContent = `${Math.round(currentPdf.scale * 100)}%`;
|
||||
}
|
||||
};
|
||||
|
||||
const setupCanvasEventHandlers = () => {
|
||||
const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
// Zoom buttons
|
||||
const zoomOutBtn = document.getElementById('pdfZoomOut');
|
||||
const zoomResetBtn = document.getElementById('pdfZoomReset');
|
||||
const zoomInBtn = document.getElementById('pdfZoomIn');
|
||||
|
||||
if (zoomOutBtn) {
|
||||
zoomOutBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = Math.max(MIN_PDF_SCALE, currentPdf.scale - 0.1);
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
updateZoomLabel();
|
||||
});
|
||||
}
|
||||
|
||||
if (zoomResetBtn) {
|
||||
zoomResetBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = DEFAULT_PDF_SCALE;
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
updateZoomLabel();
|
||||
});
|
||||
}
|
||||
|
||||
if (zoomInBtn) {
|
||||
zoomInBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = Math.min(MAX_PDF_SCALE, currentPdf.scale + 0.1);
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
updateZoomLabel();
|
||||
});
|
||||
}
|
||||
|
||||
// Page navigation buttons (if multi-page)
|
||||
const prevBtn = document.getElementById('pdfPrev');
|
||||
const nextBtn = document.getElementById('pdfNext');
|
||||
|
||||
if (prevBtn) {
|
||||
prevBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf || currentPdf.page <= 1) return;
|
||||
currentPdf.page--;
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
await renderPdfPage();
|
||||
document.getElementById('pdfPageNum')!.textContent = String(currentPdf.page);
|
||||
});
|
||||
}
|
||||
|
||||
if (nextBtn) {
|
||||
nextBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf || currentPdf.page >= currentPdf.pages) return;
|
||||
currentPdf.page++;
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
await renderPdfPage();
|
||||
document.getElementById('pdfPageNum')!.textContent = String(currentPdf.page);
|
||||
});
|
||||
}
|
||||
|
||||
// Canvas pan (mouse drag)
|
||||
canvas.addEventListener('mousedown', (e) => {
|
||||
pdfPan.dragging = true;
|
||||
pdfPan.startX = e.clientX;
|
||||
pdfPan.startY = e.clientY;
|
||||
canvas.style.cursor = 'grabbing';
|
||||
});
|
||||
|
||||
canvas.addEventListener('mousemove', (e) => {
|
||||
if (!pdfPan.dragging) return;
|
||||
const dx = e.clientX - pdfPan.startX;
|
||||
const dy = e.clientY - pdfPan.startY;
|
||||
pdfPan.x = pdfPan.baseX + dx;
|
||||
pdfPan.y = pdfPan.baseY + dy;
|
||||
canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseup', () => {
|
||||
if (pdfPan.dragging) {
|
||||
pdfPan.baseX = pdfPan.x;
|
||||
pdfPan.baseY = pdfPan.y;
|
||||
pdfPan.dragging = false;
|
||||
canvas.style.cursor = 'grab';
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('mouseleave', () => {
|
||||
if (pdfPan.dragging) {
|
||||
pdfPan.baseX = pdfPan.x;
|
||||
pdfPan.baseY = pdfPan.y;
|
||||
pdfPan.dragging = false;
|
||||
canvas.style.cursor = 'grab';
|
||||
}
|
||||
});
|
||||
|
||||
// Pinch zoom (touch)
|
||||
let lastDistance = 0;
|
||||
canvas.addEventListener('touchstart', (e) => {
|
||||
if (e.touches.length === 2) {
|
||||
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
||||
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
||||
lastDistance = Math.sqrt(dx * dx + dy * dy);
|
||||
} else if (e.touches.length === 1) {
|
||||
pdfPan.dragging = true;
|
||||
pdfPan.startX = e.touches[0].clientX;
|
||||
pdfPan.startY = e.touches[0].clientY;
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('touchmove', async (e) => {
|
||||
if (e.touches.length === 2) {
|
||||
e.preventDefault();
|
||||
const dx = e.touches[0].clientX - e.touches[1].clientX;
|
||||
const dy = e.touches[0].clientY - e.touches[1].clientY;
|
||||
const distance = Math.sqrt(dx * dx + dy * dy);
|
||||
if (lastDistance > 0) {
|
||||
const scaleDelta = (distance - lastDistance) / 100;
|
||||
const newScale = currentPdf ? currentPdf.scale + scaleDelta : DEFAULT_PDF_SCALE;
|
||||
if (currentPdf) {
|
||||
currentPdf.scale = Math.max(MIN_PDF_SCALE, Math.min(MAX_PDF_SCALE, newScale));
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
updateZoomLabel();
|
||||
}
|
||||
}
|
||||
lastDistance = distance;
|
||||
} else if (e.touches.length === 1 && pdfPan.dragging) {
|
||||
const dx = e.touches[0].clientX - pdfPan.startX;
|
||||
const dy = e.touches[0].clientY - pdfPan.startY;
|
||||
pdfPan.x = pdfPan.baseX + dx;
|
||||
pdfPan.y = pdfPan.baseY + dy;
|
||||
const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
|
||||
if (canvas) {
|
||||
canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
canvas.addEventListener('touchend', () => {
|
||||
pdfPan.baseX = pdfPan.x;
|
||||
pdfPan.baseY = pdfPan.y;
|
||||
pdfPan.dragging = false;
|
||||
lastDistance = 0;
|
||||
});
|
||||
|
||||
// Wheel zoom (mouse scroll)
|
||||
canvas.addEventListener('wheel', async (e) => {
|
||||
e.preventDefault();
|
||||
if (!currentPdf) return;
|
||||
const zoomDelta = e.deltaY > 0 ? -0.1 : 0.1;
|
||||
currentPdf.scale = Math.max(MIN_PDF_SCALE, Math.min(MAX_PDF_SCALE, currentPdf.scale + zoomDelta));
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
updateZoomLabel();
|
||||
}, { passive: false });
|
||||
};
|
||||
|
||||
|
||||
// Setup canvas event handlers for zoom, pan, and pinch
|
||||
const setupPdfControls = () => {
|
||||
const zoomInBtn = document.getElementById('pdfZoomInBtn') as HTMLButtonElement;
|
||||
const zoomOutBtn = document.getElementById('pdfZoomOutBtn') as HTMLButtonElement;
|
||||
const zoomResetBtn = document.getElementById('pdfZoomResetBtn') as HTMLButtonElement;
|
||||
const canvas = document.getElementById('pdfCanvas') as HTMLCanvasElement;
|
||||
|
||||
if (!canvas) return;
|
||||
|
||||
const applyPan = () => {
|
||||
canvas.style.transform = `translate(${pdfPan.x}px, ${pdfPan.y}px)`;
|
||||
};
|
||||
|
||||
const activePointers = new Map<number, {x: number; y: number}>();
|
||||
let pinchStartDistance: number | null = null;
|
||||
let pinchStartScale: number | null = null;
|
||||
|
||||
const distanceBetween = () => {
|
||||
if (activePointers.size < 2) return null;
|
||||
const pts = Array.from(activePointers.values());
|
||||
const a = pts[0];
|
||||
const b = pts[1];
|
||||
const dx = a.x - b.x;
|
||||
const dy = a.y - b.y;
|
||||
return Math.hypot(dx, dy);
|
||||
};
|
||||
|
||||
// Zoom button handlers
|
||||
if (zoomInBtn) {
|
||||
zoomInBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = Math.min(currentPdf.scale + 0.1, MAX_PDF_SCALE);
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (zoomOutBtn) {
|
||||
zoomOutBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = Math.max(currentPdf.scale - 0.1, MIN_PDF_SCALE);
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (zoomResetBtn) {
|
||||
zoomResetBtn.addEventListener('click', async () => {
|
||||
if (!currentPdf) return;
|
||||
currentPdf.scale = currentPdf.isImage ? 1 : DEFAULT_PDF_SCALE;
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
canvas.style.transform = 'translate(0px, 0px)';
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Canvas event handlers
|
||||
canvas.style.touchAction = 'none';
|
||||
|
||||
canvas.addEventListener('wheel', async (e) => {
|
||||
if (!currentPdf) return;
|
||||
e.preventDefault();
|
||||
const delta = e.deltaY;
|
||||
const step = 0.1;
|
||||
if (delta < 0) {
|
||||
currentPdf.scale = Math.min(currentPdf.scale + step, MAX_PDF_SCALE);
|
||||
} else {
|
||||
currentPdf.scale = Math.max(currentPdf.scale - step, MIN_PDF_SCALE);
|
||||
}
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
await renderPdfPage();
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
canvas.addEventListener('pointerdown', (e) => {
|
||||
if (!currentPdf) return;
|
||||
activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||
|
||||
if (activePointers.size === 2) {
|
||||
pinchStartDistance = distanceBetween();
|
||||
pinchStartScale = currentPdf.scale;
|
||||
pdfPan.dragging = false;
|
||||
} else if (activePointers.size === 1) {
|
||||
pdfPan.dragging = true;
|
||||
pdfPan.startX = e.clientX;
|
||||
pdfPan.startY = e.clientY;
|
||||
pdfPan.baseX = pdfPan.x;
|
||||
pdfPan.baseY = pdfPan.y;
|
||||
canvas.style.cursor = 'grabbing';
|
||||
}
|
||||
|
||||
canvas.setPointerCapture(e.pointerId);
|
||||
});
|
||||
|
||||
canvas.addEventListener('pointermove', (e) => {
|
||||
if (!currentPdf) return;
|
||||
|
||||
if (activePointers.has(e.pointerId)) {
|
||||
activePointers.set(e.pointerId, { x: e.clientX, y: e.clientY });
|
||||
}
|
||||
|
||||
if (activePointers.size >= 2 && pinchStartDistance) {
|
||||
const dist = distanceBetween();
|
||||
if (dist) {
|
||||
const scale = pinchStartScale! * (dist / pinchStartDistance);
|
||||
currentPdf.scale = Math.min(Math.max(scale, MIN_PDF_SCALE), MAX_PDF_SCALE);
|
||||
if (currentPdf.isImage) {
|
||||
renderImageOnCanvas();
|
||||
} else {
|
||||
renderPdfPage();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pdfPan.dragging) return;
|
||||
const dx = e.clientX - pdfPan.startX;
|
||||
const dy = e.clientY - pdfPan.startY;
|
||||
pdfPan.x = pdfPan.baseX + dx;
|
||||
pdfPan.y = pdfPan.baseY + dy;
|
||||
applyPan();
|
||||
});
|
||||
|
||||
const endPan = (e: PointerEvent) => {
|
||||
if (activePointers.has(e.pointerId)) {
|
||||
activePointers.delete(e.pointerId);
|
||||
}
|
||||
|
||||
if (activePointers.size < 2) {
|
||||
pinchStartDistance = null;
|
||||
pinchStartScale = null;
|
||||
}
|
||||
|
||||
if (activePointers.size === 0) {
|
||||
pdfPan.dragging = false;
|
||||
canvas.style.cursor = 'grab';
|
||||
}
|
||||
|
||||
try {
|
||||
canvas.releasePointerCapture(e.pointerId);
|
||||
} catch {}
|
||||
};
|
||||
|
||||
canvas.addEventListener('pointerup', endPan);
|
||||
canvas.addEventListener('pointercancel', endPan);
|
||||
};
|
||||
|
||||
// Search and filters
|
||||
let searchQuery = '';
|
||||
let showFiltersPanel = false;
|
||||
@@ -111,6 +506,15 @@
|
||||
let showFileViewer = false;
|
||||
let currentViewerFile: any = null;
|
||||
let graphToken = '';
|
||||
|
||||
// PDF/Image viewer state
|
||||
let currentPreviewUrl: string | null = null;
|
||||
let currentPdf: {doc: any; page: number; pages: number; scale: number; isImage?: boolean; imageElement?: HTMLImageElement; imageWidth?: number; imageHeight?: number} | null = null;
|
||||
let pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
let showPdfViewer = false;
|
||||
const DEFAULT_PDF_SCALE = 0.45;
|
||||
const MIN_PDF_SCALE = 0.3;
|
||||
const MAX_PDF_SCALE = 3.0;
|
||||
let graphTokenExpiry: number | null = null; // Expiration timestamp in ms
|
||||
let graphTokenRefreshTimer: NodeJS.Timeout | null = null; // Background refresh timer
|
||||
|
||||
@@ -487,11 +891,108 @@
|
||||
currentViewerFile = file;
|
||||
showFileViewer = true;
|
||||
showDetailModal = false;
|
||||
|
||||
// Try to preview in canvas if supported
|
||||
const ext = (file.name || '').toLowerCase().split('.').pop();
|
||||
if (canPreviewInline(file.name, file.contentType || '')) {
|
||||
loadFilePreview(file);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadFilePreview(file: any) {
|
||||
if (!file.url) return;
|
||||
|
||||
try {
|
||||
let fetchUrl = '';
|
||||
|
||||
// Use backend proxy endpoints instead of direct SharePoint URLs
|
||||
const ext = (file.name || '').toLowerCase().split('.').pop();
|
||||
const isWordDoc = ['doc', 'docx'].includes(ext as string) || file.contentType?.includes('word');
|
||||
const isPdf = ext === 'pdf' || file.contentType?.includes('pdf');
|
||||
|
||||
if (file.driveId && file.id) {
|
||||
// Use proxy endpoints with Graph API
|
||||
if (isWordDoc) {
|
||||
fetchUrl = `/api/file-pdf?driveId=${encodeURIComponent(file.driveId)}&itemId=${encodeURIComponent(file.id)}`;
|
||||
} else {
|
||||
fetchUrl = `/api/file-content?driveId=${encodeURIComponent(file.driveId)}&itemId=${encodeURIComponent(file.id)}`;
|
||||
}
|
||||
} else {
|
||||
// Fallback to direct URL (for non-SharePoint files)
|
||||
fetchUrl = file.url;
|
||||
}
|
||||
|
||||
const response = await fetch(fetchUrl, {
|
||||
headers: graphToken ? { 'x-graph-token': graphToken } : {},
|
||||
});
|
||||
|
||||
if (!response.ok) throw new Error(`Failed to fetch file: ${response.status}`);
|
||||
|
||||
const blob = await response.blob();
|
||||
if (currentPreviewUrl) {
|
||||
URL.revokeObjectURL(currentPreviewUrl);
|
||||
}
|
||||
currentPreviewUrl = URL.createObjectURL(blob);
|
||||
|
||||
const isImage = ['jpg', 'jpeg', 'png', 'gif', 'svg', 'bmp'].includes(ext as string) || file.contentType?.includes('image');
|
||||
|
||||
if ((isPdf || isWordDoc) && (window as any).pdfjsLib) {
|
||||
const data = await blob.arrayBuffer();
|
||||
const pdfjsLib = (window as any).pdfjsLib;
|
||||
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||
|
||||
const doc = await pdfjsLib.getDocument({ data }).promise;
|
||||
currentPdf = {
|
||||
doc,
|
||||
page: 1,
|
||||
pages: doc.numPages,
|
||||
scale: DEFAULT_PDF_SCALE,
|
||||
};
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
showPdfViewer = true;
|
||||
|
||||
// Render first page and setup controls
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
await renderPdfPage();
|
||||
updateZoomLabel();
|
||||
setupCanvasEventHandlers();
|
||||
} else if (isImage) {
|
||||
const img = new Image();
|
||||
img.onload = async () => {
|
||||
currentPdf = {
|
||||
doc: null,
|
||||
page: 1,
|
||||
pages: 1,
|
||||
scale: 1,
|
||||
isImage: true,
|
||||
imageElement: img,
|
||||
imageWidth: img.width,
|
||||
imageHeight: img.height,
|
||||
};
|
||||
pdfPan = { x: 0, y: 0, dragging: false, startX: 0, startY: 0, baseX: 0, baseY: 0 };
|
||||
showPdfViewer = true;
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
renderImageOnCanvas();
|
||||
updateZoomLabel();
|
||||
setupCanvasEventHandlers();
|
||||
};
|
||||
img.src = currentPreviewUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Preview load error:', err);
|
||||
}
|
||||
}
|
||||
|
||||
function closeFileViewer() {
|
||||
showFileViewer = false;
|
||||
showPdfViewer = false;
|
||||
currentViewerFile = null;
|
||||
currentPdf = null;
|
||||
if (currentPreviewUrl) {
|
||||
URL.revokeObjectURL(currentPreviewUrl);
|
||||
currentPreviewUrl = null;
|
||||
}
|
||||
showDetailModal = true;
|
||||
}
|
||||
|
||||
@@ -967,8 +1468,50 @@
|
||||
</div>
|
||||
|
||||
<!-- Viewer Content -->
|
||||
<div style="flex: 1; overflow: auto; background: #000; display: flex; align-items: center; justify-content: center;">
|
||||
{#if currentViewerFile.url}
|
||||
<div style="flex: 1; overflow: auto; background: #000; display: flex; align-items: center; justify-content: center; flex-direction: column;">
|
||||
{#if showPdfViewer && currentPdf}
|
||||
<!-- PDF/Image Controls -->
|
||||
<div style="background: #1f2937; color: white; width: 100%; padding: 8px 12px; display: flex; align-items: center; gap: 12px; border-bottom: 1px solid #374151; flex-shrink: 0;">
|
||||
<div style="display: flex; align-items: center; gap: 6px;">
|
||||
<button
|
||||
id="pdfZoomOutBtn"
|
||||
style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
|
||||
>
|
||||
−
|
||||
</button>
|
||||
<button
|
||||
id="pdfZoomResetBtn"
|
||||
style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
|
||||
>
|
||||
{Math.round(currentPdf.scale * 100)}%
|
||||
</button>
|
||||
<button
|
||||
id="pdfZoomInBtn"
|
||||
style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: bold;"
|
||||
>
|
||||
+
|
||||
</button>
|
||||
<span style="font-size: 11px; color: #d1d5db; margin-left: 6px;">
|
||||
{Math.round(currentPdf.scale * 100)}%
|
||||
</span>
|
||||
</div>
|
||||
{#if !currentPdf.isImage && currentPdf.pages > 1}
|
||||
<div style="display: flex; align-items: center; gap: 6px; margin-left: 12px;">
|
||||
<button style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">◀</button>
|
||||
<button style="padding: 4px 8px; background: #6b7280; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">▶</button>
|
||||
<span style="font-size: 11px; color: #d1d5db;">Page <span>{currentPdf.page}</span> / <span>{currentPdf.pages}</span></span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Canvas Viewer -->
|
||||
<div style="flex: 1; overflow: hidden; background: #111; display: flex; align-items: center; justify-content: center; width: 100%;">
|
||||
<canvas
|
||||
id="pdfCanvas"
|
||||
style="max-width: 100%; max-height: 100%; object-fit: contain; background: white; cursor: grab; touch-action: none;"
|
||||
></canvas>
|
||||
</div>
|
||||
{:else if currentViewerFile.url}
|
||||
<iframe
|
||||
src={currentViewerFile.url}
|
||||
style="width: 100%; height: 100%; border: none;"
|
||||
|
||||
Reference in New Issue
Block a user