TF-2737 Separate PDF view handler between each browser
This commit is contained in:
@@ -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 {
|
try {
|
||||||
|
fileName ??= 'unknown.pdf';
|
||||||
final deviceInfo = await _deviceInfoPlugin.deviceInfo;
|
final deviceInfo = await _deviceInfoPlugin.deviceInfo;
|
||||||
if ('${deviceInfo.data['browserName']}' != 'BrowserName.chrome'
|
if (mimeType != Constant.pdfMimeType
|
||||||
|| mimeType != Constant.pdfMimeType) {
|
|| '${deviceInfo.data['browserName']}' == 'BrowserName.firefox') {
|
||||||
final blob = html.Blob([bytes], mimeType);
|
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.window.open(url, '_blank');
|
||||||
html.Url.revokeObjectUrl(url);
|
html.Url.revokeObjectUrl(url);
|
||||||
|
} else if ('${deviceInfo.data['browserName']}' == 'BrowserName.chrome') {
|
||||||
|
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.chromePdfViewer(bytes, fileName));
|
||||||
} else {
|
} else {
|
||||||
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.pdfViewer(bytes));
|
HtmlUtils.openNewTabHtmlDocument(HtmlUtils.safariPdfViewer(bytes, fileName));
|
||||||
}
|
}
|
||||||
} catch (exception) {
|
} catch (exception) {
|
||||||
logError('DownloadManager::openDownloadedFileWeb(): ERROR: $exception');
|
logError('DownloadManager::openDownloadedFileWeb(): ERROR: $exception');
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'js_interop_stub.dart' if (dart.library.html) 'dart:js_interop';
|
import 'js_interop_stub.dart' if (dart.library.html) 'dart:js_interop';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
@@ -151,44 +153,256 @@ class HtmlUtils {
|
|||||||
html.Url.revokeObjectUrl(url);
|
html.Url.revokeObjectUrl(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
static get iframeFullScreenCssStyle => '''
|
static String chromePdfViewer(Uint8List bytes, String fileName) {
|
||||||
position: fixed;
|
|
||||||
border: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;''';
|
|
||||||
|
|
||||||
static String pdfViewer(Uint8List bytes) {
|
|
||||||
return '''
|
return '''
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
</head>
|
<title>$fileName</title>
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.0.379/pdf.min.mjs" type="module"></script>
|
||||||
<style>
|
<style>
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|
||||||
iframe {
|
#pdf-container {
|
||||||
${HtmlUtils.iframeFullScreenCssStyle}
|
$_pdfContainerStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
#pdf-viewer {
|
||||||
|
$_pdfViewerStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
#app-bar {
|
||||||
|
$_pdfAppBarStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
#download-btn {
|
||||||
|
$_pdfDownloadButtonStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-info {
|
||||||
|
$_pdfFileInfoStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
#file-name {
|
||||||
|
$_pdfFileNameStyle
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<body></body>
|
</head>
|
||||||
<script>
|
<body>
|
||||||
const bytesJs = new Uint8Array(${bytes.toJS}).buffer;
|
<div id="pdf-container">
|
||||||
const blob = new Blob([bytesJs], { type: "application/pdf" });
|
$_pdfAppbarElement
|
||||||
const url = URL.createObjectURL(blob);
|
<div id="pdf-viewer"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
window.onload = function() {
|
<script type="module">
|
||||||
const iframe = document.createElement("iframe");
|
function renderPage(pdfDoc, pageNumber, canvas) {
|
||||||
iframe.src = url;
|
pdfDoc.getPage(pageNumber).then(page => {
|
||||||
document.body.appendChild(iframe);
|
const viewport = page.getViewport({ scale: 1.5 });
|
||||||
};
|
canvas.height = viewport.height;
|
||||||
window.addEventListener("beforeunload", function listener(event) {
|
canvas.width = viewport.width;
|
||||||
URL.revokeObjectURL(url);
|
|
||||||
window.removeEventListener("beforeunload", listener);
|
const context = canvas.getContext('2d');
|
||||||
});
|
const renderContext = {
|
||||||
</script>
|
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>''';
|
</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(
|
_downloadManager.openDownloadedFileWeb(
|
||||||
success.bytes,
|
success.bytes,
|
||||||
success.attachment.type?.mimeType);
|
success.attachment.type?.mimeType,
|
||||||
|
success.attachment.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) {
|
void _downloadAttachmentForWebFailureAction(DownloadAttachmentForWebFailure failure) {
|
||||||
|
|||||||
@@ -178,7 +178,10 @@ void main() {
|
|||||||
'when attachment mime type is pdf',
|
'when attachment mime type is pdf',
|
||||||
() async {
|
() async {
|
||||||
// arrange
|
// 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(mailboxDashboardController.sessionCurrent).thenReturn(testSession);
|
||||||
when(viewAttachmentForWebInteractor.execute(
|
when(viewAttachmentForWebInteractor.execute(
|
||||||
testDownloadTaskId,
|
testDownloadTaskId,
|
||||||
@@ -201,10 +204,11 @@ void main() {
|
|||||||
await untilCalled(mailboxDashboardController.deleteDownloadTask(any));
|
await untilCalled(mailboxDashboardController.deleteDownloadTask(any));
|
||||||
verify(mailboxDashboardController.deleteDownloadTask(testDownloadTaskId))
|
verify(mailboxDashboardController.deleteDownloadTask(testDownloadTaskId))
|
||||||
.called(1);
|
.called(1);
|
||||||
await untilCalled(downloadManager.openDownloadedFileWeb(any, any));
|
await untilCalled(downloadManager.openDownloadedFileWeb(any, any, any));
|
||||||
verify(downloadManager.openDownloadedFileWeb(
|
verify(downloadManager.openDownloadedFileWeb(
|
||||||
testBytes,
|
testBytes,
|
||||||
Constant.pdfMimeType,
|
Constant.pdfMimeType,
|
||||||
|
attachmentName,
|
||||||
)).called(1);
|
)).called(1);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user