Skip to content

Commit

Permalink
🧱 NoneBlockly basic framework implementation (#1
Browse files Browse the repository at this point in the history
  • Loading branch information
mnixry authored Oct 12, 2024
2 parents 652a5d2 + 3a85816 commit 36fb13b
Show file tree
Hide file tree
Showing 48 changed files with 4,705 additions and 3,830 deletions.
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
"engines": {
"node": ">=18.0.0"
},
"packageManager": "pnpm@8.15.0+",
"packageManager": "pnpm@9.4.0",
"devDependencies": {
"prettier": "^3.2.5"
"prettier": "^3.3.2"
}
}
28 changes: 28 additions & 0 deletions packages/app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<title>NoneBlockly for NoneBot2</title>
</head>

<body>
<div id="app"></div>
<script type="module" src="./src/main.ts"></script>
</body>
</html>

<style lang="scss">
html {
font-family: "Inter", sans-serif;
line-height: 1.2;
font-size: 1.1rem;
overflow-x: hidden;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
overflow-y: auto;
}
</style>
39 changes: 23 additions & 16 deletions packages/app/package.json
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
{
"name": "@noneblockly/app",
"version": "0.0.0",
"main": "index.js",
"version": "0.1.0",
"main": "main.ts",
"private": true,
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --mode production",
"start": "webpack serve --open --mode development"
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"keywords": [
"blockly"
],
"author": "",
"license": "Apache-2.0",
"license": "MIT",
"devDependencies": {
"css-loader": "^6.11.0",
"html-webpack-plugin": "^5.6.0",
"source-map-loader": "^4.0.2",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"typescript": "^5.4.4",
"webpack": "^5.91.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.15.2"
"@blockly/theme-dark": "^7.0.1",
"@highlightjs/vue-plugin": "^2.1.0",
"@mdi/js": "^7.4.47",
"@types/file-saver": "^2.0.7",
"@vitejs/plugin-vue": "^5.0.5",
"typescript": "^5.5.2",
"vite": "^5.3.1",
"vite-plugin-static-copy": "^1.0.5",
"vite-plugin-vuetify": "^2.0.3",
"vue-tsc": "^2.0.21"
},
"dependencies": {
"blockly": "^10.4.3"
"blockly": "^11.1.1",
"file-saver": "^2.0.5",
"highlight.js": "^11.9.0",
"jszip": "^3.10.1",
"sass": "^1.77.6",
"vue": "^3.4.29",
"vuetify": "^3.6.10"
}
}
85 changes: 85 additions & 0 deletions packages/app/src/App.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<script setup lang="ts">
import { onMounted, shallowRef } from "vue";
// Components
import ContentCard from "@/components/ContentCard.vue";
import BlocklyTab from "@/components/BlocklyTab.vue";
import TutorialTab from "@/components/TutorialTab.vue";
import CodeTab from "@/components/CodeTab.vue";
import ConfigTab from "@/components/ConfigTab.vue";
import ButtonPanel from "@/components/ButtonPanel.vue";
// Workspace
import { loadJson, generateCode } from "@/workspace";
import { optionsStore, workspaceStore } from "@/workspace";
// Workspace data
import { startBlocks } from "@/default";
// Blockly config
import * as Blockly from "blockly";
import { blocks } from "@/blocks";
import { toolbox } from "@/toolbox";
import { generators } from "@/generators";
import { pythonGenerator } from "blockly/python";
import * as ZhHans from "blockly/msg/zh-hans";
Blockly.common.defineBlocks(blocks);
generators.forEach((generator) => {
Object.assign(pythonGenerator.forBlock, generator);
});
pythonGenerator.addReservedWords(
"json,Annotated,Matcher,Message,EventMessage,CommandArg,on_command,on_message,on_alconna,to_me",
);
Blockly.setLocale(ZhHans);
// Set store data
optionsStore.toolbox = toolbox;
workspaceStore.startBlocks = startBlocks;
const workspace = Blockly.getMainWorkspace();
workspaceStore.workspace = workspace;
onMounted(() => {
loadJson();
const workspace = Blockly.getMainWorkspace();
workspace.addChangeListener(generateCode);
});
</script>

