Skip to content

Commit

Permalink
added menu parser in toolts
Browse files Browse the repository at this point in the history
  • Loading branch information
fs-context committed Nov 30, 2024
1 parent a7e7269 commit cdbf163
Show file tree
Hide file tree
Showing 11 changed files with 430 additions and 96 deletions.
2 changes: 1 addition & 1 deletion config/loader.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
type LoaderConfig = import("@framework/internal").LoaderConfig;
const config: LoaderConfig = {
target: import("@samples/fs-iframe/extension"),
target: import("@samples/falling-anchors/extension"),
errorCatches: [],
platform: ["TurboWarp"]
}
Expand Down
12 changes: 4 additions & 8 deletions src/fs-context/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,9 @@ if (!window._FSContext) {
};
};
export namespace Extensions {
const inputTypeCastToScratch: any = {
bool: "Boolean",
"hat-paramater": "ccw_hat_parameter"
};
async function generateConstructor(extension: new () => import("./structs").Extension): Promise<any> {
const { Version, Menu } = await import("./structs");
const { Unnecessary } = await import("./tools");
var ext = new extension();
var context = getFSContext();
function ExtensionConstructor(this: any, runtime: Scratch) {
Expand Down Expand Up @@ -50,7 +47,7 @@ export namespace Extensions {
for (let arg of block.arguments) {
if (arg.type === "input") {
let currentArg: any = {
type: Object.hasOwn(inputTypeCastToScratch, arg.inputType) ? inputTypeCastToScratch[arg.inputType] : arg.inputType,
type: Unnecessary.castInputType(arg.inputType),
}
if (arg.inputType === "menu") {
currentArg.menu = arg.value;
Expand All @@ -70,7 +67,7 @@ export namespace Extensions {
items: menu.items.map((item) => {
return {
text: item.name,
value: item.value ? item.value : item.name
value: item.value
};
})
};
Expand All @@ -94,7 +91,7 @@ export namespace Extensions {
loader: loaderConfig
}
export function isInWaterBoxed() {
return !!window.ScratchWaterBoxed;
return window.ScratchWaterBoxed !== undefined;
}
export function getScratch(): Scratch | ScratchWaterBoxed | null {
if (window.ScratchWaterBoxed) return window.ScratchWaterBoxed;
Expand All @@ -114,7 +111,6 @@ export namespace Extensions {
objectPlain,
objectGenerated,
to(...platforms: PlatformSupported[]) {
console.log(platforms);
for (let platform of platforms) {
console.log(`Trying to load FSExtension "${objectPlain.id}" on platform "${platform}"...`);
if (platform === "TurboWarp") {
Expand Down
35 changes: 27 additions & 8 deletions src/fs-context/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export interface ColorDefine {
}
export interface MenuItem {
name: string;
value?: any;
value: any;
}
export type InputTypeCast = {
string: string;
Expand All @@ -84,15 +84,30 @@ export interface GlobalResourceMachine {
export interface ScratchTranslateFunction extends Function {
language: LanguageSupported;
}
export interface StyleSetFunc<E extends HTMLElement> {
<K extends keyof FilterWritableKeys<CSSStyleDeclaration>>
(key: K, value: CSSStyleDeclaration[K]): ElementContext<E>;
<K extends keyof FilterWritableKeys<CSSStyleDeclaration>>
(key: K): CSSStyleDeclaration[K];
};
export interface AttributeSetFunc<E extends HTMLElement> {
<K extends keyof FilterWritableKeys<E>>
(key: K, value: E[K]): ElementContext<E>;
<K extends keyof FilterWritableKeys<E>>
(key: K): E[K];
}
export interface DataSetFunc<E extends HTMLElement> {
(key: string, value: any): ElementContext<E>;
(key: string): any;
}
export interface ElementContext<T extends HTMLElement = any> {
result: T;
datas: ObjectInclude<any>;
child: (target: ElementContext) => ElementContext<T>;
store: ObjectInclude<any>;
child: (target: ElementContext | HTMLElement) => ElementContext<T>;
class: (...classes: string[]) => ElementContext<T>;
attribute: <K extends keyof FilterWritableKeys<T>>(key: K, value: T[K]) => ElementContext<T>;
style: <K extends keyof FilterWritableKeys<CSSStyleDeclaration>>(key: K, value: CSSStyleDeclaration[K]) => ElementContext<T>;
data: (key: string, value: any) => ElementContext<T>;
readData: (key: string) => any;
attribute: AttributeSetFunc<T>;
style: StyleSetFunc<T>;
data: DataSetFunc<T>;
}
export type WritableKeys<T> = {
[K in keyof T]: If<
Expand Down Expand Up @@ -121,4 +136,8 @@ export interface LoaderConfig {
target: Promise<{ default: new () => Extension }>;
errorCatches: string[];
platform: PlatformSupported[];
}
}
export type KeyValueString<T extends string = "="> = `${string}${T}${string}`;
export type CopyAsGenericsOfArray<E> = E | E[];
export type MenuDefine = CopyAsGenericsOfArray<string | KeyValueString | MenuItem | StringArray>;
export type StringArray = KeyValueString<",">;
207 changes: 207 additions & 0 deletions src/fs-context/samples/falling-anchors/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { ElementContext, Scratch } from "@framework/internal";
import { Block, Extension, Menu, Translator } from "@framework/structs";
import { GlobalContext, MenuParser, Unnecessary } from "@framework/tools";
import "./style.css";
let translator = Translator.create("zh-cn", {
name: "沉沦之锚",
description: "使用锚点机制构建可自适应的精美UI。可与「高级数据结构」联动使用。",
clear: "清除所有组件",
create: "创建一个$tag组件,命名为$name,锚定点$anchor%,着床点$bed%,大小$size",
render: "渲染$name到舞台",
append: "将$name添加为$parent的子组件",
setAttr: "设置$name的$attr为$value"
});
export default class FallingAnchors extends Extension {
id: string = "falling-anchors";
displayName: string = translator.load("name");
description: string = translator.load("description");
allowSandboxed: boolean = false;
blocks: Block[] = [
Block.create(translator.load("clear"), {}, function clearAll() { dataStore.clear("componets"); }),
Block.create(translator.load("create"), {
arguments: [
{
name: "$tag",
inputType: "menu",
value: new Menu("tags", [
{
name: "方框",
value: "div"
},
{
name: "图片",
value: "img"
},
{
name: "文本",
value: "span"
}
])
},
{
name: "$name"
},
{
name: "$anchor",
value: "11 45"
},
{
name: "$bed",
value: "14 19"
},
{
name: "$size",
value: "10 10"
}
]
}, function createComponent(arg) {
if (findComponent(arg.$name)) { return; };
dataStore.write("componets", {
name: arg.$name,
anchor: parseVector(arg.$anchor),
bed: parseVector(arg.$bed),
size: arg.$size === "auto" ? "auto" : parseVector(arg.$size),
element: Unnecessary.elementTree(arg.$tag).class("fsa"),
tag: arg.$tag,
isInStage: false,
childs: [],
updateStyle() {
this.element
.style("left", `${this.bed.horizontal}%`)
.style("top", `${this.bed.vertical}%`)
.style("transform", `translate(${this.anchor.horizontal}%, ${this.anchor.vertical}%)`)
if (this.size === "auto") {
this.element.style("width", "auto").style("height", "auto");
} else {
this.element.style("width", `${this.size.horizontal}%`).style("height", `${this.size.vertical}%`);
}
this.childs.forEach(child => child.updateStyle());
},
currentRenderingTree: null
});
}),
Block.create(translator.load("render"), {
arguments: [
{
name: "$name"
}
]
}, function renderComponent(arg) {
let component = findComponent(arg.$name);
if (!component) { return; };
if (component.isInStage && component.currentRenderingTree) {
dataStore.read("rootBase").result.removeChild(component.currentRenderingTree);
};
component.updateStyle();
let tree = generateDomTree(component);
dataStore.read("rootBase").child(tree);
component.currentRenderingTree = tree;
component.isInStage = true;
}),
Block.create(translator.load("append"), {
arguments: [
{
name: "$name"
},
{
name: "$parent"
}
]
}, function appendComponent(arg) {
let component = findComponent(arg.$name);
let parent = findComponent(arg.$parent);
if (!component || !parent || parent.childs.includes(component)) { return; };
parent.childs.push(component);
component.isInStage = false;
}),
Block.create(translator.load("setAttr"), {
arguments: [
{
name: "$name"
},
{
name: "$attr",
inputType: "menu",
value: new Menu("acceptedAttrs", [
{
name: "背景色",
value: "backgroundColor"
},
{
name: "边框色",
value: "borderColor"
},
{
name: "边框宽度",
value: "borderWidth"
}
])
},
{
name: "$value"
}
]
}, function setAttr(arg) {
let component = findComponent(arg.$name);
if (!component) { return; };
component.element.style(arg.$attr, arg.$value);
})
];
menus = [
new Menu("exampleA", "abc,defg,gh,i"),
new Menu("exampleB", " abc = ABCaaabcc, defg, gh =GH,i= I"),
new Menu("exampleC", [
"abcdefg ",
" hijklmnop",
{
name: " qwer",
value: " QWER"
},
{
name: "asdf ",
value: "ASDF"
},
"zxcv= APple"
])
];
init(runtime: Scratch) {
runtime.renderer.canvas.parentElement?.appendChild(dataStore.read("rootBase").result);
}
};
function parseVector(target: `${number} ${number}`): Vector {
if (typeof target !== "string") { return createZeroVector(); };
let splited = target.split(" ");
if (splited.length !== 2) { return createZeroVector(); };
return { horizontal: Number(splited[0]), vertical: Number(splited[1]) };
};
function createZeroVector(): Vector {
return { horizontal: 0, vertical: 0 };
};
function findComponent(name: string): Componet | null {
return dataStore.read("componets").find(component => component.name === name) || null;
};
function generateDomTree(target: Componet): HTMLElement {
let result = target.element.result.cloneNode() as HTMLElement;
target.childs.forEach(child => result.appendChild(generateDomTree(child)));
return result;
};
interface Vector {
horizontal: number;
vertical: number;
};
interface Componet {
name: string;
anchor: Vector;
bed: Vector;
size: Vector | "auto";
element: ElementContext<HTMLElement>;
tag: keyof HTMLElementTagNameMap;
isInStage: boolean;
childs: Componet[];
updateStyle: () => void;
currentRenderingTree: HTMLElement | null;
};
let dataStore = GlobalContext.createDataStore(FallingAnchors, {
componets: [] as Componet[],
rootBase: Unnecessary.elementTree("div").class("fsa", "base")
});
11 changes: 11 additions & 0 deletions src/fs-context/samples/falling-anchors/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
.fsa {
position: absolute;
left: 0%;
top: 0%;
transform: translate(0%, 0%);
}

.fsa.base {
width: 100%;
height: 100%;
}
4 changes: 2 additions & 2 deletions src/fs-context/samples/fs-iframe/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export default class FSIFrame extends Extension {
console.log(this);
if (this.canvas) {
iframe
.style("width", `${this.canvas.clientWidth * iframe.readData("ratio-x")}px`)
.style("height", `${this.canvas.clientHeight * iframe.readData("ratio-y")}px`);
.style("width", `${this.canvas.clientWidth * iframe.data("ratio-x")}px`)
.style("height", `${this.canvas.clientHeight * iframe.data("ratio-y")}px`);
}
}, 100);
}),
Expand Down
17 changes: 7 additions & 10 deletions src/fs-context/structs.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Extensions } from ".";
import { ArgumentDefine, ArgumentPart, BlockType, ColorDefine, MethodFunction, MenuItem, TranslatorStoredData, LanguageSupported, LanguageStored, BlockConfigB, ExtractField, Scratch, ObjectInclude, VersionString } from "./internal";
import { ArgumentDefine, ArgumentPart, BlockType, ColorDefine, MethodFunction, MenuItem, TranslatorStoredData, LanguageSupported, LanguageStored, BlockConfigB, ExtractField, Scratch, ObjectInclude, VersionString, KeyValueString } from "./internal";
import md5 from "md5";
import { Unnecessary } from "./tools";
import { MenuParser, Unnecessary } from "./tools";
export class Extension {
id: string = "example-extension";
displayName: string = "Example extension";
Expand All @@ -25,7 +25,7 @@ export class Extension {
this.colors.inputer = Unnecessary.darken(this.colors.theme, 0.15);
this.colors.menu = Unnecessary.darken(this.colors.theme, 0.3);
} else {
throw new Error(`FSExtension "${this.id}" can auto derive this.colors but have no theme color.`);
throw new Error(`FSExtension "${this.id}" can auto derive colors but have no theme color.`);
}
}
return this.colors;
Expand Down Expand Up @@ -53,13 +53,10 @@ export class Block {
}
static create<T extends BlockConfigB<ArgumentDefine[]>>(
text: string,
config?: T,
config: T,
method?: (this: Extension, arg: T extends BlockConfigB<infer R> ? ExtractField<R> : never) => any
) {
let realConfig: BlockConfigB<ArgumentDefine[]> = config || {
arguments: [],
type: "command"
};
let realConfig: BlockConfigB<ArgumentDefine[]> = { arguments: config.arguments || [], ...config };
let _arguments = realConfig.arguments as ArgumentDefine[];
let realMethod = method || (() => { }) as any;
let textLoaded: (string | ArgumentDefine)[] = [];
Expand Down Expand Up @@ -113,10 +110,10 @@ export class Menu {
acceptReporters: boolean = true;
items: MenuItem[] = [];
name: string;
constructor(name: string, items?: MenuItem[], acceptReporters?: boolean) {
constructor(name: string, items?: (MenuItem | string | KeyValueString)[] | string, acceptReporters?: boolean) {
this.name = name;
acceptReporters && (this.acceptReporters = acceptReporters);
items && (this.items = items);
items && (this.items = MenuParser.normalize(items));
}
}
export class Translator<L extends LanguageSupported, D extends LanguageStored> {
Expand Down
Loading

0 comments on commit cdbf163

Please sign in to comment.