I would like to use the react-to-print library to print an iframe from my Electron app. How can I use the iframe reference to get the correct window/element to print?
const handleElectronPrint = async (target: HTMLIFrameElement) {
// Instead of this (printing the whole page)
// let win = BrowserWindow.getFocusedWindow();
// How do I print just the referenced iframe?
// `target` iframe has id="printWindow", how to select it?
let win = BrowserWindow.getMyIframe();
// Is this the right way to do the print once we have the iframe?
const options = { printBackground: true };
win.webContents.print(options, (success, failureReason) => {
if (!success) console.log(failureReason);
console.log('Print Initiated');
});
};
<ReactToPrint
...
print={handleElectronPrint}
/>
You need to convert the iframe object to Data URL. And load the URL in a new hidden BrowserWindow object.
Build data URL in Renderer process and send the URL to Main process using preload. In main process do the BrowserWindow.loadURL and printing.
App.js
// Send print request to the Main process
this.handlePrint = function (target) {
return new Promise(() => {
console.log('forwarding print request to the main process...');
// convert the iframe into data url
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs
let data = target.contentWindow.document.documentElement.outerHTML;
//console.log(data);
var blob = new Blob([data], { type: 'text/html' });
var url = URL.createObjectURL(blob);
window.electronAPI.printComponent(url, (response) => {
console.log('Main: ', response);
});
});
};
main.js
// List of all options at -
// https://www.electronjs.org/docs/latest/api/web-contents#contentsprintoptions-callback
const printOptions = {
silent: false,
printBackground: true,
color: true,
margin: {
marginType: 'printableArea',
},
landscape: false,
pagesPerSheet: 1,
collate: false,
copies: 1,
header: 'Page header',
footer: 'Page footer',
};
ipcMain.handle('printComponent', (event, url) => {
let win = new BrowserWindow({ show: false });
win.loadURL(url);
win.webContents.on('did-finish-load', () => {
win.webContents.print(printOptions, (success, failureReason) => {
console.log('Print Initiated in Main...');
if (!success) console.log(failureReason);
});
});
return 'done in main';
});
preload.js
const { contextBridge, ipcRenderer } = require('electron');
contextBridge.exposeInMainWorld('electronAPI', {
printComponent: async (url, callback) => {
let response = await ipcRenderer.invoke('printComponent', url);
callback(response);
},
});
Here is the list of all print options. Some options like page size, margins, orientation can be set in CSS #page rule refer App.css in my demo app.
Here is demo app on GitHub electron-react-to-print-demo.
Print Preview: There is no, Chrome browser style, inbuilt print preview feature due to these reasons. We need to implement our own workaround. Like print to PDF and show pdf in new window:
//handle preview
ipcMain.handle('previewComponent', (event, url) => {
let win = new BrowserWindow({ title: 'Preview', show: false, autoHideMenuBar: true });
win.loadURL(url);
win.webContents.once('did-finish-load', () => {
win.webContents.printToPDF(printOptions).then((data) => {
let buf = Buffer.from(data);
var data = buf.toString('base64');
let url = 'data:application/pdf;base64,' + data;
win.webContents.on('ready-to-show', () => {
win.show();
win.setTitle('Preview');
});
win.webContents.on('closed', () => win = null;);
win.loadURL(url);
})
.catch((error) => {
console.log(error);
});
});
return 'shown preview window';
});
I've added above preview feature in electron-react-to-print-demo.
Related
I want to use web share api, so i can share specific image coming from google spreadsheet with text and description, but unfortunatelly only image is being shared to facebook. The text, title and url it's missing. For whatsapp if working fine for example. Can anyone give hand for this? Thanks
Code below:
`
async function share() {
const imageQuote = '<?=$image_quote; ?>';
const response = await fetch(imageQuote);
const blob = await response.blob();
let pageTitle = 'THe Page';
let pageText = document.getElementsByClassName("quote")[0].innerText;
let pageUrl = window.location.href;
const filesArray = [
new File(
[blob],
'panion.jpg',
{
type: blob.type,
lastModified: new Date().getTime()
}
)
];
const shareData = {
title: pageTitle,
text: pageText,
url: pageUrl,
files: filesArray,
};
if (navigator.share) {
navigator
.share(shareData)
.then(() => console.log("Successful share"))
.catch((error) => console.log("Error sharing", error));
} else {
console.error("Browser doesn't support Web Share API");
}
console.log(shareData);
}
function shareThisQuote() {
return share();
}
`
I'm trying to add a image upload feature to react-draft-wysiwyg editor.
As per the editor documentation,
image: uploadCallback: This is image upload callBack. It should return a promise that resolves to give image src. Default value is true.
Both above options of uploadEnabled and uploadCallback should be present for upload to be enabled.
Promise should resolve to return an object { data: { link: <THE_URL>}}.
Source : https://jpuri.github.io/react-draft-wysiwyg/#/docs
The problem I'm facing is that, while uploading the image to firebase, and the resultant url, I'm trying to use it to create this object for returning from this callback.
Here, in code, firstly image is being uploaded using uploadBytes method of firebase, then using getDownloadURL, we are getting the url of the file. There is a bug that the object with undefined url link is being returned from the callback.
const uploadImageCallBack = (file) => {
let linkImg = "";
let arr = [];
const saveImagePromise = new Promise((resolve, reject) => {
const fileNameParts = file.name.split(".");
const storage = getStorage(app);
const storageRef = ref(
storage,
"Random/" +
title +
"/editorImage." +
uuidv4() +
fileNameParts[fileNameParts.length - 1]
);
debugger;
const metadata = {
contentType: file.type,
};
try {
const uploadTask = uploadBytes(storageRef, file, metadata);
debugger;
uploadTask.then((snapshot) => {
debugger;
const downloadURLPromise = getDownloadURL(storageRef);
downloadURLPromise.then((url) => {
linkImg = url;
debugger;
});
arr.push(downloadURLPromise);
});
arr.push(uploadTask);
} catch (error) {
console.log(error);
reject(error);
}
});
arr.push(uploadBytes, saveImagePromise);
console.log(Infinity);
Promise.all(arr).then((res) => {
console.log(res);
console.log(Infinity);
return new Promise((resolve, reject) => {
resolve({ data: { link: linkImg } });
});
});
};
and the code for editor is
<Editor
toolbar={{
inline: { inDropdown: true },
list: { inDropdown: true },
textAlign: { inDropdown: true },
link: { inDropdown: true },
history: { inDropdown: true },
image: {
urlEnabled: true,
uploadEnabled: true,
alignmentEnabled: true,
uploadCallback: uploadImageCallBack,
previewImage: true,
inputAccept:
"image/gif,image/jpeg,image/jpg,image/png,image/svg",
alt: { present: true, mandatory: false },
defaultSize: {
height: "auto",
width: "auto",
},
},
}}
onContentStateChange={(data) => {
let res = convertToPlain(draftToHtml(data));
console.log(data);
setReasonProgress(
remainigchar >= 100 ? 100 : remainigchar
);
}}
wrapperClassName="wrapper-class"
editorClassName="editor-class"
toolbarClassName="toolbar-class"
/>```
Please help me to create a correct return statement.
I solved this problem by using async function and adding wait statement for the url.
const uploadImageCallBack = async (file) => {
const fileNameParts = file.name.split(".");
const storage = getStorage(app);
const storageRef = ref(
storage,
uuidv4() +
fileNameParts[fileNameParts.length - 1]
);
let imageObject = {
file: file,
localSrc: URL.createObjectURL(file),
};
const metadata = {
contentType: file.type,
};
const snapshot = await uploadBytes(storageRef, file, metadata);
const url = await getDownloadURL(storageRef);
console.log(url, snapshot);
return new Promise((resolve, reject) => {
resolve({ data: { link: url } });
});
};
I'm using javascript scripting with a legacy version of iMacros (v8.9.7)
And I'm a bit confused about getting fingerprinting JS to work properly.
My use case :
1- load fingerprintingJS from CDN asynchronously
2- store the visitorID into variable
My code :
Components.utils.import("resource://gre/modules/Promise.jsm");
Components.utils.import("resource://gre/modules/devtools/Console.jsm");
function initFingerprintJS() {
FingerprintJS.load()
.then(fp => fp.get())
.then(result => {
const visitorId = result.visitorId;
window.console.log(visitorId);
});
}
const loadScript = (src, async = true, type = "text/javascript") => {
return new Promise((resolve, reject) => {
try {
const el = window.document.createElement("script");
const container = window.document.head || window.document.body;
el.type = type;
el.async = async;
el.src = src;
el.addEventListener("load", () => {
resolve({ status: true });
});
el.addEventListener("error", () => {
reject({
status: false,
message: `Failed to load the script ${src}`
});
});
container.appendChild(el);
} catch (err) {
reject(err);
}
});
};
function get_fingerprint() {
loadScript("https://cdn.jsdelivr.net/npm/#fingerprintjs/fingerprintjs#3/dist/fp.min.js")
.then((data) => {
window.console.log("Script loaded successfully", data);
initFingerprintJS();
})
.catch((err) => {
window.console.error(err);
});
}
<button onclick="get_fingerprint()">FINGERPRINT</button>
when I remove the two Components imports and run the bit of script in the console, everything works. But when it's from iMacros interface, it generates errors
GET
https://cdn.jsdelivr.net/npm/#fingerprintjs/fingerprintjs#3/dist/fp.min.js
[HTTP/2.0 200 OK 0 ms] Script loaded successfully Object { status:
true } ReferenceError: FingerprintJS is not defined Trace de la pile :
initFingerprintJS#resource://gre/modules/RemoteAddonsParent.jsm:1102:5
get_fingerprint/<#resource://gre/modules/RemoteAddonsParent.jsm:1142:9
Handler.prototype.process#resource://gre/modules/Promise.jsm ->
resource://gre/modules/Promise-backend.js:932:23
this.PromiseWalker.walkerLoop#resource://gre/modules/Promise.jsm ->
resource://gre/modules/Promise-backend.js:813:7
this.PromiseWalker.scheduleWalkerLoop/<#resource://gre/modules/Promise.jsm
-> resource://gre/modules/Promise-backend.js:747:11
Any helps please
I have a web-page with set of 'li' elements. I need to make screenshots for each 'li' element and save it to a new files.
I'm trying to use nightmare-screenshot-selector for it.
But I get a few files with the same screenshot but with different names (from my array).
Here is my code.
const Nightmare = require('nightmare');
const fs = require('fs');
const screenshotSelector = require('nightmare-screenshot-selector');
Nightmare.action('screenshotSelector', screenshotSelector);
function savePicture(picture) {
picture = ['v1', 'v2', 'v3'];
let browser = Nightmare({
show: false,
webPreferences: {
partition: 'nopersist'
}
});
browser
.goto('https://www.google.com')
picture.forEach(v => {
browser
.wait(7000)
.screenshotSelector(`li[class="${v}"]`)
.then(function (data) {
fs.writeFileSync(`img/${v}.png`, data)
})
.catch((error) => {
console.log('Error loading the page', error)
})
})
browser.end();
}
I inserted a .end() call, works for me. Slightly modified code, grabs two regions of Google home page:
function savePicture(picture) {
picture = ['div#hplogo', 'div.tsf-p'];
let browser = Nightmare({
show: false,
webPreferences: {
partition: 'nopersist'
}
});
browser
.goto('https://www.google.com')
picture.forEach(v => {
browser
.wait(2000)
.screenshotSelector(v)
.then(function (data) {
fs.writeFileSync(`img/${v}.png`, data)
})
.then(()=> {
browser.end();
})
.catch((error) => {
console.log('Error loading the page', error)
})
})
}
I am trying to upload a file to web api which takes the file as byte array using angular 2 application.
I am not able to pass the byte array from angular 2 page to web api. It looks like the File Reader read method is asynchronous. How do I make this as synchronous call or wait for the file content to be loaded before executing the next line of code?
Below is my code
//attachment on browse - when the browse button is clicked
//It only assign the file to a local variable (attachment)
fileChange = (event) => {
var files = event.target.files;
if (files.length > 0) {
this.attachment = files[0];
}
}
//when the submit button is clicked
onSubmit = () => {
//Read the content of the file and store it in local variable (fileData)
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
this.fileData = fr.result; //Note : This always "undefined"
};
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: this.fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
//call the web api and pass the purchaseorder object
this.updatePoService
.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order)
.subscribe(data => {
if (data) {
this.saveSuccess = true;
}
else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
Any hint would be useful.
regards,
-Alan-
You cannot make this async call synchronous. But you can take advantage of the observables to wait for the files to be read:
//when the submit button is clicked
onSubmit = () => {
let file = Observable.create((observer) => {
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
observer.next(fr.result);
observer.complete()
};
fr.onerror = (err) => {
observer.error(err)
}
fr.onabort = () => {
observer.error("aborted")
}
});
file.map((fileData) => {
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
return order;
})
.switchMap(order => this.updatePoService.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order))
.subscribe(data => {
if (data) {
this.saveSuccess = true;
} else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
I arrived here looking for a solution for a similar issue. I'm performing requests to an endpoint which can response a binary blob if anything goes well or a JSON file in event of error.
this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
if (err instanceof HttpErrorResponse) {
// here, err.error is a BLOB containing a JSON String with the error message
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
As FileReaderSync apparently doesn't work in Angular6 I took n00dl3's solution (above) to throw the error after parsing the Blob content:
return this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
const message = `In TtsService.getTts(${locale},${outputFormat}). ${err.message}`;
if (err instanceof HttpErrorResponse) {
const $errBlobReader: Observable<HttpErrorResponse> = Observable.create((observer) => {
const fr = new FileReader();
const errorBlob = err.error;
fr.readAsText(errorBlob, 'utf8');
fr.onloadend = () => {
const errMsg = JSON.parse(fr.result).message;
const msg = `In TtsService.getTts(${locale},${outputFormat}). ${errMsg}`;
observer.error(ErrorDataService.overLoadError(err, msg));
};
fr.onerror = (blobReadError) => {
observer.error(blobReadError);
};
fr.onabort = () => {
observer.error('aborted');
};
});
return $errBlobReader;
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
Thanks! You really saved my day!