<template>
<v-app>
<v-card class="rounded-0">
<div class="pa-0 ma-0">
<ContentCard>
<template v-slot:tab-0>
<BlocklyTab
id="blockly-div"
:options="optionsStore"
ref="workspace"
/>
</template>
<template v-slot:tab-1>
<TutorialTab />
</template>
<template v-slot:tab-2>
<CodeTab />
</template>
<template v-slot:tab-3>
<ConfigTab />
</template>
</ContentCard>
<ButtonPanel />
</div>
</v-card>
</v-app>
</template>

<style lang="scss" scoped>
@import url("https://fonts.googleapis.com/css?family=Open+Sans");
@import url("https://fonts.googleapis.com/css?family=Inter");
</style>

<script lang="ts">
import hljs from "highlight.js/lib/core";
import python from "highlight.js/lib/languages/python";
hljs.registerLanguage("python", python);
</script>
30 changes: 30 additions & 0 deletions packages/app/src/blocks/fields/alconna_helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as Blockly from "blockly/core";

export function getAlconnaArg(block: Blockly.Block): string[] {
let args: string[] = [];
// get top block
let parent = block.getParent();
if (parent == null) {
return [];
}
while (parent.type != "nonebot_on_alconna") {
parent = parent.getParent();
if (parent == null) {
return [];
}
}
// get all arg blocks of alconna top block
for (let n = 0; n < (parent as any).itemCount_; n++) {
const arg_block = parent
.getInput("ARG" + String(n))
?.connection?.targetConnection?.getSourceBlock();
const arg_type = arg_block?.type;
if (arg_type === "alconna_arg") {
const arg_name = arg_block?.getFieldValue("NAME");
if (arg_name) {
args.push(arg_name);
}
}
}
return args;
}
67 changes: 67 additions & 0 deletions packages/app/src/blocks/fields/field_minus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview A function that creates a minus button used for mutation.
*/

import * as Blockly from "blockly/core";
import { getExtraBlockState } from "./serialization_helper";

/**
* Creates a minus image field used for mutation.
* @param {Object=} args Untyped args passed to block.minus when the field
* is clicked.
* @returns {Blockly.FieldImage} The minus field.
*/
export function createMinusField(args?: Object): Blockly.FieldImage {
const minus = new Blockly.FieldImage(minusImage, 15, 15, undefined, onClick_);
/**
* Untyped args passed to block.minus when the field is clicked.
* @type {?(Object|undefined)}
* @private
*/
(minus as any).args_ = args;
return minus;
}

/**
* Calls block.minus(args) when the minus field is clicked.
* @param {Blockly.FieldImage} minusField The field being clicked.
* @private
*/
function onClick_(minusField: Blockly.FieldImage) {
// TODO: This is a dupe of the mutator code, anyway to unify?
const block = minusField.getSourceBlock() as Blockly.BlockSvg;

if (block.isInFlyout) {
return;
}

Blockly.Events.setGroup(true);
const oldExtraState = getExtraBlockState(block);
(block as any).minus((minusField as any).args_);
const newExtraState = getExtraBlockState(block);

if (oldExtraState != newExtraState) {
Blockly.Events.fire(
new Blockly.Events.BlockChange(
block,
"mutation",
null,
oldExtraState,
newExtraState,
),
);
}
Blockly.Events.setGroup(false);
}

const minusImage =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw" +
"MC9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPS" +
"JNMTggMTFoLTEyYy0xLjEwNCAwLTIgLjg5Ni0yIDJzLjg5NiAyIDIgMmgxMmMxLjEwNCAw" +
"IDItLjg5NiAyLTJzLS44OTYtMi0yLTJ6IiBmaWxsPSJ3aGl0ZSIgLz48L3N2Zz4K";
69 changes: 69 additions & 0 deletions packages/app/src/blocks/fields/field_plus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/**
* @license
* Copyright 2020 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

/**
* @fileoverview A field for a plus button used for mutation.
*/

import * as Blockly from "blockly/core";
import { getExtraBlockState } from "./serialization_helper";

