# 拓展开发中的工具集
框架中提供了一些工具。
# 调试器...(感觉好鸡肋)
开发拓展总是需要调试的,每次都进入编辑器重载未免显得麻烦。因此框架提供了一个独立调试器,名为WaterBox。这是一个基于浏览器的调试器,因此需要浏览器支持。在WaterBox中,你可以尽情重载拓展的代码,调试积木逻辑,并且可以查看拓展的输出和积木返回值。
# WaterBox
# 启动
重建依赖库,在快速开始处以有,此处不再赘述。
在项目根目录下运行yarn project dev ui,即可启动WaterBox。
WaterBox会自动打开浏览器,并跳转到http://localhost:25565,如果浏览器没有自动打开,请手动打开。
网页下方会有一个方框用于显示拓展积木,上方有一个canvas用于预览舞台。点击重载拓展按钮可刷新数据库内容。
# 使用
- 点击积木的
Run按钮,可根据输入执行积木函数。 - 点击积木的
View按钮,可查看当前积木的opcode、blockType、arguments等信息。 - 点击积木的
Arg按钮,可查看当前积木输入的对应参数对象。
# 可能不太必要的
接下来提到的的所有命名空间均在@framework/tools可以找到。
# ⚠️️过时-全局上下文
此用法已过时,但暂时没有找到更合适的替代。
使用GlobalContext命名空间的createDataStore方法可以创建一个基于全局变量的数据库,将拓展的数据导出便于调试,也可以便于联动其他拓展。
方法的第一个参数传入一个字符串或拓展派生于Extension的类的原型,第二个参数传入一个对象,对象中的字段将会被导出到全局上下文。
let dataStore = GlobalContext.createDataStore(MyExtension, {
alertedSth: [] as string[], //类型断言,转换成string[],否则会被自动推断为never[]
lastSuffix: ""
});
2
3
4
现在修改积木的执行函数,将弹窗的内容添加到数据库中。
function alertSth(args) {
alert(args.$sth + " " + args._suffix);
//数据字段为数组时,能自动判断并且添加元素而不是直接覆盖
dataStore.write("alertedSth", args.$sth.toString());
dataStore.write("lastSuffix", args._suffix.toString());
}
2
3
4
5
6
现在打开浏览器的开发者工具,在控制台中输入_FSContext.EXPORTED.[拓展ID]即可查看数据库的内容。
# 与其他拓展联动
在任意时刻,都可以使用GlobalContext命名空间的getDataStore方法来获取其他拓展的已导出数据。
function alertSth(args) {
alert(args.$sth + " " + args._suffix);
dataStore.write("alertedSth", args.$sth.toString());
dataStore.write("lastSuffix", args._suffix.toString());
//------------------------------------------↓↓↓其他拓展的数据库的类型声明↓↓↓
let otherDataStore = GlobalContext.getDataStore<{ lastSuffix: string }>("其他拓展ID");
dataStore.write("alertedSth", otherDataStore.read("alertedSth")[0]); //将其他拓展的数据写入自己的数据库
otherDataStore.write("lastSuffix", args._suffix.toString()); //将自己的数据写入其他拓展的数据库
}
2
3
4
5
6
7
8
9
# 链式生成组件树
使用DOM命名空间的elementTree方法可链式生成组件树,该方法返回一个ElementContext接口的实现,该对象具有child方法用于添加子组件,class方法用于添加类名,attribute方法用于添加属性,style方法用于添加样式。
import { DOM } from "@framework/tools";
let tree = DOM.elementTree("div", [
//子元素
DOM.elementTree("span")
.attribute("innerText", "hello")
.style("color", "orange"),
DOM.elementTree("span")
.attribute("innerText", "test")
.style("color", "green")
])
.class("my-class", "another-class", "anymore---")
.attribute("data-attr", "value")
.style("color", "red");
2
3
4
5
6
7
8
9
10
11
12
13
这棵树最终会被渲染成:
<div class="my-class another-class anymore---" data-attr="value" style="color: red;">
<span style="color: orange;">hello</span>
<span style="color: green;">test</span>
</div>
2
3
4
# 舞台叠加层
在舞台上创建一个叠加层元素,用于放置一些界面。使用DOM.createStageOverlay方法并传入一个拓展的实例即可。会返回创建好的叠加层元素,可在第二个参数指定一个具体HTML元素的节点名称。
import { DOM } from "@framework/tools";
const span = DOM.createStageOverlay(extension, "span"); //HTMLSpanElement
span.innerText = "福瑞占领世界!";
2
3
# 生成随机数、颜色、字符串等
使用Random命名空间的方法生成随机内容,用法无需多言。
import { Random } from "@framework/tools";
let randomInt = Random.integer(0, 100);
let randomFloat = Random.float(0, 10.0);
let randomColor = Random.color();
let randomString = Random.string(10, "abcdefABCDEF123456");
2
3
4
5
# ⚠️过时-处理积木文字
# ⚠️-切割得到参数框
此用法已过时,请改用下文TextParser
使用LegacyParser.splitArgBoxPart方法,切割传入积木文字,得到每个参数框按照对应位置排序的名称数组。
import { LegacyParser } from "@framework/tools";
let args = LegacyParser.splitArgBoxPart("alert $sth $sth $sth to window with _suffix", ["$sth", "_suffix"]);
console.log(args); //输出["$sth", "$sth", "$sth", "_suffix"]
2
3
# ⚠️-切割得到参数框外的文字
此用法已过时,请改用下文TextParser
使用LegacyParser.splitTextPart方法,切割传入积木文字,得到参数框外的文字数组。
import { LegacyParser } from "@framework/tools";
let args = LegacyParser.splitTextPart("alert $sth $sth $sth to window with _suffix", ["$sth", "_suffix"]);
console.log(args); //输出["alert ", " to window with ", ""]
2
3
# 处理颜色
# HEX转RGB数组
使用Color.hexToRgb方法,将传入的HEX颜色值转换成RGB数组。无需多言。
import { Color } from "@framework/tools";
let rgb = Color.hexToRGB("#FF00FF");
console.log(rgb); //输出[255, 0, 255]
2
3
# 使颜色加深(混入黑色)
使用Color.darken方法来加深颜色,即降低HSL亮度。首先传入一个HEX颜色,再传入加深的比例。
import { Color } from "@framework/tools";
let hex = Color.darken("#FF00FF", 0.5);
console.log(hex); //输出"#800080"
2
3
# 使颜色变浅(混入白色)
使用Color.lighten方法来变浅颜色,即提高HSL亮度。首先传入一个HEX颜色,再传入变浅的比例。
import { Color } from "@framework/tools";
let hex = Color.lighten("#FF00FF", 0.5);
console.log(hex); //输出"#FF80FF"
2
3
# 定义的积木参数输入类型投射到TW类型
使用Cast.castInputType方法,将传入的积木参数类型投射到TW类型。
import { Cast } from "@framework/tools";
let type = Cast.castInputType("string");
console.log(type); //输出"string"
let type = Cast.castInputType("bool");
console.log(type); //输出"Boolean"
let type = Cast.castInputType("hat-parameter");
console.log(type); //输出"ccw_hat_parameter"
2
3
4
5
6
7
# 一些新写法的解析器
# 菜单解析器
MenuParser命名空间提供了一个用来解析新写法菜单的工具集。新菜单的写法请前往快速开始查看。
解析器的核心方法为normalize,可将新写法自动解析为完全机器可读的对象。只需传入按照新写法编写的菜单项列表即可。
TS
import { MenuParser } from "@framework/tools";
MenuParser.normalize("苹果=apple,梨子=pear,香蕉=banana");
//返回↓
/*
[
{
name: "苹果",
value: "apple"
},
{
name: "梨子",
value: "pear"
},
{
name: "香蕉",
value: "banana"
}
]
*/
MenuParser.normalize("苹果,梨子,香蕉");
//返回↓
/*
[
{
name: "苹果",
value: "苹果"
},
{
name: "梨子",
value: "梨子"
},
{
name: "香蕉",
value: "香蕉"
}
]
*/
MenuParser.normalize([
"苹果",
"梨子=pear",
{
name: "香蕉",
value: "banana"
}
]);
//返回↓
/*
[
{
name: "苹果",
value: "苹果"
},
{
name: "梨子",
value: "pear"
},
{
name: "香蕉",
value: "banana"
}
]
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
使用trimSpace可去除内容两边空格,会自动判断是否传入了字符串,若不是字符串则无需去除,直接返回自身。
TS
import { MenuParser } from "@framework/tools";
MenuParser.trimSpace(" 苹果 "); //"苹果"
MenuParser.trimSpace(" 梨子 = pear "); //"梨子 = pear"
MenuParser.trimSpace({
name: "香蕉",
value: "banana"
});
/*
{
name: "香蕉",
value: "banana"
}
*/
2
3
4
5
6
7
8
9
10
11
12
13
使用trimSpaceMenuItem可去除可读对象两边的空格,只会返回name和value两个字段。
TS
import { MenuParser } from "@framework/tools";
MenuParser.trimSpaceMenuItem({
name: " 香蕉 ",
value: " banana "
});
/*
{
name: "香蕉",
value: "banana"
}
*/
2
3
4
5
6
7
8
9
10
11
使用parseKeyValue来解析一个使用等于号连接的键值对字符串。会自动去除两头空格。
TS
import { MenuParser } from "@framework/tools";
MenuParser.parseKeyValue("苹果=apple");
/*
{
name: "苹果",
value: "apple"
}
*/
MenuParser.parseKeyValue("梨子 = pear");
/*
{
name: "梨子",
value: "pear"
}
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
使用splitStringArray来切割字符串数组,会自动去除两头空格。
TS
import { MenuParser } from "@framework/tools";
MenuParser.splitStringArray("苹果,梨子,香蕉");
/*
[
"苹果",
"梨子",
"香蕉"
]
*/
2
3
4
5
6
7
8
9
使用isStringArray和isKeyValueString来分别判断传入的参数是否为字符串数组或键值对字符串。
TS
import { MenuParser } from "@framework/tools";
MenuParser.isStringArray("苹果,梨子,香蕉"); //true
MenuParser.isStringArray("苹果梨子香蕉"); //false
MenuParser.isKeyValueString("苹果=apple,梨子=pear,香蕉=banana"); //false,已经是字符串数组了
MenuParser.isKeyValueString("苹果=apple"); //true
2
3
4
5
# 积木文字解析器
TextParser命名空间提供了一个用来解析新写法(实验性)积木文字的工具集。新积木文字的写法请前往快速开始查看。
解析器的核心方法为parsePart,可将新写法自动解析为完全机器可读的对象。只需传入按照新写法编写的积木文字的字符串即可。
TS
import { TextParser } from "@framework/tools";
TextParser.parsePart("text1 [argA] text2");
//返回↓
/*
[
ArgumentPart("text1", "text"),
ArgumentPart("argA", "input", "", "string"),
ArgumentPart("text2", "text")
]
*/
TextParser.parsePart("text1 [argA:bool] text2");
//返回↓
/*
[
ArgumentPart("text1", "text"),
ArgumentPart("argA", "input", false, "bool"),
ArgumentPart("text2", "text")
]
*/
TextParser.parsePart("text1 [argA=福瑞占领世界] text2");
//返回↓
/*
[
ArgumentPart("text1", "text"),
ArgumentPart("argA", "input", "福瑞占领世界", "string"),
ArgumentPart("text2", "text")
]
*/
TextParser.parsePart("text1 [argA:number=114514] text2");
//返回↓
/*
[
ArgumentPart("text1", "text"),
ArgumentPart("argA", "input", 114514, "number"),
ArgumentPart("text2", "text")
]
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
使用split方法来切割文字和参数框部分,会保留空字符串,用于分别处理后合并。不会自动解析对应的参数框部分,需要手动调用解析方法或进行一些其他处理。
返回值有两个字段:text和arg,分别为文字部分和参数框部分。
TS
import { TextParser } from "@framework/tools";
TextParser.split("text1 [argA] text2 [argB:bool=true]");
//返回↓
/*
{
text: ["text1 ", " text2"],
arg: ["argA", "argB:bool=true"]
}
*/
2
3
4
5
6
7
8
9
以parse为名称前缀的方法均为解析参数属性的函数。
| 名称 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| parseName | 字符串 | 字符串 | 名称 |
| parseType | 字符串 | 字符串 | 输入类型 |
| parseDefaultValue | 字符串 | 字符串 | 默认值 |
TS
import { TextParser } from "@framework/tools";
const arg = "argA:bool=true";
TextParser.parseName(arg); //"argA"
TextParser.parseType(arg); //"bool"
TextParser.parseDefaultValue(arg); //Boolean(true)
2
3
4
5
以has为名称前缀的方法同理,不过参数必须有名称,所以也就不存在hasName这一说了。
| 名称 | 参数 | 返回值 | 说明 |
|---|---|---|---|
| hasType | 字符串 | 布尔值 | 是否有类型 |
| hasDefaultValue | 字符串 | 布尔值 | 是否有默认值 |
TS
import { TextParser } from "@framework/tools";
const arg = "argA:bool=true";
TextParser.hasType(arg); //true
TextParser.hasDefaultValue(arg); //true
const arg2 = "argB";
TextParser.hasType(arg2); //false
TextParser.hasDefaultValue(arg2); //false
const arg3 = "argC:bool";
TextParser.hasType(arg3); //true
TextParser.hasDefaultValue(arg3); //false
2
3
4
5
6
7
8
9
10
若需要动态调用判断方法,为了防止出现报错,尝试调用TextParser.hasName时不会出现报错,但不管传入什么参数,都会永远返回true。
TS
import { TextParser } from "@framework/tools";
TextParser.hasName("argA:bool=true"); //true
TextParser.hasName("argB"); //true
TextParser.hasName("argC:bool"); //true
2
3
4