Merge branch 'main' into Alert/Confirm/Prompt
This commit is contained in:
commit
3dbd97449a
10 changed files with 281 additions and 27 deletions
|
|
@ -109,7 +109,7 @@
|
|||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div class="__alert_prompt" id="Zm5BSEgzQW5HeW9yZkFkNEhpbGJiamtpZmdoOGNDc2lMR29JSzF0K2JnPT0=">
|
||||
<p style="margin-block: 0;">TurboBuilder is currently in development. Features may not work correctly here.</p>
|
||||
<p style="margin-block: 0;">TurboBuilder is currently in development, some project files may break due to code changing all the time.</p>
|
||||
<button
|
||||
style="margin: 0; margin-left: 8px; background: transparent; border: 0; outline: 0; padding: 0; cursor: pointer;"
|
||||
onclick="document.getElementById('Zm5BSEgzQW5HeW9yZkFkNEhpbGJiamtpZmdoOGNDc2lMR29JSzF0K2JnPT0=').remove()"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,26 @@
|
|||
<xml>
|
||||
<category name="Events" colour="#FFBF00">
|
||||
<block type="events_interval" />
|
||||
<block type="events_timeout" />
|
||||
</category>
|
||||
<category name="Control" colour="#FFAB19">
|
||||
<block type="control_ifthen" />
|
||||
<block type="control_ifthenelse" />
|
||||
<block type="control_ifthenreturn" />
|
||||
<sep gap="48"></sep>
|
||||
<block type="control_switch" />
|
||||
<block type="control_case" />
|
||||
<block type="control_default" />
|
||||
<block type="control_break" />
|
||||
</category>
|
||||
<category name="Sound" colour="#CF63CF">
|
||||
<block type="sound_startsound" />
|
||||
</category>
|
||||
<category name="Sensing" colour="#5CB1D6">
|
||||
<!--
|
||||
<block type="sensing_keypress" />
|
||||
<sep gap="48"></sep>
|
||||
-->
|
||||
<block type="sensing_alert" />
|
||||
<block type="sensing_prompt" />
|
||||
<block type="sensing_confirm" />
|
||||
|
|
@ -19,6 +29,7 @@
|
|||
<block type="literals_true" />
|
||||
<block type="literals_false" />
|
||||
<sep gap="48"></sep>
|
||||
<block type="literals_null" />
|
||||
<block type="literals_number" />
|
||||
<block type="literals_string" />
|
||||
<block type="literals_color" />
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ function register() {
|
|||
{
|
||||
"type": "field_input",
|
||||
"name": "ID",
|
||||
"value": "id",
|
||||
"text": "id",
|
||||
"spellcheck": false
|
||||
},
|
||||
{
|
||||
|
|
@ -25,7 +25,7 @@ function register() {
|
|||
{
|
||||
"type": "field_input",
|
||||
"name": "TEXT",
|
||||
"value": "text",
|
||||
"text": "text",
|
||||
"spellcheck": false
|
||||
},
|
||||
{
|
||||
|
|
@ -75,7 +75,7 @@ function register() {
|
|||
text: \`${TEXT}\`,
|
||||
arguments: { ${INPUTS} }
|
||||
})
|
||||
Extension.prototype[\`${ID}\`] = (args) => { ${FUNC} }`;
|
||||
Extension.prototype[\`${ID}\`] = (args, util) => { vm = util; console.log(util); ${FUNC} }`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
|
||||
|
|
@ -89,7 +89,7 @@ function register() {
|
|||
{
|
||||
"type": "field_input",
|
||||
"name": "ID",
|
||||
"value": "ID",
|
||||
"text": "ID",
|
||||
"spellcheck": false
|
||||
},
|
||||
{
|
||||
|
|
@ -135,7 +135,7 @@ function register() {
|
|||
{
|
||||
"type": "field_input",
|
||||
"name": "NAME",
|
||||
"value": "INPUTID",
|
||||
"text": "INPUTID",
|
||||
"spellcheck": false
|
||||
}
|
||||
],
|
||||
|
|
|
|||
|
|
@ -73,6 +73,34 @@ function register() {
|
|||
return `${code}\n`;
|
||||
})
|
||||
|
||||
// if <> then () else ()
|
||||
registerBlock(`${categoryPrefix}ifthenreturn`, {
|
||||
message0: 'if %1 then %2 else %3',
|
||||
args0: [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "X",
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "Y",
|
||||
},
|
||||
],
|
||||
output: null,
|
||||
inputsInline: false,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const CONDITION = javascriptGenerator.valueToCode(block, 'CONDITION', javascriptGenerator.ORDER_ATOMIC);
|
||||
const X = javascriptGenerator.valueToCode(block, 'X', javascriptGenerator.ORDER_ATOMIC);
|
||||
const Y = javascriptGenerator.valueToCode(block, 'Y', javascriptGenerator.ORDER_ATOMIC);
|
||||
return [`(${CONDITION || false} ? ${X} : ${Y})`, javascriptGenerator.ORDER_ATOMIC];
|
||||
})
|
||||
|
||||
// switch statement
|
||||
registerBlock(`${categoryPrefix}switch`, {
|
||||
message0: 'switch %1 %2 %3',
|
||||
|
|
|
|||
62
src/resources/blocks/events.js
Normal file
62
src/resources/blocks/events.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
import registerBlock from '../register';
|
||||
|
||||
const categoryPrefix = 'events_';
|
||||
const categoryColor = '#FFBF00';
|
||||
|
||||
function register() {
|
||||
// setInterval
|
||||
registerBlock(`${categoryPrefix}interval`, {
|
||||
message0: 'every %1 seconds do %2 %3',
|
||||
args0: [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIME",
|
||||
"check": "Number"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "BLOCKS"
|
||||
}
|
||||
],
|
||||
inputsInline: true,
|
||||
colour: categoryColor,
|
||||
}, (block) => {
|
||||
const TIME = javascriptGenerator.valueToCode(block, 'TIME', javascriptGenerator.ORDER_ATOMIC);
|
||||
const BLOCKS = javascriptGenerator.statementToCode(block, 'BLOCKS');
|
||||
const code = `setInterval(() => { ${BLOCKS} }, (${TIME} * 1000));`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
// setTimeout
|
||||
registerBlock(`${categoryPrefix}timeout`, {
|
||||
message0: 'in %1 seconds do %2 %3',
|
||||
args0: [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIME",
|
||||
"check": "Number"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "BLOCKS"
|
||||
}
|
||||
],
|
||||
previousStatement: null,
|
||||
nextStatement: null,
|
||||
inputsInline: true,
|
||||
colour: categoryColor,
|
||||
}, (block) => {
|
||||
const TIME = javascriptGenerator.valueToCode(block, 'TIME', javascriptGenerator.ORDER_ATOMIC);
|
||||
const BLOCKS = javascriptGenerator.statementToCode(block, 'BLOCKS');
|
||||
const code = `setTimeout(() => { ${BLOCKS} }, (${TIME} * 1000));`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
}
|
||||
|
||||
export default register;
|
||||
|
|
@ -46,6 +46,17 @@ function register() {
|
|||
return [NUMBER, javascriptGenerator.ORDER_ATOMIC];
|
||||
})
|
||||
|
||||
// null
|
||||
registerBlock(`${categoryPrefix}null`, {
|
||||
message0: 'null',
|
||||
args0: [],
|
||||
output: "Null",
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
return ['null', javascriptGenerator.ORDER_ATOMIC];
|
||||
})
|
||||
|
||||
// string
|
||||
registerBlock(`${categoryPrefix}string`, {
|
||||
message0: "'%1'",
|
||||
|
|
@ -53,7 +64,7 @@ function register() {
|
|||
{
|
||||
"type": "field_input",
|
||||
"name": "STRING",
|
||||
"value": 0,
|
||||
"text": "string",
|
||||
"spellcheck": false
|
||||
}
|
||||
],
|
||||
|
|
|
|||
30
src/resources/blocks/sound.js
Normal file
30
src/resources/blocks/sound.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
import registerBlock from '../register';
|
||||
|
||||
const categoryPrefix = 'sound_';
|
||||
const categoryColor = '#CF63CF';
|
||||
|
||||
function register() {
|
||||
// start playing a sound (and also it needs to load lol!!)
|
||||
registerBlock(`${categoryPrefix}startsound`, {
|
||||
message0: 'start sound %1',
|
||||
args0: [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "SOUND",
|
||||
"text": "https://t.ly/2gHlM",
|
||||
"spellcheck": false
|
||||
},
|
||||
],
|
||||
previousStatement: null,
|
||||
nextStatement: null,
|
||||
inputsInline: true,
|
||||
colour: categoryColor,
|
||||
}, (block) => {
|
||||
const SOUND = block.getFieldValue('SOUND')
|
||||
const code = `doSound(\`${SOUND}\`, Scratch.vm.runtime.targets.find(target => target.isStage), Scratch.vm.runtime);`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
}
|
||||
|
||||
export default register;
|
||||
|
|
@ -1,13 +1,13 @@
|
|||
const throwAwayVars = {}; // used for repeat loops
|
||||
const compileVars = {};
|
||||
export const compileVars = {};
|
||||
compileVars._idx = 0;
|
||||
compileVars.new = () => {
|
||||
const _listLow = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
|
||||
const _listHigh = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
|
||||
const _listSym = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '!', '@', '#', '$', '%', '&', '(', ')', '_', '-', '+', '=', '[', ']', '|'];
|
||||
const list = [].concat(_listLow, _listHigh, _listSym);
|
||||
const list = [].concat(_listLow, _listHigh);
|
||||
let str = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
for (let i = 0; i < 16; i++) {
|
||||
str += list[Math.round(Math.random() * (list.length - 1))];
|
||||
};
|
||||
return str;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,127 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
|
||||
const start = `
|
||||
function doSound(ab, cd, runtime) {
|
||||
const audioEngine = runtime.audioEngine;
|
||||
|
||||
const fetchAsArrayBufferWithTimeout = (url) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
let timeout = setTimeout(() => {
|
||||
xhr.abort();
|
||||
reject(new Error("Timed out"));
|
||||
}, 5000);
|
||||
xhr.onload = () => {
|
||||
clearTimeout(timeout);
|
||||
if (xhr.status === 200) {
|
||||
resolve(xhr.response);
|
||||
} else {
|
||||
reject(new Error(\`HTTP error \${xhr.status} while fetching \${url}\`));
|
||||
}
|
||||
};
|
||||
xhr.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error(\`Failed to request \${url}\`));
|
||||
};
|
||||
xhr.responseType = "arraybuffer";
|
||||
xhr.open("GET", url);
|
||||
xhr.send();
|
||||
});
|
||||
|
||||
const soundPlayerCache = new Map();
|
||||
|
||||
const decodeSoundPlayer = async (url) => {
|
||||
const cached = soundPlayerCache.get(url);
|
||||
if (cached) {
|
||||
if (cached.sound) {
|
||||
return cached.sound;
|
||||
}
|
||||
throw cached.error;
|
||||
}
|
||||
|
||||
try {
|
||||
const arrayBuffer = await fetchAsArrayBufferWithTimeout(url);
|
||||
const soundPlayer = await audioEngine.decodeSoundPlayer({
|
||||
data: {
|
||||
buffer: arrayBuffer,
|
||||
},
|
||||
});
|
||||
soundPlayerCache.set(url, {
|
||||
sound: soundPlayer,
|
||||
error: null,
|
||||
});
|
||||
return soundPlayer;
|
||||
} catch (e) {
|
||||
soundPlayerCache.set(url, {
|
||||
sound: null,
|
||||
error: e,
|
||||
});
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
const playWithAudioEngine = async (url, target) => {
|
||||
const soundBank = target.sprite.soundBank;
|
||||
|
||||
let soundPlayer;
|
||||
try {
|
||||
const originalSoundPlayer = await decodeSoundPlayer(url);
|
||||
soundPlayer = originalSoundPlayer.take();
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
"Could not fetch audio; falling back to primitive approach",
|
||||
e
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
soundBank.addSoundPlayer(soundPlayer);
|
||||
await soundBank.playSound(target, soundPlayer.id);
|
||||
|
||||
delete soundBank.soundPlayers[soundPlayer.id];
|
||||
soundBank.playerTargets.delete(soundPlayer.id);
|
||||
soundBank.soundEffects.delete(soundPlayer.id);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
const playWithAudioElement = (url, target) =>
|
||||
new Promise((resolve, reject) => {
|
||||
const mediaElement = new Audio(url);
|
||||
|
||||
mediaElement.volume = target.volume / 100;
|
||||
|
||||
mediaElement.onended = () => {
|
||||
resolve();
|
||||
};
|
||||
mediaElement
|
||||
.play()
|
||||
.then(() => {
|
||||
// Wait for onended
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
const playSound = async (url, target) => {
|
||||
try {
|
||||
if (!(await Scratch.canFetch(url))) {
|
||||
throw new Error(\`Permission to fetch \${url} denied\`);
|
||||
}
|
||||
|
||||
const success = await playWithAudioEngine(url, target);
|
||||
if (!success) {
|
||||
return await playWithAudioElement(url, target);
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn(\`All attempts to play \${url} failed\`, e);
|
||||
}
|
||||
};
|
||||
|
||||
playSound(ab, cd)
|
||||
}`
|
||||
|
||||
class Compiler {
|
||||
/**
|
||||
* Generates JavaScript code from the provided workspace & info.
|
||||
|
|
@ -19,7 +141,9 @@ class Compiler {
|
|||
`(function (Scratch) {`,
|
||||
`const variables = {};`,
|
||||
`const blocks = [];`,
|
||||
`const menus = [];`
|
||||
`const menus = [];`,
|
||||
``,
|
||||
start
|
||||
];
|
||||
const classRegistry = {
|
||||
top: [
|
||||
|
|
|
|||
|
|
@ -51,8 +51,10 @@
|
|||
registerGeneric();
|
||||
|
||||
import registerCore from "../resources/blocks/core.js";
|
||||
import registerEvents from "../resources/blocks/events.js";
|
||||
import registerControl from "../resources/blocks/control.js";
|
||||
import registerSensing from "../resources/blocks/sensing.js";
|
||||
import registerSound from "../resources/blocks/sound.js";
|
||||
import registerLiterals from "../resources/blocks/literals.js";
|
||||
import registerOperators from "../resources/blocks/operators.js";
|
||||
import registerVariables from "../resources/blocks/variables.js";
|
||||
|
|
@ -61,6 +63,8 @@
|
|||
|
||||
registerCore();
|
||||
registerControl();
|
||||
registerEvents();
|
||||
registerSound();
|
||||
registerSensing();
|
||||
registerLiterals();
|
||||
registerOperators();
|
||||
|
|
@ -531,22 +535,6 @@
|
|||
>
|
||||
Download
|
||||
</StyledButton>
|
||||
<div style="margin-right: 8px" />
|
||||
<StyledButton
|
||||
on:click={() => {
|
||||
window.open("https://turbowarp.org/editor?extension=" + encodeURI("data:text/plain;base64," + btoa(lastGeneratedCode)), '_blank').focus();
|
||||
}}
|
||||
>
|
||||
TurboWarp
|
||||
</StyledButton>
|
||||
<div style="margin-right: 4px" />
|
||||
<StyledButton
|
||||
on:click={() => {
|
||||
window.open("https://studio.penguinmod.com/editor?extension=" + encodeURI("data:text/plain;base64," + btoa(lastGeneratedCode)), '_blank').focus();
|
||||
}}
|
||||
>
|
||||
PenguinMod
|
||||
</StyledButton>
|
||||
</div>
|
||||
<div class="codeWrapper">
|
||||
<div class="codeDisplay">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue