AWS Comprehend Medical: Invalid Signature Exception Error (JavaScriptv3, Node.js)

I am trying to learn how to use AWS’s Comprehend Medical Tool and I am not exactly sure why I am getting this InvalidSignatureException. I am new to using AWS tools for reference. Below is my index.js file and my package.json file. I setup my AWS credentials in a shared file on my computer with the access key and secret access key.

index.js file: Where I create the client and try to access the detectEntities() function from an example text that I got from the Comprehend Medical webpage.

import {S3Client} from "@aws-sdk/client-s3"
import * as AWS from "@aws-sdk/client-s3"
import {ComprehendMedicalClient, DetectEntitiesV2Command} from "@aws-sdk/client-comprehendmedical";
import {fromIni} from "@aws-sdk/credential-providers"
//AWS.config.loadFromPath('./config.json');

// const REGION = "us-east-2";
//const s3Client = new S3Client(config);

const client = new ComprehendMedicalClient({
    credentials: fromIni({profile: 'default'}),
    region: "us-east-2"
});

const input = {
    Text: "Pt is 87 yo woman, highschool teacher with past medical history that includes - status post cardiac catheterization in April 2019. She presents today palpitations and chest pressure. HPI: Sleeping trouble on present dosage of Clonidine. Severe Rash on face and leg, slightly itchy. Meds: Vyvanse 50 mgs po at breakfast daily, Clonidine 0.2 mgs -- 1 and 1 / 2 tabs po qhs. HEENT: Boggy inferior turbinates, No oropharyngeal lesion. Lungs : clear. Heart : Regular rhythm. Skin :  Mild erythematous eruption to hairline. Follow-up as scheduled"
};

const command = new DetectEntitiesV2Command(input);
const response = await client.send(command);
console.log(response);

package.json file: where I set the dependencies for the project

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "nodemon index.js"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@aws-sdk/client-comprehendmedical": "^3.349.0",
    "@aws-sdk/credential-providers": "^3.350.0",
    "@aws-sdk/client-s3": "^3.32.0",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "mysql2": "^3.3.3",
    "nodemon": "^2.0.22"
  },
  "type": "module"
}

Error Message:

const response = new exceptionCtor({
                     ^

InvalidSignatureException: The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details.
    at throwDefaultError (C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdksmithy-clientdist-cjsdefault-error-handler.js:8:22)     
    at C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdksmithy-clientdist-cjsdefault-error-handler.js:18:39
    at de_DetectEntitiesV2CommandError (C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdkclient-comprehendmedicaldist-cjsprotocolsAws_json1_1.js:491:20)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdkmiddleware-serdedist-cjsdeserializerMiddleware.js:7:24
    at async C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdkmiddleware-signingdist-cjsawsAuthMiddleware.js:14:20
    at async C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdkmiddleware-retrydist-cjsretryMiddleware.js:27:46
    at async C:UsersshubhOneDriveDesktopProjectsMedical App Exampleservernode_modules@aws-sdkmiddleware-loggerdist-cjsloggerMiddleware.js:7:26
    at async file:///C:/Users/shubh/OneDrive/Desktop/Projects/Medical%20App%20Example/server/index.js:20:18 {       
  '$fault': 'client',
    httpStatusCode: 400,
    requestId: '5c6416f9-5c59-4da8-9842-cd4e39bd4886',
    extendedRequestId: undefined,
    cfId: undefined,
    attempts: 1,
    totalRetryDelay: 0
  },
  __type: 'InvalidSignatureException'
}

I already looked through the documentation that AWS provides and YouTube videos and I kinda lost on how Comprehend Medical works and how AWS works in general. Any help would be greatly appreciated.

Here are the resources I already looked through:

  1. https://www.youtube.com/watch?v=gwLtwW1MUTQ&t=1s – unfortunately this video uses v2 of the JavaScript SDK and not v3, so there are some differences in how to implement the client I believe.
  2. https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/loading-node-credentials-shared.html – this just helped me understand how to setup the shared credentials file and how that works
  3. https://docs.aws.amazon.com/pdfs/sdk-for-javascript/v3/developer-guide/js-sdk-dg.pdf – tried to implement and understand how other AWS services are used however I got confused.
  4. https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/ComprehendMedical.html – I also looked through this documentation, and I am not sure how to fix the error I am getting using the information given here.

The Javascript of my website isn’t working

When I click on my increment and save buttons, the increment() and save() functions in the javascript aren’t working which is weird because the css and html work perfectly fine, and when I run the code in Visual Studios, things work perfectly fine.

I tried messing with the script tag in the html file but that isn’t working. Here’s how the website currently looks:https://illustrious-scone-264b88.netlify.app/ Any help would be appreciated.

    <html>
    <head><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.1/normalize.css">
        <link rel="stylesheet" href="index.css">
        <title>Counter</title>
    </head>
    <body>
        <div id="container">
            <h1>Numbers of Cars Passing:</h1>
            <h2 id="count-el">0</h2>
            <button id="increment-btn" onclick="increment()">INCREMENT</button>
            <button id="save-btn" onclick="save()">SAVE</button>
            <p id="save-el">Previous entries: </p>
            <script src="index.js">
                InnerText
            </script>
        </div>
    </body>
</html>
    let saveEl = document.getElementById("save-el")
let countEl = document.getElementById("count-el")
let count = 0

function increment() {
    count += 1
    countEl.textContent = count
}

function save() {
    let countStr = count + " - "
    saveEl.textContent += countStr
    countEl.textContent = 0
    count = 0
}

WordPress JavaScript doesn’t execute unless I paste it in the console

I am developing a WordPress website using Oxygen Builder. I have custom user registration form that I made with Forminator.

There’s a section where the user needs to select at least 3 options and I need to validate that before they click the next button to go to the next section of the form.

I am using this JavaScript code which is being included in the site in the footer, but it does not work. However, if I paste it into the browser’s console, then it works. What could be the issue?

jQuery(document).ready(function(){

var checked = 0;

jQuery('#forminator-custom-form-83--page-1 input[type="checkbox"]').click(function(){
    
    if(jQuery(this).is(':checked')){
        checked++;
    }
    
    if(checked < 3){
        jQuery('button.forminator-button-next').attr("disabled","");
    } else {
        jQuery('button.forminator-button-next').removeAttr("disabled");
    }
    
    
});

});

Else executes even If is satisfied in firebase

I’ve tried to read a lot, but I can’t find any solutions. I want to create a condition to check data in Firebase and update or add data according to the condition. I tried it as follows:

this.storage.get('Conta').then((data) => {
  var date = new Date();
  var mesPassado = date.getMonth() - 1;
  this.angulfirestore.collection('reembolsos', ref => ref.where('mes', '==', mesPassado)).valueChanges().subscribe(
    (datax) => {
      datax.forEach(ee => {
        this.cc.push(ee);
      });
      this.cc.forEach(y => {
        this.somaMesPassado.push(y.jurosPagos);
        var r = this.somaMesPassado.map(Number);
        this.firebaseSrvc.getFluxo().subscribe((dat) => {
          dat.forEach(x => {
            const diaAgora = new Date(this.dia).getDate();
            const mesAgora = new Date(this.dia).getMonth();
            const anoAgora = new Date(this.dia).getFullYear();
            this.angulfirestore.collection('fluxoMonetario', ref => ref.where('dia', '==', diaAgora).where('mes', '==', mesAgora).where('ano', '==', anoAgora)).get()
              .subscribe(datas =>
                datas.forEach(el => {
                  if (x.dia == diaAgora && mesAgora == x.mes && anoAgora == x.ano) {
                    this.angulfirestore.collection('fluxoMonetario').doc(el.id).update({
                      creditoTrans: this.credito,
                      datadivida: this.dia,
                      juros: this.juros,
                    })
                  } else {
                    const dadosConta = {
                      creditoTrans: this.credito,
                      datadivida: this.dia,
                    };
                    this.firebaseSrvc.f(dadosConta);
                  }
                }));
          })
        });
      })
    });
})

The problem is that when the IF is satisfied it also executes the ELSE and this in turn adds several documents to the collection. Any help please…

how to loop back in JS if input is incorrect? [duplicate]

I want the input to be a number but if the input is letters or null, i want to loop back and ask for the user to provide the input again until the correct input is a number. How can i get the code to recognise NaN or null and loop back again.

This is what i have tried, but when i enter a letter or nothing, the output is ‘New balance is £NaN’

