How do you create app inventor block images from outside app inventor?

Hello!
Good morning,
I have a question, my question is how to create app inventor blocks from another program and download them in an image that can be imported to app inventor as for example what is done here Getaix marketplace

Thank you very much

You might ask @Jerin_Jacob how it is done ?

2 Likes

YES!
Let's see if he can explain it...it would be very interesting to know :slight_smile:

with this three function, you can load xml as blocks in working space.

question is how to generate the xml file?

1 Like

Yes, and how to generate the image

How about generating blocks images in a browser extension?

That can be dragged into a project from GitHub?

Is that what you want?

1 Like

I could use it, if you can pass it on to me, I would appreciate it.

Basically I want to see if there is a way to program a block from outside, and put it in appinventor from the photo as they do in CURL.

See [Chrome Extension] AI2Helper - download all blocks seperately and remove all orphan blocks with one click
from
FAQ Section: Blocks Editor

1 Like

thanks

This isn't quite what you want, but could probably be adapted to do what you are looking to do by adjusting some of the logic. This is a script I wrote back in 2017 that takes an AIX file and produces the documentation and SVG files for the blocks. It was used as part of the process to build the reference documentation for the old IOT website.

If you want to create draggable block PNGs, then you'd have to adjust the code that produces the SVG to instead call Blockly.exportBlockAsPng instead.

#!/usr/bin/env phantomjs
// -*- mode: javascript; js-indent-level: 2; -*-
// Copyright © 2017 Massachusetts Institute of Technology, All rights reserved.

'use strict';

var system = require('system'),
  fs = require('fs'),
  webpage = require('webpage'),
  JSZip = require('../lib/jszip/jszip.min'),
  platform = '',
  components = null;

/**
 * Flag that controls whether parameters are listed under methods and events. Since parameter
 * annotations were only added in 1.8, we do not have support for documenting parameters in the
 * extensions. This will result in to-do messages in the output file.
 *
 * @type {boolean} true if parameter lists should be autogenerated, otherwise false (default: false)
 */
var includeParameters = false;

/**
 * Flag that controls whether or not to generate a single amalgamated file for extension bundles or
 * whether to create a file per-extension in the bundle. For extensions that have a single
 * component, this flag should have no observable effect.
 *
 * @type {boolean} true if a file should be created per extension in a bundle (default), otherwise
 * false to have a single output file with all extensions together.
 */
var filePerExtension = true;

/**
 * Function to create a named anchor for the Markdown content. This is used to make linkable
 * references to properties, methods, and events in the documentation.
 *
 * @type {?function(extensionName: string, targetName: string): string} a function that returns
 * HTML for a named anchor for the given extensionName and targetName.
 */
var makeAnchor = null;

/**
 * Function to create a link to a named anchor for the Markdown content. This is used to make links
 * to other extensions.
 *
 * @type {?function(extensionName: string, targetName: string): string} a function that returns
 * HTML for a link to the given extensionName and targetName.
 */
var makeLink = null;
var titlePrefix = '# ';
var sectionPrefix = '## ';

