Comments (11)
Nice, thanks again.
After fiddling a bit with the asynchronous nature of some of the involved functions: Here is an updated version of the action that downloads all pictures
as a single ZIP file. Works for single entity or multiple entities.
Type: Custom action (with HTML template)
Resources: https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
(Is module False
)
JS:
let $injector = widgetContext.$scope.$injector;
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
// generates a ZIP file of pictures, a download link and programmatically clicks it
async function downloadPictureZip(pictures, zipFilename) {
// JSZip magic
const zip = new JSZip();
// Loop through each picture in the array and add it to the zip file
pictures.forEach(picture => {
// Extract the file extension from the base64 string
const mimeType = picture.picture_base64.match(/^data:(.*);base64,/)[1];
const fileExtension = mimeType.split('/')[1];
// Add the base64 image to the zip file with the specified name and extension
zip.file(`${picture.name}.${fileExtension}`, picture.picture_base64.split(',')[1], { base64: true });
});
const zipBlob = await zip.generateAsync({ type: 'blob' });
// Create a URL for the blob and create a link element
const url = URL.createObjectURL(zipBlob);
const link = document.createElement('a');
link.href = url;
link.download = zipFilename;
// Append the link to the body (required for Firefox)
// programmatically click the link and finally clean up
document.body.appendChild(link);
link.click();
URL.revokeObjectURL(url);
document.body.removeChild(link);
}
function getDevicePictures() {
const picturePromises = widgetContext.datasources.map(datasource => {
return new Promise((resolve, reject) => {
deviceService.getDevice(datasource.entityId).subscribe(
device => {
attributeService.getEntityAttributes(
device.id,
'SERVER_SCOPE',
['picture']
).subscribe(
attributes => {
const pictures = attributes.map(attribute => ({
name: datasource.entityName,
picture_base64: attribute.value
}));
resolve(pictures);
},
error => reject(error)
);
},
error => reject(error)
);
});
});
return Promise.all(picturePromises).then(results => results.flat());
}
// Asynchronous operation to fetch pictures and add them to the Zip file
getDevicePictures()
.then(pictures => {
return downloadPictureZip(pictures, 'all_pictures.zip');
})
.catch(error => {
console.error('Error fetching device pictures:', error);
});
from thingsboard.
Nice, thanks for the hint! For reference I got it to work with the following custom action
let $injector = widgetContext.$scope.$injector;
let deviceService = $injector.get(widgetContext.servicesMap.get('deviceService'));
let attributeService = $injector.get(widgetContext.servicesMap.get('attributeService'));
// generates a download link and programmatically clicks it
function downloadBase64Picture(picture, fileName) {
// Extract the mime type from the base64 string and set extension
const mimeType = picture.match(/^data:(.*);base64,/)[1];
const fileExtension = mimeType.split('/')[1];
const link = document.createElement('a');
link.href = picture;
link.download = `${fileName}.${fileExtension}`;
// Append the link to the body (required for Firefox)
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
function getDevicePicture() {
deviceService.getDevice(entityId.id).subscribe(
function (device) {
attributeService.getEntityAttributes(
device.id,
'SERVER_SCOPE',
['picture']
).subscribe(
function (attributes) {
for (let i = 0; i < attributes.length; i++) {
downloadBase64Picture(attributes[i].value, entityName)
}
}
);
}
);
}
getDevicePicture();
This works well for Widgets that select a single entity.
Any idea how this could be adapted to also work for Widgets that select multiple entities?
from thingsboard.
Several notes:
deviceService.getDevice(datasource.entity.id.id)
- you don't use the result of this API call, so you don't need it- Just call
widgetContext.attributeService
instead of injector-based approach. - Shorter form of
datasource.entity.id.id
isdatasource.entityId
from thingsboard.
You can create custom action and create your image file on the fly. For details see this SO thread
from thingsboard.
@dmohns Thanks for sharing the solution, will add it to awesome-thingsboard
from thingsboard.
@dmohns Thanks for sharing the solution, will add it to awesome-thingsboard
Nice! Thank you. Please note, that in my example picture
attribute is stored as a SERVER_SCOPE
attribute. Can't remember if this was the default behaviour, or something I customised in my setup.
from thingsboard.
Any idea how this could be adapted to also work for Widgets that select multiple entities?
What widget do you need? For example, if you use Entities Table widget, there are rowClick
, doubleClick
, actionCell
and upcoming cellClick
(in 3.7), where you can do the same as you suggested above
from thingsboard.
Any idea how this could be adapted to also work for Widgets that select multiple entities?
What widget do you need? For example, if you use Entities Table widget, there are
rowClick
,doubleClick
,actionCell
and upcomingcellClick
(in 3.7), where you can do the same as you suggested above
In fact, I have two use cases.
- For the simple use-case I have a single Device Dashboard from which I want to be able to extract the devices pictures. This works already with the widget action above.
- Additionally, I have an asset Dashboard which shows multiple devices that are assigned to that asset. This is implemented as an Entity Alias that "resolves as multiple entities". I want to give users the possibility to download all of the related device pictures at once.
On this dashboard, I do show entities in an entity table widget (and want to add the download button there). Adding the code above only downloads the first image in the list.
This is because entityName
and entityId.id
seem to only link the first image.
My question is: How can I access the entire list of entityNames and Id's for all entities in the Widget programatically?
from thingsboard.
How can I access the entire list of entityNames and Id's for all entities in the Widget programatically?
widgetContext.datasources
- entire list of entities, with console.log(widgetContext.datasources)
you can explore its structure
from thingsboard.
Thanks for all the further advice!
deviceService.getDevice(datasource.entity.id.id)
- you don't use the result of this API call, so you don't need it
Good catch. I changed the code around a little bit, such that I am now re-using the result of getDevice
as input for getEntityAttributes
. I understand this is not strictly needed, but I find it a bit more general, and might help users that want to customise the logic.
- Shorter form of
datasource.entity.id.id
isdatasource.entityId
👍
- Just call
widgetContext.attributeService
instead of injector-based approach.
I don't fully understand this (and to an extend, not what even an injector is either 😇 ). I'm not sure how much of an improvement this is and if it's worth the time to further change the code.
Too not have too many code blocks floating around in this thread I will update previously shared snippets with the suggestions.
PS: This main goal of this issue was resolved, I think we can close it. WDYT?
from thingsboard.
WDYT?
It is up to you, to close or not. In general, you made awesome contribution by sharing the solution.
I'm not sure how much of an improvement this is and if it's worth the time to further change the code.
Frankly speaking I just don't like TB's injector approach of getting access to one of available API services. In my opinion, the shorter form (widgetContext.someApiService
) should be a single way especially if the autocomplete feature is up to date.
from thingsboard.
Related Issues (20)
- What is "profile type" of a device profile ? HOT 1
- Access device claiming timestamp
- Add an option to customise the file name for widget data export
- Searching dashboards as a customer with entityquerycontroller HOT 3
- Heatmap Widget HOT 2
- Back button HOT 2
- Toast Notifications in Custom Action and Markdown HTML HOT 1
- kafka always error HOT 4
- I can not find gen.MsgProtos class. HOT 1
- How to remove link underline in thingsboard. HOT 4
- docker mkdir: cannot create directory ‘/data/db’: Permission denied HOT 5
- OPC-UA browse name & display name HOT 3
- Rule Engine to validate api request
- Trendz Loading HOT 1
- websocket api failed to unsubscribe data HOT 2
- Widget title internationalization
- typo in locale-de_DE.json HOT 1
- Count alarme by customer HOT 2
- Changing the device customers
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from thingsboard.