function depositCorrect() {
  let deposit = Number(prompt("Please enter the amount you'd like to deposit"));

  if (deposit == NaN || deposit == null) {
    alert("Incorrect input, please try again ensuring to input a number");
    depositCorrect()
  } else {
    balance = balance + deposit;
    alert(`New balance is £${balance}`);
  }
}
depositCorrect();

Thanks in advance!

I provide the input as ‘dfsg’, then the output is ‘New balance is £NaN’ but i want it to say “Incorrect input, please try again ensuring to input a number” and then start the function again -> “Please enter the amount you’d like to deposit”

Move game pawn through map

I’m creating a boarding game with javascript, where the players can move through the map, which has rooms and aisles. The players can go inside the rooms, but only by passing from the doors.

For example. let’s take this image:
enter image description here

I am the yellow pawn, and I want to go to the red square. But, between me and the square there’s a room (the blue squares). If there are doors (green squares), I can use them to pass inside the room and exit from the opposite door, and then reach the red square. If there are no doors, I must pass around the room to reach it.

Now, let’s suppose I have an array like this to represent the following room:

[
  [1, 1, 2, 2, 2, 2, 2, 1, 1],
  [1, 1, 2, 2, 2, 2, 2, 1, 1],
  [1, 1, "d2", 2, 2, 2, "d2", 1, 1],
  [1, 1, 2, 2, 2, 2, 2, 1, 1]
]

In the array above:

  • Number 1: walkable floor
  • Number 2: room
  • “d2” the door to enter/exit room 2

I need an algorithm that gives me back all the steps the pawn needs to do (Y position and X using the map indexes), in order to animate it then and move it.
The method must take the current coords of the player and the ending coords of the square to reach (and obsiously the map).

Can someone help me to understand how to write this algorithm?
Thanks in advance.

Not able to use OAuth 2.0 to access Google Analytics API GA4

I am trying to set up access to Google Analytics data using Google OAuth 2.0 sign-in (instead of a service account). However, I can’t find documentation or examples on how to do it with JS.

The Oauth authentication is working and I get both an access and refresh token. Then I tried to access the Google Analytics API with it using https://www.npmjs.com/package/@google-analytics/data

The error I get from below is ‘Error: Could not load the default credentials’. What I am missing is how analyticsDataClient can use the auth oauth2Client

const { google } = require('googleapis');

const tokens = { access_token: event.queryStringParameters.accessToken, refresh_token: event.queryStringParameters.refreshToken }

oauth2Client.setCredentials(tokens)

const analyticsDataClient = new BetaAnalyticsDataClient()

  const [response] = await analyticsDataClient.runReport({
    property: `properties/${propertyId}`,
    dateRanges: [
      {
        startDate: '7daysAgo',
        endDate: 'today',
      },
    ],
    metrics: [
      {
        name: 'activeUsers',
      },
    ],
  });

PS: I’m a junior dev

jQuery Program Dynamically Loaded Events Aren’t Being Detected

I am running a modified version of a formset script that was found in GITHUB.

;(function($) {
    $.fn.formset = function(opts)
    {
        var options = $.extend({}, $.fn.formset.defaults, opts),
            flatExtraClasses = options.extraClasses.join(' '),
            totalForms = $('#id_' + options.prefix + '-TOTAL_FORMS'),
            initialForms = $('#id_' + options.prefix + '-INITIAL_FORMS'),
            currentFormIndex = parseInt(totalForms.val()),
            maxForms = $('#id_' + options.prefix + '-MAX_NUM_FORMS'),
            minForms = $('#id_' + options.prefix + '-MIN_NUM_FORMS'),
            childElementSelector = 'input,select,textarea,label,div',
            $$ = $(this),

            applyExtraClasses = function(row, ndx) {
                if (options.extraClasses) {
                    row.removeClass(flatExtraClasses);
                    row.addClass(options.extraClasses[ndx % options.extraClasses.length]);
                }
            },

            updateElementIndex = function(elem, prefix, ndx) {
                var idRegex = new RegExp(prefix + '-(\d+|__prefix__)-'),
                    replacement = prefix + '-' + ndx + '-';
                if (elem.attr("for")) elem.attr("for", elem.attr("for").replace(idRegex, replacement));
                if (elem.attr('id')) elem.attr('id', elem.attr('id').replace(idRegex, replacement));
                if (elem.attr('name')) elem.attr('name', elem.attr('name').replace(idRegex, replacement));
            },

            hasChildElements = function(row) {
                return row.find(childElementSelector).length > 0;
            },

            showAddButton = function() {
                return maxForms.length == 0 ||
                    (maxForms.val() == '' || (maxForms.val() - totalForms.val() > 0));
            },

            showDeleteLinks = function() {
                return minForms.length == 0 ||
                    (minForms.val() == '' || (totalForms.val() - minForms.val() > 0));
            },

            insertDeleteLink = function(row) {
                var delCssSelector = $.trim(options.deleteCssClass).replace(/s+/g, '.'),
                    addCssSelector = $.trim(options.addCssClass).replace(/s+/g, '.');

                var delButtonHTML = document.createElement("a");
                    delButtonHTML.classList.add(options.deleteCssClass);
                    delButtonHTML.textContent = options.deleteText;
                    delButtonHTML.id = "deleteID";

                if (options.deleteContainerClass) {
                    row.find('[class*="' + options.deleteContainerClass + '"]').append(delButtonHTML);
                } else if (row.is('TR')) {
                    row.children(':last').append(delButtonHTML);
                } else if (row.is('UL') || row.is('OL')) {
                    row.append('<li>' + delButtonHTML + '</li>');
                } else {
                    row.append(delButtonHTML);
                }

                if (!showDeleteLinks()){
                    row.find('a.' + delCssSelector).hide();
                }

                row.find('a.' + delCssSelector).click(function() {
                var result = confirm("Are you sure you want to Delete3?");
                if (result) {
                var row = $(this).parents('.dynamic-form');
                var itemIdInput = row.find('input:hidden[id $= "-id"]');
                var itemId = itemIdInput.val();
                var deleteInput = `<input type="hidden" name="line_items_to_delete" value="${itemId}">`;  // Update here....
                if( $(deleteInput).val() ) {
                  $('#line_items_to_delete').append(deleteInput);   // Update here....
                }

                var row = $(this).parents('.' + options.formCssClass),
                    del = row.find('input:hidden[id $= "-DELETE"]'),
                    buttonRow = row.siblings("a." + addCssSelector + ', .' + options.formCssClass + '-add'),
                    forms;
                if (del.length) {
                    del.val('on');
                    row.hide();
                    forms = $('.' + options.formCssClass);
                    totalForms.val(forms.length);
                } else {
                    row.remove();
                    forms = $('.' + options.formCssClass).not('.formset-custom-template');
                    totalForms.val(forms.length);
                }
                for (var i=0, formCount=forms.length; i<formCount; i++) {
                    applyExtraClasses(forms.eq(i), i);
                    if (!del.length) {
                        forms.eq(i).find(childElementSelector).each(function() {
                            updateElementIndex($(this), options.prefix, i);
                        });
                    }
                }

                if (!showDeleteLinks()){
                    $('a.' + delCssSelector).each(function(){$(this).hide();});
                }
                if (buttonRow.is(':hidden') && showAddButton()) buttonRow.show();
                if (options.removed) options.removed(row);
                return false;
                }
              });
            };

        $$.each(function(i) {
            var row = $(this),
                del = row.find('input:checkbox[id $= "-DELETE"]');
            if (del.length) {
                if (del.is(':checked')) {
                    del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" value="on" />');
                    row.hide();
                } else {
                    del.before('<input type="hidden" name="' + del.attr('name') +'" id="' + del.attr('id') +'" />');
                }
                $('label[for="' + del.attr('id') + '"]').hide();
                del.remove();
            }
            if (hasChildElements(row)) {
                row.addClass(options.formCssClass);
                if (row.is(':visible')) {
                    insertDeleteLink(row);
                    applyExtraClasses(row, i);
                }
            }
        });

        if ($$.length) {
            var hideAddButton = !showAddButton(),
                addButton, template;
            if (options.formTemplate) {
                template = (options.formTemplate instanceof $) ? options.formTemplate : $(options.formTemplate);
                template.removeAttr('id').addClass(options.formCssClass + ' formset-custom-template');
                template.find(childElementSelector).each(function() {
                    updateElementIndex($(this), options.prefix, '__prefix__');
                });
                insertDeleteLink(template);
            } else {
                if (options.hideLastAddForm) $('.' + options.formCssClass + ':last').hide();
                template = $('.' + options.formCssClass + ':last').clone(true).removeAttr('id');
                template.find('input:hidden[id $= "-DELETE"]').remove();
                template.find(childElementSelector).not(options.keepFieldValues).each(function() {
                    var elem = $(this);
                    if (elem.is('input:checkbox') || elem.is('input:radio')) {
                        elem.attr('checked', false);
                    }
                    if (elem.is('select')) {
                        elem.append('<option style="display:none" selected value="">Optional</option>');
                    }
                    else {
                        elem.val('');
                    }
                });
            }
            options.formTemplate = template;

            var addButtonHTML = '<a class="' + options.addCssClass + '" href="javascript:void(0)">' + options.addText + '</a>';
            if (options.addContainerClass) {
              var addContainer = $('[class*="' + options.addContainerClass + '"');
              addContainer.append(addButtonHTML);
              addButton = addContainer.find('[class="' + options.addCssClass + '"]');
            } else if ($$.is('TR')) {
                var numCols = $$.eq(0).children().length,
                    buttonRow = $('<tr><td colspan="' + numCols + '">' + addButtonHTML + '</tr>').addClass(options.formCssClass + '-add');
                $$.parent().append(buttonRow);
                addButton = buttonRow.find('a');
            } else {
                $$.filter(':last').after(addButtonHTML);
                addButton = $$.filter(':last').next();
            }

            if (hideAddButton) addButton.hide();

            addButton.click(function() {
              var formCount = currentFormIndex,
                    row = options.formTemplate.clone(true).removeClass('formset-custom-template'),
                    buttonRow = $($(this).parents('tr.' + options.formCssClass + '-add').get(0) || this),
                    delCssSelector = $.trim(options.deleteCssClass).replace(/s+/g, '.');

                    let highestId = 0;
                    const idRegex = /d+/;
                    $("input[name$='-research_line_item_description']").each(function(){  // Update here....
                      const currentName = $(this).attr('name');
                      const idVal = currentName.match(idRegex);
                      const idInt = parseInt(idVal);
                      highestId = Math.max(highestId,parseInt(idInt));
                    });
                    formCount = highestId+1;

                applyExtraClasses(row, formCount);
                row.insertBefore(buttonRow).show();
                row.find(childElementSelector).each(function() {
                    updateElementIndex($(this), options.prefix, formCount);
                });
                totalForms.val(formCount + 1);
                if (showDeleteLinks()){
                  $('a.' + delCssSelector).each(function(){$(this).show();});
                  RemoveFirstButton();
                  UpdatePlaceHolder();
                  UpdateTotals();
                 }
                if (!showAddButton()) buttonRow.hide();
                if (options.added) options.added(row);
                currentFormIndex++;
                return false;
            });
        }
        return $$;
    };

    /* Setup plugin defaults */
    $.fn.formset.defaults = {
        prefix: 'form',                         // The form prefix for your django formset
        formTemplate: null,                     // The jQuery selection cloned to generate new form instances
        addText: 'Add',                         // Text for the add link
        deleteText: 'Del',                      // Text for the delete link
        addContainerClass: null,                // Container CSS class for the add link
        deleteContainerClass: null,             // Container CSS class for the delete link
        addCssClass: 'add-row-budget',          // CSS class applied to the add link
        deleteCssClass: 'delete-row-budget',    // CSS class applied to the delete link
        formCssClass: 'dynamic-form',           // CSS class applied to each form in a formset
        extraClasses: [],                       // Additional CSS classes, which will be applied to each form in turn
        keepFieldValues: '',                    // jQuery selector for fields whose values should be kept when the form is cloned
        added: null,                            // Function called each time a new form is added
        removed: null,                          // Function called each time a form is deleted
        hideLastAddForm: false,                 // When set to true, hide last empty add form (becomes visible when clicking on add button)
    };
})(jQuery);

