basic stuff
This commit is contained in:
parent
af5e07e1e5
commit
4abadd5358
28 changed files with 4542 additions and 1 deletions
10
.gitignore
vendored
Normal file
10
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/build
|
||||
/.svelte-kit
|
||||
/package
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
2
.npmrc
Normal file
2
.npmrc
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
engine-strict=true
|
||||
resolution-mode=highest
|
||||
|
|
@ -1,7 +1,14 @@
|
|||
<img src="https://raw.githubusercontent.com/JeremyGamer13/turbobuilder/main/icon.png"/>
|
||||
<img src="./icon.png" width="64" height="64" /> <img src="./icon_title.png" height="64" />
|
||||
|
||||
# TurboBuilder
|
||||
Create extensions for TurboWarp using block-based coding.
|
||||
|
||||
## In development
|
||||
This project is not finished and is still being worked on. Expect bugs and problems that may prevent the site from working.
|
||||
|
||||
## Running locally
|
||||
|
||||
1. Clone the repo
|
||||
2. Run `npm i --force` in a terminal inside the folder where the repo is
|
||||
3. Run `npm run dev` in a terminal inside the folder where the repo is
|
||||
4. Visit http://localhost:5173/
|
||||
BIN
icon_nomargin.png
Normal file
BIN
icon_nomargin.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
icon_title.png
Normal file
BIN
icon_title.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
3723
package-lock.json
generated
Normal file
3723
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
package.json
Normal file
24
package.json
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
{
|
||||
"name": "turbobuilder",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^2.0.0",
|
||||
"@sveltejs/kit": "^1.20.4",
|
||||
"svelte": "^4.0.5",
|
||||
"vite": "^4.4.2"
|
||||
},
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@blockly/continuous-toolbox": "^5.0.2",
|
||||
"@sveltejs/adapter-vercel": "^3.0.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"svelte-blockly": "^0.1.0"
|
||||
}
|
||||
}
|
||||
48
src/app.html
Normal file
48
src/app.html
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<title>TurboBuilder - Make extensions with blocks</title>
|
||||
<meta name="description" content="Create TurboWarp extensions using Scratch-like block coding." />
|
||||
<meta name="keywords" content="turbowarp, extensions, blocks" />
|
||||
<meta name="author" content="JeremyGamer13" />
|
||||
<meta name="theme-color" content="#ff4b4b" />
|
||||
<meta name="og:image" content="icon.png" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<style>
|
||||
*:not(code) {
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ff4b4b;
|
||||
}
|
||||
a:hover {
|
||||
color: #ff7373;
|
||||
}
|
||||
a:active {
|
||||
color: #a52b2b;
|
||||
}
|
||||
|
||||
/* blockly overrides */
|
||||
.blocklyMenu {
|
||||
color: black;
|
||||
}
|
||||
.blocklyToolboxCategory {
|
||||
cursor: pointer;
|
||||
}
|
||||
.blocklyTreeLabel {
|
||||
font-size: 10px !important;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
.categoryBubble {
|
||||
border: 1px rgba(0,0,0,0.35) solid;
|
||||
}
|
||||
</style>
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
9
src/lib/NavigationBar/Divider.svelte
Normal file
9
src/lib/NavigationBar/Divider.svelte
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
<div class="divider" />
|
||||
|
||||
<style>
|
||||
.divider {
|
||||
border-right: 1px dashed rgba(0, 0, 0, 0.15);
|
||||
margin: 0 0.5rem;
|
||||
height: 90%;
|
||||
}
|
||||
</style>
|
||||
40
src/lib/NavigationBar/NavigationBar.svelte
Normal file
40
src/lib/NavigationBar/NavigationBar.svelte
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<script>
|
||||
// Components
|
||||
import Divider from "$lib/NavigationBar/Divider.svelte";
|
||||
</script>
|
||||
|
||||
<div class="nav">
|
||||
<img
|
||||
src="/images/icon.png"
|
||||
alt="Logo"
|
||||
class="logo-margin"
|
||||
style="height: 100%;"
|
||||
/>
|
||||
<Divider />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--nav-height: 3rem;
|
||||
}
|
||||
|
||||
.nav {
|
||||
position: fixed;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: calc(100% - 8px);
|
||||
height: calc(var(--nav-height) - 8px);
|
||||
|
||||
padding: 4px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
background: #ff4b4b;
|
||||
}
|
||||
.logo-margin {
|
||||
margin-right: 8px;
|
||||
}
|
||||
</style>
|
||||
6
src/lib/Toolbox/Toolbox.xml
Normal file
6
src/lib/Toolbox/Toolbox.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<xml>
|
||||
<category name="Control" colour="#FFAB19">
|
||||
<block type="control_ifthen" />
|
||||
<block type="control_ifthenelse" />
|
||||
</category>
|
||||
</xml>
|
||||
1
src/lib/index.js
Normal file
1
src/lib/index.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
72
src/resources/blocks/control.js
Normal file
72
src/resources/blocks/control.js
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
import registerBlock from '../register';
|
||||
|
||||
const categoryPrefix = 'control_';
|
||||
const categoryColor = '#FFAB19';
|
||||
|
||||
function register() {
|
||||
// if <> then {}
|
||||
registerBlock(`${categoryPrefix}ifthen`, {
|
||||
message0: 'if %1 then %2 %3',
|
||||
args0: [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "BLOCKS"
|
||||
}
|
||||
],
|
||||
previousStatement: null,
|
||||
nextStatement: null,
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const CONDITION = javascriptGenerator.valueToCode(block, 'CONDITION', javascriptGenerator.ORDER_ATOMIC);
|
||||
const BLOCKS = javascriptGenerator.statementToCode(block, 'BLOCKS');
|
||||
const code = `if (${CONDITION ? `Boolean(${CONDITION})` : 'false'}) { ${BLOCKS} };`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
// if <> then {} else {}
|
||||
registerBlock(`${categoryPrefix}ifthenelse`, {
|
||||
message0: 'if %1 then %2 %3 else %4 %5',
|
||||
args0: [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "CONDITION",
|
||||
"check": "Boolean"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "BLOCKS"
|
||||
},
|
||||
{
|
||||
"type": "input_dummy"
|
||||
},
|
||||
{
|
||||
"type": "input_statement",
|
||||
"name": "BLOCKS2"
|
||||
}
|
||||
],
|
||||
previousStatement: null,
|
||||
nextStatement: null,
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const CONDITION = javascriptGenerator.valueToCode(block, 'CONDITION', javascriptGenerator.ORDER_ATOMIC);
|
||||
const BLOCKS = javascriptGenerator.statementToCode(block, 'BLOCKS');
|
||||
const BLOCKS2 = javascriptGenerator.statementToCode(block, 'BLOCKS2');
|
||||
const code = `if (${CONDITION ? `Boolean(${CONDITION})` : 'false'}) { ${BLOCKS} } else { ${BLOCKS2} };`;
|
||||
return `${code}\n`;
|
||||
})
|
||||
}
|
||||
|
||||
export default register;
|
||||
11
src/resources/blocks/core.js
Normal file
11
src/resources/blocks/core.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
import registerBlock from '../register';
|
||||
|
||||
const categoryPrefix = 'core_';
|
||||
const categoryColor = '#ff4b4b';
|
||||
|
||||
function register() {
|
||||
|
||||
}
|
||||
|
||||
export default register;
|
||||
67
src/resources/blocks/generic.js
Normal file
67
src/resources/blocks/generic.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import javascriptGenerator from '../javascriptGenerator';
|
||||
import registerBlock from '../register';
|
||||
|
||||
const categoryPrefix = 'generic_';
|
||||
const categoryColor = '#fff';
|
||||
|
||||
function register() {
|
||||
// number
|
||||
registerBlock(`${categoryPrefix}number`, {
|
||||
message0: '%1',
|
||||
args0: [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "NUMBER",
|
||||
"value": 0
|
||||
}
|
||||
],
|
||||
output: "Number",
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const NUMBER = block.getFieldValue('NUMBER');
|
||||
const code = `Number(${NUMBER})`;
|
||||
return [code, javascriptGenerator.ORDER_NONE];
|
||||
})
|
||||
// text
|
||||
registerBlock(`${categoryPrefix}text`, {
|
||||
message0: '%1',
|
||||
args0: [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "TEXT",
|
||||
"text": ""
|
||||
}
|
||||
],
|
||||
output: "String",
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const TEXT = block.getFieldValue('TEXT');
|
||||
const code = `String(${JSON.stringify(TEXT)})`;
|
||||
return [code, javascriptGenerator.ORDER_NONE];
|
||||
})
|
||||
// boolean
|
||||
registerBlock(`${categoryPrefix}boolean`, {
|
||||
message0: '%1',
|
||||
args0: [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "STATE",
|
||||
"options": [
|
||||
["True", "true"],
|
||||
["False", "false"],
|
||||
["Random", "Boolean(Math.round(Math.random()))"]
|
||||
]
|
||||
}
|
||||
],
|
||||
output: "Boolean",
|
||||
inputsInline: true,
|
||||
colour: categoryColor
|
||||
}, (block) => {
|
||||
const code = block.getFieldValue('STATE');
|
||||
return [code, javascriptGenerator.ORDER_NONE];
|
||||
})
|
||||
}
|
||||
|
||||
export default register;
|
||||
18
src/resources/compiler/compileVarSection.js
Normal file
18
src/resources/compiler/compileVarSection.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
const throwAwayVars = {}; // used for repeat loops
|
||||
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);
|
||||
let str = '';
|
||||
for (let i = 0; i < 100; i++) {
|
||||
str += list[Math.round(Math.random() * (list.length - 1))];
|
||||
};
|
||||
return str;
|
||||
};
|
||||
compileVars.next = () => {
|
||||
compileVars._idx++;
|
||||
return `v${compileVars._idx}`;
|
||||
};
|
||||
35
src/resources/compiler/index.js
Normal file
35
src/resources/compiler/index.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// compile functions
|
||||
import raw_randomNumberGen from './randomNumberGen.js?raw';
|
||||
import raw_compileVarSection from './compileVarSection.js?raw';
|
||||
|
||||
import javascriptGenerator from '../javascriptGenerator';
|
||||
|
||||
class Compiler {
|
||||
/**
|
||||
* Generates JavaScript code from the provided workspace.
|
||||
* @param {Blockly.Workspace} workspace
|
||||
* @returns {string} Generated code.
|
||||
*/
|
||||
compile(workspace) {
|
||||
const code = javascriptGenerator.workspaceToCode(workspace);
|
||||
|
||||
const headerCode = [
|
||||
`/*`,
|
||||
` This extension was made with TurboBuilder!`,
|
||||
` https://turbobuilder.vercel.app/`,
|
||||
`*/`,
|
||||
`(function (Scratch) {`,
|
||||
`const variables = {};`,
|
||||
raw_compileVarSection,
|
||||
raw_randomNumberGen
|
||||
];
|
||||
const footerCode = [
|
||||
`Scratch.extensions.register(new Extension());`,
|
||||
`})(Scratch);`
|
||||
];
|
||||
|
||||
return [].concat(headerCode, code, footerCode).join('\n');
|
||||
}
|
||||
}
|
||||
|
||||
export default Compiler;
|
||||
18
src/resources/compiler/randomNumberGen.js
Normal file
18
src/resources/compiler/randomNumberGen.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* Chooses a random number between the min and max.
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @returns {number}
|
||||
*/
|
||||
function randomNumberGen(min, max) {
|
||||
// swap if min is larger
|
||||
if (min > max) {
|
||||
let _v = max;
|
||||
max = min;
|
||||
min = _v;
|
||||
}
|
||||
// math
|
||||
const difference = max - min;
|
||||
const random = Math.random() * difference;
|
||||
return min + random;
|
||||
};
|
||||
37
src/resources/compiler/xmlToCode.js
Normal file
37
src/resources/compiler/xmlToCode.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import Blockly from "blockly/core";
|
||||
import javascriptGenerator from '../javascriptGenerator';
|
||||
|
||||
function xmlToCode(xml) {
|
||||
// this sucks but i dont know any other method
|
||||
// make div
|
||||
const tempDiv = document.createElement("div");
|
||||
tempDiv.style = "display:none";
|
||||
document.body.append(tempDiv);
|
||||
// inject workpace
|
||||
const workspace = Blockly.inject(tempDiv, {
|
||||
collapse: true,
|
||||
comments: true,
|
||||
scrollbars: true,
|
||||
disable: false
|
||||
});
|
||||
|
||||
let code = "";
|
||||
try {
|
||||
const dom = Blockly.utils.xml.textToDom(xml);
|
||||
Blockly.Xml.domToWorkspace(dom, workspace);
|
||||
// yay we get to compile now
|
||||
code = javascriptGenerator.workspaceToCode(workspace);
|
||||
} catch (err) {
|
||||
// we do try catch so if we fail to parse
|
||||
// we dont leave behind an entire workspace & div in the document
|
||||
console.warn("could not compile xml;", err);
|
||||
}
|
||||
|
||||
// gtfo
|
||||
workspace.dispose();
|
||||
tempDiv.remove();
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
export default xmlToCode;
|
||||
37
src/resources/fileDialog/index.js
Normal file
37
src/resources/fileDialog/index.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
// file-dialog exists on NPM and thats what this file is
|
||||
// however it uses module.exports and exports a function
|
||||
// which vite absolutely HATES and REFUSES to build no matter what
|
||||
// so its reimplemented here with a few changes to work
|
||||
|
||||
function fileDialog(...args) {
|
||||
const input = document.createElement('input');
|
||||
|
||||
// Set config
|
||||
if (typeof args[0] === 'object') {
|
||||
if (args[0].multiple === true) input.setAttribute('multiple', '');
|
||||
if (args[0].accept !== undefined) input.setAttribute('accept', args[0].accept);
|
||||
}
|
||||
input.setAttribute('type', 'file');
|
||||
|
||||
// IE10/11 Addition
|
||||
input.style.display = 'none';
|
||||
input.setAttribute('id', 'hidden-file');
|
||||
document.body.appendChild(input);
|
||||
|
||||
// Return promise/callvack
|
||||
return new Promise(resolve => {
|
||||
input.addEventListener('change', () => {
|
||||
resolve(input.files);
|
||||
const lastArg = args[args.length - 1];
|
||||
if (typeof lastArg === "function") lastArg(input.files);
|
||||
|
||||
// IE10/11 Addition
|
||||
document.body.removeChild(input);
|
||||
})
|
||||
|
||||
// Simluate click event
|
||||
input.click();
|
||||
})
|
||||
}
|
||||
|
||||
export default fileDialog;
|
||||
10
src/resources/javascriptGenerator/index.js
Normal file
10
src/resources/javascriptGenerator/index.js
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
// vercel's build doesnt work for some reason
|
||||
// its related to js generator and how its imported
|
||||
// so lets just import it the way that it wants
|
||||
|
||||
// we COULD modify the javascript generator here
|
||||
// but its much cleaner to leave this alone
|
||||
import pkg from 'blockly/javascript.js';
|
||||
const { javascriptGenerator } = pkg;
|
||||
|
||||
export default javascriptGenerator;
|
||||
13
src/resources/preload/index.js
Normal file
13
src/resources/preload/index.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Preloads all audio files specified.
|
||||
* This is because the hosted version of TurboBuilder will cause a bit of a delay before playing audio
|
||||
* due to the host having to provide the file, not the local machine.
|
||||
* @param {Array} files An array full of file paths to audio files.
|
||||
*/
|
||||
function preload(files) {
|
||||
for (const path of files) {
|
||||
new Audio(path);
|
||||
}
|
||||
}
|
||||
|
||||
export default preload;
|
||||
16
src/resources/register/index.js
Normal file
16
src/resources/register/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import Blockly from 'blockly/core';
|
||||
import javascriptGenerator from '../javascriptGenerator';
|
||||
|
||||
export default (blockName, jsonData, compileFunction) => {
|
||||
const blockObject = {
|
||||
init: function () {
|
||||
this.jsonInit(jsonData);
|
||||
}
|
||||
};
|
||||
|
||||
// register visual block
|
||||
Blockly.Blocks[blockName] = blockObject
|
||||
|
||||
// register block compile function
|
||||
javascriptGenerator[blockName] = compileFunction;
|
||||
}
|
||||
312
src/routes/+page.svelte
Normal file
312
src/routes/+page.svelte
Normal file
|
|
@ -0,0 +1,312 @@
|
|||
<script>
|
||||
import { onMount } from "svelte";
|
||||
|
||||
// Components
|
||||
import NavigationBar from "$lib/NavigationBar/NavigationBar.svelte";
|
||||
|
||||
// Toolbox
|
||||
import Toolbox from "$lib/Toolbox/Toolbox.xml?raw";
|
||||
|
||||
import JSZip from "jszip";
|
||||
import * as FileSaver from "file-saver";
|
||||
import fileDialog from "../resources/fileDialog";
|
||||
|
||||
import Blockly from "blockly/core";
|
||||
import * as ContinuousToolboxPlugin from "@blockly/continuous-toolbox";
|
||||
const Theme = Blockly.Theme.defineTheme("BasicTheme", {
|
||||
base: Blockly.Themes.Classic,
|
||||
fontStyle: {
|
||||
family: '"Helvetica Neue", Helvetica, Arial, sans-serif',
|
||||
weight: "600",
|
||||
size: 12,
|
||||
},
|
||||
startHats: true,
|
||||
});
|
||||
|
||||
import En from "blockly/msg/en";
|
||||
import "blockly/blocks";
|
||||
|
||||
import BlocklyComponent from "svelte-blockly";
|
||||
|
||||
import Compiler from "../resources/compiler";
|
||||
import preload from "../resources/preload";
|
||||
|
||||
// Blocks
|
||||
import registerGeneric from "../resources/blocks/generic.js";
|
||||
registerGeneric();
|
||||
|
||||
import registerCore from "../resources/blocks/core.js";
|
||||
import registerControl from "../resources/blocks/control.js";
|
||||
registerCore();
|
||||
registerControl();
|
||||
|
||||
const en = {
|
||||
rtl: false,
|
||||
msg: {
|
||||
...En,
|
||||
},
|
||||
};
|
||||
|
||||
const config = {
|
||||
toolbox: Toolbox,
|
||||
collapse: true,
|
||||
comments: true,
|
||||
scrollbars: true,
|
||||
disable: false,
|
||||
theme: Theme,
|
||||
renderer: "zelos",
|
||||
zoom: {
|
||||
controls: true,
|
||||
wheel: true,
|
||||
startScale: 0.8,
|
||||
maxScale: 4,
|
||||
minScale: 0.25,
|
||||
scaleSpeed: 1.1,
|
||||
},
|
||||
plugins: {
|
||||
toolbox: ContinuousToolboxPlugin.ContinuousToolbox,
|
||||
flyoutsVerticalToolbox: ContinuousToolboxPlugin.ContinuousFlyout,
|
||||
metricsManager: ContinuousToolboxPlugin.ContinuousMetrics,
|
||||
},
|
||||
};
|
||||
|
||||
let workspace;
|
||||
let compiler;
|
||||
let lastGeneratedCode = "";
|
||||
|
||||
onMount(() => {
|
||||
console.log("ignore the warnings above we dont care about those");
|
||||
|
||||
window.onbeforeunload = () => "";
|
||||
compiler = new Compiler(workspace);
|
||||
// workspace was changed
|
||||
workspace.addChangeListener(() => {
|
||||
const code = compiler.compile(workspace);
|
||||
lastGeneratedCode = code;
|
||||
});
|
||||
});
|
||||
|
||||
let fileMenu;
|
||||
function showFileMenu() {
|
||||
if (fileMenu.style.display == "none") {
|
||||
fileMenu.style.display = "";
|
||||
return;
|
||||
}
|
||||
fileMenu.style.display = "none";
|
||||
}
|
||||
|
||||
let projectName = "";
|
||||
function downloadProject() {
|
||||
// generate file name
|
||||
let filteredProjectName = projectName.replace(/[^a-z0-9\-]+/gim, "_");
|
||||
let fileName = filteredProjectName + ".tbext";
|
||||
if (!filteredProjectName) {
|
||||
fileName = "MyProject.tbext";
|
||||
}
|
||||
|
||||
// data
|
||||
const projectData = State.serializeProject(State.currentProject);
|
||||
|
||||
// zip
|
||||
const zip = new JSZip();
|
||||
zip.file(
|
||||
"README.txt",
|
||||
"This file is not meant to be opened!" +
|
||||
"\nBe careful as you can permanently break your project!"
|
||||
);
|
||||
|
||||
// workspaces
|
||||
const workspaces = zip.folder("workspaces");
|
||||
for (const character of State.currentProject.characters) {
|
||||
workspaces.file(character.id + ".xml", character.xml);
|
||||
}
|
||||
|
||||
// data
|
||||
const data = zip.folder("data");
|
||||
data.file("project.json", projectData);
|
||||
|
||||
// download
|
||||
zip.generateAsync({ type: "blob" }).then((blob) => {
|
||||
FileSaver.saveAs(blob, fileName);
|
||||
});
|
||||
}
|
||||
function loadProject() {
|
||||
fileDialog({ accept: ".tbext" }).then((files) => {
|
||||
if (!files) return;
|
||||
const file = files[0];
|
||||
|
||||
// set project name
|
||||
const projectNameIdx = file.name.lastIndexOf(".tbext");
|
||||
projectName = file.name.substring(0, projectNameIdx);
|
||||
|
||||
JSZip.loadAsync(file.arrayBuffer()).then(async (zip) => {
|
||||
console.log("loaded zip file...");
|
||||
|
||||
// get project json from the data folder
|
||||
const dataFolder = zip.folder("data");
|
||||
const projectJsonString = await dataFolder
|
||||
.file("project.json")
|
||||
.async("string");
|
||||
const projectJson = JSON.parse(projectJsonString);
|
||||
|
||||
// get project workspace xml stuffs
|
||||
const workspacesFolder = zip.folder("workspaces");
|
||||
const fileNames = [];
|
||||
workspacesFolder.forEach((_, file) => {
|
||||
const fileName = file.name.replace("workspaces/", "");
|
||||
fileNames.push(fileName);
|
||||
});
|
||||
// console.log(fileNames); // debug
|
||||
const idWorkspacePairs = {};
|
||||
for (const fileName of fileNames) {
|
||||
const idx = fileName.lastIndexOf(".xml");
|
||||
const id = fileName.substring(0, idx);
|
||||
// assign to pairs
|
||||
idWorkspacePairs[id] = await workspacesFolder
|
||||
.file(fileName)
|
||||
.async("string");
|
||||
}
|
||||
// console.log(idWorkspacePairs); // debug
|
||||
|
||||
// laod
|
||||
console.log(projectJson); // debug
|
||||
State.loadProject(projectJson, idWorkspacePairs);
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavigationBar>
|
||||
<input
|
||||
class="project-name"
|
||||
type="text"
|
||||
placeholder="Extension Name here"
|
||||
style="margin-left:4px;margin-right:4px"
|
||||
bind:value={projectName}
|
||||
/>
|
||||
</NavigationBar>
|
||||
<div class="main">
|
||||
<div class="row-menus">
|
||||
<div class="blocklyWrapper">
|
||||
<BlocklyComponent {config} locale={en} bind:workspace />
|
||||
</div>
|
||||
<div class="row-submenus">
|
||||
<div class="assetsWrapper">
|
||||
<h1>Assets</h1>
|
||||
{#if projectName}
|
||||
<p>{projectName} extension</p>
|
||||
{:else}
|
||||
<p>Extension</p>
|
||||
{/if}
|
||||
<p>
|
||||
These things are not required, you can leave them empty if
|
||||
you want!
|
||||
</p>
|
||||
<br />
|
||||
<p>
|
||||
Documentation URL:
|
||||
<input type="text" placeholder="https://..." />
|
||||
</p>
|
||||
<p>
|
||||
Extension Icon:
|
||||
<input type="file" />
|
||||
</p>
|
||||
<!-- <p class="warning">
|
||||
Warning! This is not an image! The icon may appear broken!
|
||||
</p> -->
|
||||
</div>
|
||||
<div class="codeWrapper">
|
||||
<textarea
|
||||
value={lastGeneratedCode}
|
||||
disabled="true"
|
||||
style="width:100%;height:100%;border:0;padding:0;color:white;background:black;font-family:monospace"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--nav-height: 3rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: var(--nav-height);
|
||||
width: 100%;
|
||||
height: calc(100% - var(--nav-height));
|
||||
}
|
||||
|
||||
.project-name {
|
||||
width: 236px;
|
||||
|
||||
font-size: 20px;
|
||||
|
||||
border-radius: 6px;
|
||||
outline: 1px dashed rgba(0, 0, 0, 0.15);
|
||||
border: 0;
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
color: white;
|
||||
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
padding: 0.5rem;
|
||||
transition: 0.25s;
|
||||
}
|
||||
.project-name::placeholder {
|
||||
font-weight: normal;
|
||||
color: white;
|
||||
opacity: 1;
|
||||
font-style: italic;
|
||||
}
|
||||
.project-name:hover {
|
||||
background-color: hsla(0, 100%, 100%, 0.5);
|
||||
transition: 0.25s;
|
||||
}
|
||||
.project-name:active,
|
||||
.project-name:focus {
|
||||
outline: none;
|
||||
border: 1px solid hsla(0, 100%, 100%, 0);
|
||||
box-shadow: 0 0 0 calc(0.5rem * 0.5) hsla(0, 100%, 100%, 0.25);
|
||||
background-color: hsla(0, 100%, 100%, 1);
|
||||
color: black;
|
||||
transition: 0.25s;
|
||||
}
|
||||
|
||||
.row-menus {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
.row-submenus {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 35%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.blocklyWrapper {
|
||||
position: relative;
|
||||
width: 65%;
|
||||
height: 100%;
|
||||
}
|
||||
.assetsWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
.codeWrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 50%;
|
||||
}
|
||||
|
||||
.warning {
|
||||
background-color: yellow;
|
||||
color: black;
|
||||
}
|
||||
</style>
|
||||
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
BIN
static/images/icon.png
Normal file
BIN
static/images/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
13
svelte.config.js
Normal file
13
svelte.config.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import adapter from '@sveltejs/adapter-auto';
|
||||
|
||||
/** @type {import('@sveltejs/kit').Config} */
|
||||
const config = {
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://kit.svelte.dev/docs/adapter-auto for a list.
|
||||
// If your environment is not supported or you settled on a specific environment, switch out the adapter.
|
||||
// See https://kit.svelte.dev/docs/adapters for more information about adapters.
|
||||
adapter: adapter()
|
||||
}
|
||||
};
|
||||
|
||||
export default config;
|
||||
12
vite.config.js
Normal file
12
vite.config.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()],
|
||||
optimizeDeps: {
|
||||
include: [
|
||||
'@blockly/continuous-toolbox',
|
||||
'file-saver',
|
||||
]
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue