TF-2737 Separate PDF view handler between each browser

This commit is contained in:
DatDang
2024-03-21 17:46:22 +07:00
committed by Dat H. Pham
parent 6ccdbd66c4
commit 754987b59e
4 changed files with 262 additions and 35 deletions
@@ -109,17 +109,25 @@ class DownloadManager {
}
}
Future<void> openDownloadedFileWeb(Uint8List bytes, String? mimeType) async {
Future<void> openDownloadedFileWeb(
Uint8List bytes,
String? mimeType,
String? fileName
) async {
try {
fileName ??= 'unknown.pdf';
final deviceInfo = await _deviceInfoPlugin.deviceInfo;
if ('${deviceInfo.data['browserName']}' != 'BrowserName.chrome'
|| mimeType != Constant.pdfMimeType) {
if (mimeType != Constant.pdfMimeType
|| '${deviceInfo.data['browserName']}' == 'BrowserName.firefox') {
final blob = html.Blob([bytes], mimeType);
final url = html.Url.createObjectUrlFromBlob(blob);
final file = html.File([blob], fileName, {'type': mimeType});
final url = html.Url.createObjectUrl(file);
html.window.open(url, '_blank');
html.Url.revokeObjectUrl(url);
} else if ('${deviceInfo.data['browserName']}' == 'BrowserName.chrome') {
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.chromePdfViewer(bytes, fileName));
} else {
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.pdfViewer(bytes));
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.safariPdfViewer(bytes, fileName));
}
} catch (exception) {
logError('DownloadManager::openDownloadedFileWeb(): ERROR: $exception');
+241 -27
View File
@@ -1,3 +1,5 @@
import 'dart:convert';
import 'js_interop_stub.dart' if (dart.library.html) 'dart:js_interop';
import 'dart:typed_data';
@@ -151,44 +153,256 @@ class HtmlUtils {
html.Url.revokeObjectUrl(url);
}
static get iframeFullScreenCssStyle => '''
position: fixed;
border: 0;
width: 100%;
height: 100%;''';
static String pdfViewer(Uint8List bytes) {
static String chromePdfViewer(Uint8List bytes, String fileName) {
return '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<meta charset="utf-8" />
<title>$fileName</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.mjs" type="module"></script>
<style>
body {
margin: 0;
background-color: black;
}
iframe {
${HtmlUtils.iframeFullScreenCssStyle}
#pdf-container {
$_pdfContainerStyle
}
#pdf-viewer {
$_pdfViewerStyle
}
#app-bar {
$_pdfAppBarStyle
}
#download-btn {
$_pdfDownloadButtonStyle
}
#file-info {
$_pdfFileInfoStyle
}
#file-name {
$_pdfFileNameStyle
}
</style>
<body></body>
<script>
const bytesJs = new Uint8Array(${bytes.toJS}).buffer;
const blob = new Blob([bytesJs], { type: "application/pdf" });
const url = URL.createObjectURL(blob);
</head>
<body>
<div id="pdf-container">
$_pdfAppbarElement
<div id="pdf-viewer"></div>
</div>
window.onload = function() {
const iframe = document.createElement("iframe");
iframe.src = url;
document.body.appendChild(iframe);
};
window.addEventListener("beforeunload", function listener(event) {
URL.revokeObjectURL(url);
window.removeEventListener("beforeunload", listener);
});
</script>
<script type="module">
function renderPage(pdfDoc, pageNumber, canvas) {
pdfDoc.getPage(pageNumber).then(page => {
const viewport = page.getViewport({ scale: 1.5 });
canvas.height = viewport.height;
canvas.width = viewport.width;
const context = canvas.getContext('2d');
const renderContext = {
canvasContext: context,
viewport: viewport
};
page.render(renderContext);
});
}
const bytesJs = new Uint8Array(${bytes.toJS});
const pdfContainer = document.getElementById('pdf-viewer');
var { pdfjsLib } = globalThis;
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.worker.min.mjs';
var loadingTask = pdfjsLib.getDocument(bytesJs);
loadingTask.promise.then(function(pdf) {
const numPages = pdf.numPages;
for (let i = 1; i <= numPages; i++) {
const pageContainer = document.createElement('div');
pageContainer.classList.add('pdf-page');
const canvas = document.createElement('canvas');
canvas.id = `page-\${i}`;
pageContainer.appendChild(canvas);
pdfContainer.appendChild(pageContainer);
renderPage(pdf, i, canvas);
}
}, function (reason) {
console.error(reason);
});
${_fileInfoScript(bytes, fileName)}
${_downloadButtonListenerScript(bytes, fileName)}
</script>
</body>
</html>''';
}
static String safariPdfViewer(Uint8List bytes, String fileName) {
final base64 = base64Encode(bytes);
return '''
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>$fileName</title>
<style>
body {
background-color: black;
}
#pdf-container {
$_pdfContainerStyle
overflow: hidden;
}
#pdf-viewer {
$_pdfViewerStyle
width: 100%;
height: calc(100vh - 53px);
}
#app-bar {
$_pdfAppBarStyle
}
#download-btn {
$_pdfDownloadButtonStyle
}
#file-info {
$_pdfFileInfoStyle
}
#file-name {
$_pdfFileNameStyle
}
</style>
</head>
<body>
<div id="pdf-container">
$_pdfAppbarElement
<div id="pdf-viewer"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdfobject/2.3.0/pdfobject.min.js"></script>
<script>
const bytesJs = new Uint8Array(${bytes.toJS});
PDFObject.embed('data:application/pdf;base64,$base64', "#pdf-viewer");
${_fileInfoScript(bytes, fileName)}
${_downloadButtonListenerScript(bytes, fileName)}
</script>
</body>
</html>''';
}
static const String _pdfContainerStyle = '''
display: flex;
flex-direction: column;
width: 100%;''';
static const String _pdfViewerStyle = '''
flex: 1; /* Allow viewer to fill remaining space */
border: 1px solid #ddd;
margin-left: auto;
margin-right: auto;
padding-top: 53px;
border: none;''';
static const String _pdfAppBarStyle = '''
position: fixed; /* Fix app bar to top */
top: 0;
left: 0;
right: 0; /* Stretch across entire viewport */
display: flex;
justify-content: space-between;
padding: 5px 10px;
background-color: #f0f0f0;
z-index: 100; /* Ensure buttons stay on top */''';
static const String _pdfDownloadButtonStyle = '''
padding: 5px 10px;
border: 1px solid #ddd;
border-radius: 5px;
cursor: pointer;
margin-left: 10px;''';
static const String _pdfFileInfoStyle = '''
width: 30%;
display: flex;
align-items: center;
padding: 5px 10px;''';
static const String _pdfFileNameStyle = '''
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;''';
static const String _pdfAppbarElement = '''
<div id="app-bar">
<div id="file-info">
<span id="file-name" style="margin-right: 10px;"></span>
(<span id="file-size", style="white-space: nowrap;"></span>)
</div>
<div style="width: 10px;"></div>
<div id="buttons">
<button id="download-btn">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19 20V18H5V20H19ZM19 10H15V4H9V10H5L12 17L19 10Z" fill="#7B7B7B"/>
</svg>
</button>
</div>
</div>''';
static String _downloadButtonListenerScript(Uint8List bytes, String? fileName) {
return '''
const downloadBtn = document.getElementById('download-btn');
downloadBtn.addEventListener('click', () => {
const buffer = new Uint8Array(${bytes.toJS}).buffer;
const blob = new Blob([buffer], { type: "application/pdf" });
const url = window.URL.createObjectURL(blob);
const a = document.createElement('a');
a.download = "$fileName";
a.href = url;
document.body.appendChild(a);
a.click();
window.URL.revokeObjectURL(url);
document.body.removeChild(a);
});''';
}
static String _fileInfoScript(Uint8List bytes, String? fileName) {
return '''
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat(bytes / Math.pow(k, i)).toFixed(2) + ' ' + sizes[i];
}
const fileNameSpan = document.getElementById('file-name');
fileNameSpan.textContent = "$fileName";
const fileSizeSpan = document.getElementById('file-size');
const byteArray = new Uint8Array(${bytes.toJS});
fileSizeSpan.textContent = formatFileSize(bytesJs.length);''';
}
}
@@ -828,7 +828,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_downloadManager.openDownloadedFileWeb(
success.bytes,
success.attachment.type?.mimeType);
success.attachment.type?.mimeType,
success.attachment.name);
}
void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) {
@@ -178,7 +178,10 @@ void main() {
'when attachment mime type is pdf',
() async {
// arrange
final testAttachment = Attachment(type: MediaType('application', 'pdf'));
const attachmentName = 'test_name.pdf';
final testAttachment = Attachment(
type: MediaType('application', 'pdf'),
name: attachmentName);
when(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
when(viewAttachmentForWebInteractor.execute(
testDownloadTaskId,
@@ -201,10 +204,11 @@ void main() {
await untilCalled(mailboxDashboardController.deleteDownloadTask(any));
verify(mailboxDashboardController.deleteDownloadTask(testDownloadTaskId))
.called(1);
await untilCalled(downloadManager.openDownloadedFileWeb(any, any));
await untilCalled(downloadManager.openDownloadedFileWeb(any, any, any));
verify(downloadManager.openDownloadedFileWeb(
testBytes,
Constant.pdfMimeType,
attachmentName,
)).called(1);
},
);