Git Product home page Git Product logo

Comments (11)

dmohns avatar dmohns commented on June 12, 2024 2

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.

dmohns avatar dmohns commented on June 12, 2024 1

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.

devaskim avatar devaskim commented on June 12, 2024 1

Several notes:

  1. deviceService.getDevice(datasource.entity.id.id) - you don't use the result of this API call, so you don't need it
  2. Just call widgetContext.attributeService instead of injector-based approach.
  3. Shorter form of datasource.entity.id.id is datasource.entityId

from thingsboard.

devaskim avatar devaskim commented on June 12, 2024

You can create custom action and create your image file on the fly. For details see this SO thread

from thingsboard.

devaskim avatar devaskim commented on June 12, 2024

@dmohns Thanks for sharing the solution, will add it to awesome-thingsboard

from thingsboard.

dmohns avatar dmohns commented on June 12, 2024

@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.

devaskim avatar devaskim commented on June 12, 2024

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.

dmohns avatar dmohns commented on June 12, 2024

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

In fact, I have two use cases.

  1. 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.
  2. 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.

devaskim avatar devaskim commented on June 12, 2024

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.

dmohns avatar dmohns commented on June 12, 2024

Thanks for all the further advice!

  1. 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.

  1. Shorter form of datasource.entity.id.id is datasource.entityId

👍

  1. 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.

devaskim avatar devaskim commented on June 12, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.