It works great! With one exception….I am having trouble detecting when the buttons are clicked…If I do the following…

  $(".add-row-budget").on('click', function() {
     $(window).on('beforeunload',function(){
       return '';
     });
       console.log("test1");
  });

It doesn’t work. I kind of understand why…the elements are being loaded in dynamically…and from what I gather….They’re not being bound…or I’m missing an Add-Event-Listener? I’ve hacked this a bit…by adding…

RemoveFirstButton();
UpdatePlaceHolder();
UpdateTotals();

And this gets the code to “wakeup” and after the first pass I can get the…

  $(".add-row-budget").on('click', function() {
     $(window).on('beforeunload',function(){
       return '';
     });
       console.log("test1");
  });

To work. How can I get the code above to work in concert with the original code in a less hacky way?

Thanks in advance for any thoughts.

How to keep collapsible sections closed on page load?

I’m trying to make a simple accordion style menu, when loading the page all the panels/content default to be open. How can I keep them closed until clicked on? (Hosting through Cargo Collective so must be vanilla)

Here’s the code:

<button type="button" class="collapsible active">
Collecting Memory<hr></button>

<div class="collapsiblecontent" style="display: block;">
  <div class="image-gallery" gid="1">
{image 1 no-zoom="true"}
{image 2 no-zoom="true"}
</div>

<p>Lorem ipsum...</p>
</div>

<script>
var coll = document.getElementsByClassName("collapsible");
var i;

for (i = 0; i < coll.length; i++) {
  coll[i].addEventListener("click", function() {
    this.classList.toggle("active");
    var content = this.nextElementSibling;
    if (content.style.display === "block") {
      content.style.display = "none";
    } else {
      content.style.display = "block";
    }
  });
}
</script>
.collapsible {
  background-color: rgba(255, 255, 255, 0);
  color: rgba(0, 0, 0, 0.3);
  cursor: pointer;
  padding: 0px 0px 0px 1px;
  width: 100%;
  border: none;
  text-align: left;
  outline: none;
  font-size: inherit;
}

.collapsiblecontent {
  color: rgba(0, 0, 0, 0.3);
  padding: 0px 0px 0px 1px;
  display: none;
  overflow: hidden;
  background-color: rgba(255, 255, 255, 0);
  font-size: inherit;
}

Export JointJS diagram

I obtained the SVG JointJS diagram with the following code:

const svgMarkup = paper.svg.outerHTML;

And trying to export it to PNG using canvg with the following code:

const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = svgWidth;
canvas.height = svgHeight;
canvg(canvas, svgMarkup, {
    ignoreDimensions: true,
    useCORS: true, // Set this to true if the SVG source is from a different domain
    renderCallback: function() {
    // Conversion is complete, you can now get the PNG image data
    const pngData = canvas.toDataURL('image/png');
    
    // Create a temporary anchor element
    const anchorElement = document.createElement('a');
    anchorElement.href = pngData;
    anchorElement.download = 'image.png'; // Specify the desired filename
    
    // Simulate a click event on the anchor element
    const clickEvent = new MouseEvent('click');
    anchorElement.dispatchEvent(clickEvent);
    }
});

And I get nothing. I tried to convert SVG I’ve already obtained at first to PNG using tools and it’s blank. How can i download the diagram drawn using the Jointjs library?

Send order email when submit button clicked

i am building a simple delivery app, the person writes their order in a text box such as a pickup or a in resturant order, when they press the submit button i want to be notified that i have an order by text or email then i want them to get an email saying the order is recieved how do i do that im new to android studio thanks.

i already have the page and button placed i just want it so when they press the submit button it sends the email/text.

I have not tried anything yet because i am unsure what to do next.

JS matrix of squares with texts fully centered

Using HTML, CSS and JS
How to ask the user to input 3 separate strings (first, middle and last names)
then output a CSS 2×3 “grid” – no “table”:

the first row should have those 3 strings,
the second row should have
the same 3 strings – but spelt backwards.

Every cell should be a perfect square.
Every string should be fully centered inside its cell.
?
I have been trying for months, no avail.

I got strings not vertically centered. I got non-squares. I got the second row empty of a copy
of the first row.

TypeScript function parameter destructuring with default values

I am destructuring function parameters in TypeScript, and I have an interface that declares the basic type structure for most function parameter objects. However, there are some cases where I want to assign a default value to one of these arguments. TypeScript is not inferring the type of the assigned argument, and is instead using the type of the parameter as it is declared in the interface. How can I get TypeScript to infer the type of the default parameter?

interface Args {
  foo: {};
}

function test({ foo = { bar: 1 } }: Args) {
  // typeof foo is {}, not {bar: number}
}