346 lines
8.8 KiB
PHP
346 lines
8.8 KiB
PHP
<!doctype html>
|
||
<html lang="id">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Preview Dokumen</title>
|
||
|
||
<style>
|
||
:root{
|
||
--bg:#f5f6f8;
|
||
--card:#fff;
|
||
--border:#e5e7eb;
|
||
--text:#111827;
|
||
--muted:#6b7280;
|
||
|
||
--primary:#2563eb;
|
||
--primary2:#1d4ed8;
|
||
}
|
||
|
||
*{ box-sizing:border-box; }
|
||
body{
|
||
margin:0;
|
||
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial;
|
||
background:var(--bg);
|
||
color:var(--text);
|
||
height: 100dvh;
|
||
}
|
||
|
||
/* ===== TOPBAR ===== */
|
||
.topbar{
|
||
position: sticky;
|
||
top:0;
|
||
z-index: 50;
|
||
min-height: 58px;
|
||
background: rgba(255,255,255,.96);
|
||
backdrop-filter: blur(8px);
|
||
border-bottom: 1px solid var(--border);
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:space-between;
|
||
padding: 0 12px;
|
||
gap: 8px;
|
||
}
|
||
|
||
.topbar-left,
|
||
.topbar-right{
|
||
display:flex;
|
||
align-items:center;
|
||
gap:8px;
|
||
white-space:nowrap;
|
||
flex-wrap:wrap;
|
||
}
|
||
|
||
.topbar-center{
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:center;
|
||
flex:1 1 auto;
|
||
pointer-events:none;
|
||
}
|
||
|
||
.topbar-logo{
|
||
height: 50px;
|
||
width:auto;
|
||
opacity:.95;
|
||
user-select:none;
|
||
pointer-events:none;
|
||
display:block;
|
||
}
|
||
|
||
.btn{
|
||
border:1px solid var(--border);
|
||
background:#fff;
|
||
padding:7px 10px;
|
||
border-radius:10px;
|
||
cursor:pointer;
|
||
font-size:14px;
|
||
line-height:1;
|
||
display:inline-flex;
|
||
align-items:center;
|
||
gap:6px;
|
||
text-decoration:none;
|
||
color:var(--text);
|
||
}
|
||
.btn:active{ transform: translateY(1px); }
|
||
|
||
.btn-primary{
|
||
background: var(--primary);
|
||
border-color: var(--primary);
|
||
color:#fff;
|
||
}
|
||
.btn-primary:hover{
|
||
background: var(--primary2);
|
||
border-color: var(--primary2);
|
||
color:#fff;
|
||
}
|
||
|
||
.badge{
|
||
font-size:13px;
|
||
padding:7px 10px;
|
||
border:1px solid var(--border);
|
||
background:#fff;
|
||
border-radius:999px;
|
||
white-space:nowrap;
|
||
color:var(--text);
|
||
}
|
||
|
||
/* responsive: kecilkan kontrol di layar sempit */
|
||
@media (max-width: 1000px){
|
||
.topbar-logo{ height: 28px; }
|
||
.btn{ font-size:12px; padding:6px 8px; border-radius:9px; }
|
||
.badge{ font-size:12px; padding:6px 8px; }
|
||
}
|
||
@media (max-width: 780px){
|
||
/* kalau sempit banget, sembunyikan teks "Dokumen" biar tidak tabrakan */
|
||
.doc-label{ display:none; }
|
||
.topbar{
|
||
padding: 8px 10px;
|
||
flex-wrap:wrap;
|
||
align-items:flex-start;
|
||
}
|
||
.topbar-center{
|
||
order:1;
|
||
flex-basis:100%;
|
||
justify-content:center;
|
||
}
|
||
.topbar-left{
|
||
order:2;
|
||
}
|
||
.topbar-right{
|
||
order:3;
|
||
width:100%;
|
||
justify-content:space-between;
|
||
}
|
||
}
|
||
|
||
/* ===== PDF AREA ===== */
|
||
#pdfWrap{
|
||
height: calc(100dvh - var(--topbar-height, 58px));
|
||
overflow-y: scroll;
|
||
overflow-x: auto;
|
||
padding: 14px;
|
||
scrollbar-gutter: stable both-edges;
|
||
}
|
||
|
||
#pdfPages{
|
||
display:flex !important;
|
||
flex-direction:column !important;
|
||
align-items:center !important;
|
||
gap:14px !important;
|
||
|
||
column-count:1 !important;
|
||
column-gap:0 !important;
|
||
}
|
||
|
||
.pageCanvas{
|
||
display:block !important;
|
||
background:var(--card);
|
||
border:1px solid var(--border);
|
||
border-radius:12px;
|
||
box-shadow:0 2px 10px rgba(0,0,0,.06);
|
||
max-width:100%;
|
||
height:auto;
|
||
}
|
||
|
||
.hint{
|
||
padding: 12px;
|
||
text-align:center;
|
||
color: var(--muted);
|
||
}
|
||
|
||
</style>
|
||
</head>
|
||
|
||
<body>
|
||
|
||
<div class="topbar">
|
||
<!-- LEFT -->
|
||
<div class="topbar-left">
|
||
<button class="btn" onclick="history.back()"><span class="doc-label">Kembali</span></button>
|
||
<span class="badge doc-label">Dokumen {{ $data->nama_dokumen ?? '-'}}</span>
|
||
</div>
|
||
|
||
<!-- CENTER LOGO -->
|
||
<div class="topbar-center">
|
||
<img src="{{ asset('logo/logo_rsabhk.png') }}" alt="Logo" class="topbar-logo">
|
||
</div>
|
||
|
||
<!-- RIGHT -->
|
||
<div class="topbar-right">
|
||
<button class="btn" id="zoomOut" title="Zoom Out">−</button>
|
||
<span class="badge" id="zoomLabel">90%</span>
|
||
<button class="btn" id="zoomIn" title="Zoom In">+</button>
|
||
<button class="btn" id="resetZoom" title="Reset">Reset</button>
|
||
|
||
<a class="btn btn-primary"
|
||
href="/file-download/{{ $id }}"
|
||
title="Download PDF">
|
||
Unduh
|
||
</a>
|
||
|
||
<span class="badge" id="pageInfo">Page 0 / 0</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div id="pdfWrap">
|
||
<div id="pdfPages" class="hint">Loading...</div>
|
||
</div>
|
||
|
||
<script type="module">
|
||
import * as pdfjsLib from "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.min.mjs";
|
||
pdfjsLib.GlobalWorkerOptions.workerSrc =
|
||
"https://cdnjs.cloudflare.com/ajax/libs/pdf.js/5.4.149/pdf.worker.min.mjs";
|
||
|
||
const topbar = document.querySelector('.topbar');
|
||
const pdfWrap = document.getElementById('pdfWrap');
|
||
const pdfPages = document.getElementById('pdfPages');
|
||
|
||
const zoomLabel = document.getElementById('zoomLabel');
|
||
const pageInfo = document.getElementById('pageInfo');
|
||
|
||
const btnZoomIn = document.getElementById('zoomIn');
|
||
const btnZoomOut = document.getElementById('zoomOut');
|
||
const btnReset = document.getElementById('resetZoom');
|
||
|
||
let pdfDoc = null;
|
||
|
||
// default zoom nyaman
|
||
const defaultZoom = () => (window.innerWidth < 1366 ? 0.85 : 0.90);
|
||
let zoom = defaultZoom();
|
||
|
||
// batas lebar render maksimal
|
||
const MAX_DOC_WIDTH = 960; // kecilin jadi 900 kalau masih besar
|
||
|
||
function calcFitScale(page, baseZoom = 1.0) {
|
||
const padding = 56;
|
||
const wrapWidth = Math.max(320, pdfWrap.clientWidth - padding);
|
||
const targetWidth = Math.min(wrapWidth, MAX_DOC_WIDTH);
|
||
|
||
const vp1 = page.getViewport({ scale: 1 });
|
||
const fitScale = targetWidth / vp1.width;
|
||
|
||
return fitScale * baseZoom;
|
||
}
|
||
|
||
async function renderAllPages() {
|
||
if (!pdfDoc) return;
|
||
|
||
const prevScrollTop = pdfWrap.scrollTop;
|
||
pdfPages.innerHTML = '<div class="hint">Rendering...</div>';
|
||
|
||
const numPages = pdfDoc.numPages;
|
||
pageInfo.textContent = `Page 1 / ${numPages}`;
|
||
|
||
pdfPages.innerHTML = '';
|
||
|
||
for (let p = 1; p <= numPages; p++) {
|
||
const page = await pdfDoc.getPage(p);
|
||
const scale = calcFitScale(page, zoom);
|
||
const viewport = page.getViewport({ scale });
|
||
|
||
const canvas = document.createElement('canvas');
|
||
canvas.className = 'pageCanvas';
|
||
|
||
const ctx = canvas.getContext('2d', { alpha: false });
|
||
canvas.width = Math.floor(viewport.width);
|
||
canvas.height = Math.floor(viewport.height);
|
||
|
||
pdfPages.appendChild(canvas);
|
||
await page.render({ canvasContext: ctx, viewport }).promise;
|
||
}
|
||
|
||
zoomLabel.textContent = `${Math.round(zoom * 100)}%`;
|
||
pdfWrap.scrollTop = prevScrollTop;
|
||
}
|
||
|
||
async function loadPdf(url) {
|
||
pdfPages.innerHTML = '<div class="hint">Loading PDF...</div>';
|
||
|
||
const task = pdfjsLib.getDocument({
|
||
url,
|
||
withCredentials: true,
|
||
});
|
||
|
||
pdfDoc = await task.promise;
|
||
|
||
await renderAllPages();
|
||
|
||
// update page info based on scroll
|
||
pdfWrap.addEventListener('scroll', () => {
|
||
const canvases = pdfPages.querySelectorAll('canvas');
|
||
if (!canvases.length) return;
|
||
|
||
const wrapTop = pdfWrap.getBoundingClientRect().top;
|
||
let current = 1;
|
||
|
||
for (let i = 0; i < canvases.length; i++) {
|
||
const rect = canvases[i].getBoundingClientRect();
|
||
if (rect.top - wrapTop < 80) current = i + 1;
|
||
else break;
|
||
}
|
||
|
||
pageInfo.textContent = `Page ${current} / ${pdfDoc.numPages}`;
|
||
}, { passive: true });
|
||
}
|
||
|
||
// zoom handlers
|
||
btnZoomIn.addEventListener('click', async () => {
|
||
zoom = Math.min(1.6, +(zoom + 0.1).toFixed(2));
|
||
await renderAllPages();
|
||
});
|
||
|
||
btnZoomOut.addEventListener('click', async () => {
|
||
zoom = Math.max(0.6, +(zoom - 0.1).toFixed(2));
|
||
await renderAllPages();
|
||
});
|
||
|
||
btnReset.addEventListener('click', async () => {
|
||
zoom = defaultZoom();
|
||
await renderAllPages();
|
||
});
|
||
|
||
function syncTopbarHeight(){
|
||
if (!topbar) return;
|
||
const h = topbar.offsetHeight || 58;
|
||
document.documentElement.style.setProperty('--topbar-height', `${h}px`);
|
||
}
|
||
|
||
// rerender on resize (debounce)
|
||
let resizeTimer = null;
|
||
window.addEventListener('resize', () => {
|
||
clearTimeout(resizeTimer);
|
||
resizeTimer = setTimeout(() => {
|
||
syncTopbarHeight();
|
||
renderAllPages();
|
||
}, 200);
|
||
});
|
||
|
||
// load watermarked pdf endpoint
|
||
syncTopbarHeight();
|
||
loadPdf(`/file-preview/{{ $id }}`);
|
||
</script>
|
||
|
||
</body>
|
||
</html>
|