Snapblocks (Part 1)

You all know about scratchblocks, it is a way to generate images of scratch scripts from text. Currently scratchblocks is being used on the forum and the wiki. It does have some Snap! support, but not a whole lot. There are grey rings, variadic input arrows, and the list icon (although the forum hasn't gotten that update yet).

[scratchblocks] (map ((join @addInput :: operators) ((#1) :: grey) @delInput @addInput :: grey ring) over @list :: list) [/scratchblocks]

However there are still many issues with the current scratchblocks. Empty rings do not work, the snap "define" block cannot be made because scratchblocks has special formatting for the scratch define block (and there's no way to override it), most Snap! icons are missing, multiline blocks are impossible to make, and more.

Seeing as how there is still very little Snap! support in scratchblocks, I decided to create a fork of scratchblocks, calling it snapblocks. Snapblocks aims to make scratchblocks for Snap! as well as fixing many issues. Here are the highlights. If you want to mess around with snapblocks, here's the link to the homepage, although every snapblocks image will be a link to the block on the snapblocks homepage.

First and foremost, I added a new Snap! style, which actually looks like Snap! (Scratch 2.0 looks similar, but it's still very clear that it's not Snap! especially when it comes to hat blocks).

The scratch define block now has more strict formatting

define {block}

snapblocks (42)

This will make sure we can create blocks with the define label, with no Scratch define block formatting (yes, I know it will break existing scratchblocks text, but since snapblocks is for Snap! and is seperate from scratchblocks, I feel like this is fine. Plus, I'm not sure how many people ever used it on the forum).

define ((block)) [] (() @addInput :: ring)

snapblocks (43)

As you can see, upvars now have automatic formatting. The ring around the variable is automatically set to the category of the parent.

motion ((var 1)) :: motion
looks ((var 2)) ::: looks
pen ((var 3)) :: pen stack
control ((var 4)) :: control
variables ((var 5)) :: variables
list ((var 6)) :: list
other ((var 7)) :: other

snapblocks (44)

Now, on the topic of custom blocks and upvars, I have also added Snap! custom block support.

{custom block ((input)) ((number #)) ((bool ?)) :: motion} :: define

custom block [text] (5) <>

snapblocks (45)

But Snap! usually shows a + between each block fragment in the block prototype, unless the setting (plain prototype labels) is turned on. Snapblocks does allow the + to be ignored in the block prototype.

{+ say + costume + ((costume = costume 1)) + :: looks} :: define

say costume [costume 1 V]

snapblocks (46)

Unfortunately I was unable to make it automatically set the shape (without making it look bad anyway).

( + sound + ((sound # #)) + :: sound) :: define
report (item (sound #) of (my [sounds V]))

sound (5)
(sound (5))

snapblocks (47)

Now, if you've seen, there are many blocks in Snap! libraries that have multiple lines. Scratchblocks does not have the ability to make multiline blocks, but snapblocks does.

(zip (() @addInput) inputs:
 [] leaf-rank (0)
 [] leaf-rank (0) :: control)

snapblocks (48)

If you put a newline inside a reporter or predicate (oh, by the way, you can now specify the predicate shape, not just boolean), it will have a newline. This is not the only way, as this does not work in command (stack) blocks. Due to this, you can also use \n, as well as putting a backslash at the end of a line right before a newline.

first line \n second line \
third line \
fourth line

snapblocks (49)

On the topic of multiline blocks, I think it's a good idea to mention multiline inputs.

(multiline [first line
second line
third line] :: operators)

snapblocks (50)

Snap! also has the ability to put c-shapes in reporters / predicates, which scratchblocks does not have support for, so I added it in snapblocks.

<else if <> then {

} :: control>

snapblocks (51)

Unfortunately I have not added a way to add inputs on mew lines in the middle of a block, specifically there's no way to accurately recreate variadic ring inputs. The only reason I have not added this, is because there's not a really good way of determining if the input should go on the next line at the beginning of the block, or right under the previous input on the same line. If you don't know what I'm talking about, look at the pipe block.

I have also added a bunch of Snap! icons (started with the most commonly used icons).

@greenFlag @stopSign @turnRight @turnLeft @list @pointRight @turtle @turtleOutline @pause @cloud @cloudOutline @flash @arrowRight @arrowRightOutline @arrowRightThin @delInput @verticalEllipsis @addInput◂⋮▸ :: grey

snapblocks (53)

note: there are arrow icons in every direction. All the new Snap! icons have the same name as in Snap!.

Given that you can change the color and size of icons in Snap! I have also added a way to do that. It's pretty much the same way you do it in Snap!.

@greenFlag-scale-r-g-b
@greenFlag-2-0-0-255

snapblocks (54)

Unfortunately I don't think there's a good way for doing the same for text, but seeing as how it's not very common you see a different color or size text in custom blocks, I don't think it's too big of an issue.

I have also revamped the block detection system to work with variadic inputs, and blocks that can change their text.

if <> {

} @addInput

if <> {

} else if <> {

} else if <> {

} else if <> {

} @delInput @addInput

all @verticalEllipsis @addInput
all (var)
<> and <> and <> and <> @delInput @verticalEllipsis @addInput

// Yes, all of these are detected as the same block

broadcast [message V] @addInput and wait
broadcast [message V] to [all V] @delInput @addInput and wait
broadcast [message V] to [sprite V] with data [data] @delInput and wait

snapblocks (56)

This new system may be slower than the old method, but I think it's worth it because it allows for easier block creation, and it doesn't actually have to be real time, since most of the time, the image will not change.

Block detection explenation

I just kind of want to explain how this new block detection system works, as I'm kind of proud of coming up with the idea.

Let's start by explaining how scratchblocks detects blocks.

We start out with the block information

{
  id: "id",
  spec: "broadcast %1",
  inputs: ["%m.messages"],
  shape: "stack",
  category: "control",
}

Note: this is very much simplified, and doesn't contain all the information scratchblocks uses.

When you type in a broadcast block

broadcast [message v]

It creates a block hash from this. A block hash replaces inputs with _ (weirdly similar to how programmatically naming blocks works in snap...)

broadcast _

Then it converts the block spec from the block information into this format

broadcast %1 --> broadcast _

Then it checks if the two hashes are the same. In this case, they are, so it knows to set the category and shape.

This works for scratch, but when you want to add snap primitives into the mix, things get a little messy. For some blocks, I can create aliases, other specs that a block can be compared to.

{
  spec: "broadcast %1",
  aliases: [
    "broadcast %1 @addInput",
    "broadcast %1 to %2 @delInput @addInput",
    "broadcast %1 to %2 with data %3 @delInput",
  ],
  ...
}

Which will then match these using the same rules, just also factoring in the aliases

broadcast [message v]
broadcast [message v] @addInput
broadcast [message v] to [all v] @delInput @addInput
broadcast [message v] to [all v] with data [data] @delInput

This works for some blocks, but when it comes to variadic inputs that have an infinite amount of inputs, you can see why this wouldn't work. That is why I came up with a more dynamic solution.

This method has a base spec, then some spec defs, which replace specific parts in a spec.

{
  spec: "broadcast %1 {receiver}",
  specDefs: {
    "receiver": [
      "",
      "@addInput",
      "to %2 {data}",
    ],
    "data": [
      "@delInput @addInput",
      "with data %3 @delInput",
    ],
  }
}

This works in a very different way from the old method. Instead of checking if the block specs are equal, it instead creates the block spec based off of what you typed, by stepping through the block spec one word at a time.

It starts off with the first word

broadcast

This is compared with the first word of the spec

broadcast (_to _ @delInput)

broadcast (_ {reciever})

As these both have the same thing in the current position, this block is kept, if they're not equal, the block will be dropped from the list of blocks to check.

It repeats this process until it comes to a word that is surrounded by curly braces {}, this is a spec def.

It then fills this position with it's spec defs.

broadcast _ {reciever}
broadcast _
broadcast _ @addInput
broadcast _ to _ {data}

There is only one that can compare to the current block, so the rest are dropped

broadcast _ to _ (@delInput @addInput)

broadcast _ to _ ({data})

Next it checks for the {data} def

broadcast _ to _ {data}
broadcast _ to _ @delInput @addInput
broadcast _ to _ with data _ @delInput

Again, only one of these is the same as the current block spec

broadcast _ to _ @delInput @addInput

In fact, since they're equal, I don't need to continue, I can just keep this block spec, because I know it's correct.

This example didn't have an infinite variadic input, so here's an example.

{
  spec: "{if}",
  specDefs: {
    "if": [
      "if %1 %2 {else}",
    ],
    "else": [
      "@addInput",
      "@delInput @addInput",
      "else {if}",
    ],
  }
}

Let's compare it to this

if <> {

} else if <> {

} else if <> {

} @delInput @addInput

Which will turn into this hash

if _ _ else if _ _ else if _ _ @delInput @addInput

The if spec starts with a def, so let's fill that in

{if}
if _ _ {else}

We know it is equal to the first 3 inputs, so let's keep it. Next we need to fill in the {else} def.

if _ _ {else}
if _ _ @addInput
if _ _ @delInput @addInput
if _ _ else {if}

The last one is the only one that matches out current hash

if _ _ else (if _ _ else if _ _ @delInput @addInput)
if _ _ else {if}

After we fill in that def, we get this

if _ _ else if _ _ {else}

And then if we keep repeating this process, a couple more times, we end up with

if _ _ else if _ _ else if _ _ ({else})
if _ _ else if _ _ else if _ _ (@delInput @addInput)

Let's fill in the last {else} and see what we get

if _ _ else if _ _ else if _ _  {else}
if _ _ else if _ _ else if _ _  @addInput
if _ _ else if _ _ else if _ _  @delInput @addInput
if _ _ else if _ _ else if _ _  else {if}

only the third one matches, so let's use that

if _ _ else if _ _ else if _ _  @delInput @addInput

And since that one is equal, we know our block is indeed an if block. Again, if at any point, there is something that doesn't match, it will not continue with that block, and will also not be matched. For example, this

if <> {

} else {

}

Will not be matched, because these are not equal

if _ _ else _

if _ _ else {if}
if _ _ else if _ _ {else}

If you want to understand how it's able to match all as well as <> and <> and <> @delInput @verticalEllipsis @addInput, here's the "and" block

{
  spec: "{all}",
  specDefs: {
    "all": [
      "all @addInput",
      "all @verticalEllipsis @addInput",
      "all %1",
      "{and}"
    ],
    "and": [
      "%1 and {and}",
      "%2 @delInput @addInput",
      "%2 @delInput @verticalEllipsis @addInput",
    ],
  },
}

There are still a bit more that snapblocks does, but this is the basics.

As you can tell, I am very proud of coming up with this solution. In fact, I actually came up with it one night, then spent the whole next day implementing it, because I knew it would work.

I do really want to make plugins for mediawiki and discourse, specifically fore easier support. Currently, all you really need to do is swap out the scratchblocks files (including the translations-all.js (if you use it)) with the snapblocks files (you may also want to change the reference to use the snapblocks name, everything else can stay using scratchblocks).

Since snapblocks is open source, you can contribute to it if you want.

I also noticed this when creating my post,

`snapblocks` for Discourse

I guess I'm that person who finally created a fork of scratchblocks, and made it for snap.

Here are some things that I want to add in the future

Zebra coloring
Block wrapping
Multiline comments
Maybe a snapblocks to snap xml?
(fix the translations system)

It would also be great if someone could make a logo for it, as I'm not very good at art.

Now you can make even more cursed and broken blocks!

i've appeared from the depths to say this is cool

Impressive project, I hope this gains more recognition, and let's have this installed in the Snap! Wiki and the forums!

If you didn't miss, I have a few suggestions to add in the future:

  • Make the + in definition hats black (it appears black to me in both flat and normal design)
  • Add support for longer scripts in rings
  • Add the camera symbol (for snap in Pixels library) and red circle symbol (for the record block in Audio Comp
  • Add support for library categories (e.g. SciSnap!, MQTT)

Also, I hope there is a way to shorten @verticalEllipsis because I think typing this takes too long.

I tried accessing the link but it led me to a random relevant search/cloud security monitoring/whatever one calls it website called githib. I'm probably sure this is a misspelling, but I made the URL into GitHub and nothing worked.

Yeah, I recommend "@vertEll"

Oops sorry. At least if you click on any of the snapblocks images, it's correct (I also just fixed the link).

I actually thought of that, but I'm not sure of an elegant way of doing that. Plus, I still need to work out multiple plus signs in a row.

{+ + block + + :: motion} :: define

block

1000295208

What do you mean? Can you give me an example?

Heh, I completely missed those. I do also want to add all the icons though, so at some point every icon should be there.

I actually don't want to include library blocks, because of multiple reasons

  1. Libraries are custom blocks
  2. There's so many (it took over 3 days to add all the snap primitives, I still need to fix and add the translations, which is going to take a lot longer).
  3. They change a lot more often than the snap primitives.

Now yes, I know you just said the category, so it's a bit simpler. I don't really want to add it, because they're custom categories, and you can already set a custom color of the block if you have the hex code.

@cloudOutline MQTT connect to [broker.emqx.io] \
options [] @delInput @verticalEllipsis @addInput :: #8E0164

1000295211

This does also highlight another issue I need to somehow solve, text input menus that are editable. I'm not sure of a good way to represent that.

I do agree with you, I just made the name what it is in snap, and I just got very used to typing it in. You can also type in the vertical ellipsis character , but I know it's not on the standard keyboard (same with the input arrows, and pretty much every unicode symbol that is an icon).

Great job! You put in a lot of effort for this. Thanks a lot. :slight_smile:

And even if I eventually abandon it (which I hope I don't), it's still better than nothing.

As you can see, this ringed script is stacked four commands, which is longer than just one command. Whereas scratchblocks doesn't support ringed scripts that are two blocks or more.

To get the hexadecimal, first, the color/colour of the category must be extracted with the [scratchblocks][rgba v] at [mouse-pointer v]::sensing reporter[/scratchblocks], and then, it returns a RGBA list, and not the hexadecimal. Then, I have to convert it using an external RGBA-to-hex website, and I have to collect the different categories. This is really slow for users who didn't deal with library blocks before...

Oh, I see what you mean. I guess I never tried that.

launch ({wait until <(b) > [1] @delInput @addInput>
change [a v] by (1)
say (mouse position) for (2) secs
stamp} @addInput) @addInput

snapblocks

edit: is this better?

Or you can load the colors and crayons library to get the hex

I could also add a way to set a custom color using rgb values as rgb(0,0,0) so it could be easier to set custom categories.

I added the flat design style
snap-flat

Also, what I would like in the snapblocks website is the to only show the style chosen in the dropdown (and not all five shown at once). Showing the other styles when I'm working on one just gives me the (kind of) imperfection feelings (I'm not sure how to explain it, but at least this is what users would expect).

This is a great job! We should definitely update the forum to use it. (OTOH we should also encourage people to use smart script pics instead, now that we have that capability.)

There should never be two consecutive plus signs, I think. But in any case, the user of Snap! blocks shouldn't have to type the plus signs (and should be able to type actual plus signs as title text if desired); you should generate black plus signs between every pair of title text words or input slots, and at the beginning and end of the prototype.

What were you planning to do about non-English translations? Have users type in the target language, or translate yourself? (This is another reason smart script pics are a great idea for the forum; they translate themselves.) Both possibilities (the user translates or your code translates) seem too horrible to contemplate.

Is this supposed to be a ring? To my eye it doesn't look like a ring at all! It's not a simple closed curve. It looks like a WARP block that's missing the word "warp" for some reason. I vote no.

This looks like a ring! :~D

I absolutely agree with this.

The only reason there would be two plus signs, were if the block label itself includes s plus sign
1000295287

I'm a little hesitant to do this, as plain prototype labels exist.

Scratchblocks actually has a system for non-english translations. On the homepage, you can select a language, which you can then type in the blocks in that language. There is also a javascript function that is supposed to translate a block from one language to another. That is really what's currently broken.

Don't worry, I fixed that in the next release.

launch ({wait until <(b) > [1] @delInput @addInput>
change [a v] by (1)
say (mouse position) for (2) secs
stamp} @addInput) @addInput
launch ({
wait until <(b) > [1] @delInput @addInput>
change [a v] by (1)
say (mouse position) for (2) secs
stamp} @addInput) @addInput

1000295288

(it checks if there's a newline after the open curly brace)

That is only the case on the dev site, not the main homepage.

The main homepage: https://snap-blocks.github.io/
The dev site: https://snap-blocks.github.io/snapblocks/

That sorta-C-shaped thing still isn't a ring. It can't appear in actual Snap! code. So, what's it for in Snap! blocks? Think "simple closed curve." :~)

What I'm suggesting is that, just like Snap! itself, you should have two modes, in one of which you interleave black plus signs with the contents of the prototype. But the user should never type them explicitly, because the next time the user changes the prototype somehow, the plusses won't be where they belong. Maybe the user puts, say, @+ at the beginning of a block to indicate that you should zip in plus signs.

You know what, I just realized, that may have been a mistake on my part. I made sure reporters and predicates are able to be c-shapes, but I didn't think about rings. Technically they shouldn't have c-shapes, but I think it should've defaulted to the reporter shape instead of the command block shape. Oh, and you can actually put command blocks inside other blocks in scratchblocks, and I don't see anyone complaining about how it's impossible in scratch. I feel like since snapblocks and scratchblocks are text, you do need to take into account that people will type in weird things that are impossible in snap / scratch, so it should still format whatever cursed block they decide to make, even if it's impossible.

I just edited snapblocks to fix this (I just made it use the reporter shape), though the changes are not live yet, as I'm currently working on tweaking the snap style, and I don't want it to leave my computer, until I get it to look good.

Fun fact, snapblocks also detects a c-shape just like any other input, in fact, since there is no input type validation, you can do stuff like this

move {

} steps

snapblocks (4)

This is actually also the case in scratchblocks
[scratchblocks]
move {
} steps
[/scratchblocks]

Honestly, that is a good idea, and it would make it significantly easier to implement the different formatting, but it does pose another issue. How many people are actually going to know to do

{@+ block prototype} :: define

instead of

{+ block + prototype +} :: define

After all, the latter is what they see in snap.

It can as a custom block!

He's talking about a command block in an input without a ring.

What they see is black plus signs, to make it clear that they're not actually part of the title text of the block. Nobody puts those plus signs in the text box in the make-a-block dialog, or at least not more than once. :~)

Actually, maybe the right thing is, instead of @+ at the beginning of the prototype, have a quasi-category :: define@+. Or maybe even make that the default and have define@plain to match the option in the gear menu. That would be kinda discoverable.