var linkRegex = /(<a href="#)([^"]*?)(">.*?<\/a>)|(<a href='#)([^']*?)('>.*?<\/a>)/g;

function escapeUnderscores(s) {
  return s.replace(/_/g, '\\_');
}

function updateLinks(extensionName, text) {
  if (filePerExtension) {
    return text;
  }
  // Technically we should do this by parsing the description as HTML as a context-free grammar
  // rather than abusing regular expressions. We assume that the descriptions are well-formed
  // enough that <a> tags do not include other <a> tags.
  var match, lastPos = 0, newText = [];
  while ((match = linkRegex.exec(text)) !== null) {
    newText.push(text.substr(lastPos, match.index));
    lastPos = match.index + text[0].length + 1;
    if (match[1] !== undefined) {
      newText.push(match[1], extensionName, '-', match[2], match[3]);
    } else if (match[4] !== undefined) {
      newText.push(match[4], extensionName, '-', match[5], match[6]);
    } else {
      system.stderr.write('Matched successfully but the groups were not initialized???');
      phantom.exit(2);
    }
  }
  newText.push(text.substr(lastPos));
  return newText.join();
}

function generateBlockImage(extension, blockType, descriptor, outputPath, out) {
  var page = webpage.create(), workspace, block, mutators, prefix = '';
  page.injectJs('../build/blocklyeditor/blockly-all.js');
  out.write('\n');
  switch (blockType) {
    case 'component_get':
      blockType = 'component_set_get';
      mutators = {
        'instance_name': extension.name + '1',
        'component_type': extension.name,
        'set_or_get': 'get',
        'is_generic': false,
        'property_name': descriptor.name
      };
      prefix = 'get ';
      break;
    case 'component_set':
      blockType = 'component_set_get';
      mutators = {
        'instance_name': extension.name + '1',
        'component_type': extension.name,
        'set_or_get': 'set',
        'is_generic': false,
        'property_name': descriptor.name
      };
      break;
    case 'component_method':
      mutators = {
        'instance_name': extension.name + '1',
        'component_type': extension.name,
        'is_generic': false,
        'method_name': descriptor.name
      };
      break;
    case 'component_event':
      mutators = {
        'instance_name': extension.name + '1',
        'component_type': extension.name,
        'is_generic': false,
        'event_name': descriptor.name
      };
      break;
  }
  block = page.evaluate(function(blockType, mutators, extension) {
    workspace = Blockly.BlocklyEditor.create(document.body, '0_Screen1', false, false);
    Blockly.mainWorkspace = workspace;
    workspace.getComponentDatabase().populateTypes([extension]);
    Blockly.ai_inject(document.body, workspace);
    var xmlArray = Blockly.Drawer.prototype.blockTypeToXMLArray(blockType, mutators),
      /** @type {Blockly.BlockSvg} */
      block = Blockly.Xml.domToBlock(xmlArray[0], workspace),
      s = new XMLSerializer();
    block.initSvg();
    block.render();
    var svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'),
      bbox = block.getSvgRoot().getBBox(),
      width = bbox.width, height = bbox.height;
    if (block.outputConnection) {
      width += 8;
      block.getSvgRoot().setAttribute('transform', 'translate(8, 0)');
      svg.setAttribute('viewport', '0 0 ' + width + ' ' + height);
    } else {
      svg.setAttribute('viewport', '0 0 ' + width + ' ' + height);
    }
    svg.setAttribute('width', width);
    svg.setAttribute('height', height);
    svg.appendChild(document.getElementsByTagName('style')[0].cloneNode(true));
    svg.appendChild(block.getSvgRoot().cloneNode(true));
    return {
      'svg': s.serializeToString(svg).replace(/&nbsp;/g, ' '),
      'alt': block.getSvgRoot().textContent
        .replace(new RegExp(Blockly.FieldDropdown.ARROW_CHAR, 'g'), '').replace(/\./g, '')
        .replace(/&nbsp;/g, ' ')
    };
  }, blockType, mutators, extension);
  try {
    fs.write(outputPath, block['svg'], 'w');
  } catch(e) {
    system.stderr.log(e);
    phantom.exit(1);
  }
  out.write('![' + prefix + block['alt'] + '](' + outputPath + ')\n\n');
}

function makeAnchorPackaged(extensionName, targetName) {
  return '<a name="' + extensionName + (targetName? '-' + targetName : '') + '"></a>';
}

function makeAnchorSeparate(extensionName, targetName) {
  if (targetName) {
    return '<a name="' + targetName + '"></a>';
  } else {
    return '';
  }
}

function makeLinkPackaged(extensionName, targetName, text) {
  if (!text) {
    text = targetName;
    targetName = undefined;
  }
  return '<a href="#' + extensionName + (targetName? '-' + targetName : '') + '">' + text + '</a>';
}

function makeLinkSeparate(extensionName, targetName, text) {
  if (!text) {
    text = targetName;
    targetName = undefined;
  }
  return '<a href="' + extensionName + '.md' + (targetName? '#' + targetName : '') + '">' + text + '</a>';
}

function writeExtensionDoc(extension) {
  var i, j, prop, method, param, event, name = extension['name'], basePath = 'blocks/' + name + '.';
  var out = filePerExtension ? fs.open(name + '.md', 'w') : system.out;
  system.stderr.write('Generating docs for ' + name + '...\n');
  out.write(titlePrefix + escapeUnderscores(name) + '\n\n');
  out.write(extension['helpString'] + '\n\n');
  if (extension.blockProperties.length > 0) {
    out.write(sectionPrefix + 'Properties\n\n');
    for (i = 0; i < extension.blockProperties.length; i++) {
      prop = extension.blockProperties[i];
      out.write('+ ' + makeAnchor(name, prop.name) + '`' + escapeUnderscores(prop.name) + '` – ' + prop.description + '\n\n');
      if (prop.rw.indexOf('read') >= 0) {
        generateBlockImage(extension, 'component_get', prop, basePath + prop.name + '_getter.svg', out);
      }
      if (prop.rw.indexOf('write') >= 0) {
        generateBlockImage(extension, 'component_set', prop, basePath + prop.name + '_setter.svg', out);
      }
    }
  }
  if (extension.methods.length > 0) {
    out.write(sectionPrefix + 'Methods\n\n');
    for (i = 0; i < extension.methods.length; i++) {
      method = extension.methods[i];
      out.write('+ ' + makeAnchor(name, method.name) + '`' + escapeUnderscores(method.name) + '` ' +
        (method.deprecated === 'true' ? '(deprecated) ' : '') +
        '– ' + method.description + '\n');
      if (includeParameters) {
        for (j = 0; j < method.params.length; j++) {
          param = method.params[j];
          out.write('   + `' + escapeUnderscores(param.name) + '` (_' + param.type +
            '_) – TODO write parameter description\n');
        }
      }
      generateBlockImage(extension, 'component_method', method, basePath + method.name + '.svg', out);
    }
  }
  if (extension.events.length > 0) {
    out.write(sectionPrefix + 'Events\n\n');
    for (i = 0; i < extension.events.length; i++) {
      event = extension.events[i];
      out.write('+ ' + makeAnchor(name, event.name) + '`' + escapeUnderscores(event.name) + '` ' +
        (event.deprecated === 'true' ? '(deprecated) ' : '') +
        '– ' + event.description + '\n');
      if (includeParameters) {
        for (j = 0; j < extension.events[i].params.length; j++) {
          param = event.params[j];
          out.write('   + `' + escapeUnderscores(param.name) + '` (_' + param.type +
            '_) – TODO write parameter description\n');
        }
      }
      generateBlockImage(extension, 'component_event', event, basePath + event.name + '.svg', out);
    }
  }
  out.write('\n');
  if (filePerExtension) {
    out.close();
  }
}

function writeExtensionDocs(extensions) {
  if (!filePerExtension) {
    console.log('# Platform: ' + platform + '\n');
  }
  for (var i = 0; i < extensions.length; i++) {
    writeExtensionDoc(extensions[i]);
  }
  phantom.exit();
}

function readExtensionsAIX(cb) {
  return function(zip) {
    zip.forEach(function (path, file) {
      if (path.lastIndexOf('/components.json') === path.length - '/components.json'.length) {
        file.async('string')
          .then(function (value) {
            components = JSON.parse(value);
            cb(components);
          });
      }
    });
  }
}

function main() {
  platform = system.args[1];
  var extFile = system.args[2];

  if (filePerExtension) {
    makeAnchor = makeAnchorSeparate;
    makeLink = makeLinkSeparate;
  } else {
    makeAnchor = makeAnchorPackaged;
    makeLink = makeLinkPackaged;
    titlePrefix = '#' + titlePrefix;
    sectionPrefix = '#' + sectionPrefix;
  }

  // Process extensions
  if (extFile.lastIndexOf('.aix') === extFile.length - 4) {
    new JSZip().loadAsync(fs.read(extFile, {mode: 'rb'}))
      .then(readExtensionsAIX(writeExtensionDocs))
      .catch(function (err) {
        console.log(err);
        phantom.exit();
        throw err;
      });
  }
}

if (system.args.length !== 3) {
  console.log('Usage: docgen.js <platform-name> <extension-path>');
  console.log('Example: docgen.js Microbit edu.mit.appinventor.ble.aix');
  phantom.exit(1);
} else {
  main();
}
2 Likes

Ok!
Thanks