/**
* Creates a plus image field used for mutation.
* @param {Object=} args Untyped args passed to block.minus when the field
* is clicked.
* @returns {Blockly.FieldImage} The Plus field.
*/
export function createPlusField(args?: Object): Blockly.FieldImage {
const plus = new Blockly.FieldImage(plusImage, 15, 15, undefined, onClick_);
/**
* Untyped args passed to block.plus when the field is clicked.
* @type {?(Object|undefined)}
* @private
*/
(plus as any).args_ = args;
return plus;
}

/**
* Calls block.plus(args) when the plus field is clicked.
* @param {!Blockly.FieldImage} plusField The field being clicked.
* @private
*/
function onClick_(plusField: Blockly.FieldImage) {
// TODO: This is a dupe of the mutator code, anyway to unify?
const block = plusField.getSourceBlock() as Blockly.BlockSvg;

if (block.isInFlyout) {
return;
}

Blockly.Events.setGroup(true);
const oldExtraState = getExtraBlockState(block);
(block as any).plus((plusField as any).args_);
const newExtraState = getExtraBlockState(block);

if (oldExtraState != newExtraState) {
Blockly.Events.fire(
new Blockly.Events.BlockChange(
block,
"mutation",
null,
oldExtraState,
newExtraState,
),
);
}
Blockly.Events.setGroup(false);
}

const plusImage =
"data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC" +
"9zdmciIHZlcnNpb249IjEuMSIgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0Ij48cGF0aCBkPSJNMT" +
"ggMTBoLTR2LTRjMC0xLjEwNC0uODk2LTItMi0ycy0yIC44OTYtMiAybC4wNzEgNGgtNC4wNz" +
"FjLTEuMTA0IDAtMiAuODk2LTIgMnMuODk2IDIgMiAybDQuMDcxLS4wNzEtLjA3MSA0LjA3MW" +
"MwIDEuMTA0Ljg5NiAyIDIgMnMyLS44OTYgMi0ydi00LjA3MWw0IC4wNzFjMS4xMDQgMCAyLS" +
"44OTYgMi0ycy0uODk2LTItMi0yeiIgZmlsbD0id2hpdGUiIC8+PC9zdmc+Cg==";
27 changes: 27 additions & 0 deletions packages/app/src/blocks/fields/serialization_helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/**
* @license
* Copyright 2022 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import * as Blockly from "blockly/core";

/**
* Returns the extra state of the given block (either as XML or a JSO, depending
* on the block's definition).
* @param {!Blockly.BlockSvg} block The block to get the extra state of.
* @returns {string} A stringified version of the extra state of the given
* block.
*/
export function getExtraBlockState(block: Blockly.BlockSvg): string {
// TODO: This is a dupe of the BlockChange.getExtraBlockState code, do we
// want to make that public?
if (block.saveExtraState) {
const state = block.saveExtraState();
return state ? JSON.stringify(state) : "";
} else if (block.mutationToDom) {
const state = block.mutationToDom();
return state ? Blockly.Xml.domToText(state) : "";
}
return "";
}
22 changes: 22 additions & 0 deletions packages/app/src/blocks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import * as Blockly from "blockly/core";
import { BlockDefinition } from "blockly/core/blocks";

import { pythonDict } from "./python_dict";
import { definitions as nonebotBasic } from "./nonebot_basic";
import { definitions as nonebotAlconna } from "./nonebot_alconna";
import { definitions as nonebotStore } from "./nonebot_store";
import { definitions as nonebotScheduler } from "./nonebot_scheduler";
import { definitions as nonebotRequest } from "./nonebot_request";

// Array of all block definitions
let blockDefinitions: BlockDefinition[] = [];
blockDefinitions = blockDefinitions
.concat(pythonDict)
.concat(nonebotBasic)
.concat(nonebotAlconna)
.concat(nonebotStore)
.concat(nonebotScheduler)
.concat(nonebotRequest);

export const blocks =
Blockly.common.createBlockDefinitionsFromJsonArray(blockDefinitions);
Loading

0 comments on commit 36fb13b

Please sign in to comment.