[新增]wxmlToCanvas

This commit is contained in:
唐明明
2020-12-30 10:17:56 +08:00
parent 3264f8fe95
commit 3e59b16c27
53 changed files with 6234 additions and 1 deletions

9
node_modules/widget-ui/babel.config.js generated vendored Normal file
View File

@@ -0,0 +1,9 @@
module.exports = {
presets: [
["@babel/preset-env", {
targets: {
node: "current"
}
}]
]
};

40
node_modules/widget-ui/dist/element.d.ts generated vendored Normal file
View File

@@ -0,0 +1,40 @@
declare type LayoutData = {
left: number;
top: number;
width: number;
height: number;
};
declare type LayoutNode = {
id: number;
style: Object;
children: LayoutNode[];
layout?: LayoutData;
};
declare class Element {
static uuid(): number;
parent: Element | null;
id: number;
style: {
[key: string]: any;
};
computedStyle: {
[key: string]: any;
};
lastComputedStyle: {
[key: string]: any;
};
children: {
[key: string]: Element;
};
layoutBox: LayoutData;
constructor(style?: {
[key: string]: any;
});
getAbsolutePosition(element: Element): any;
add(element: Element): void;
remove(element?: Element): void;
getNodeTree(): LayoutNode;
applyLayout(layoutNode: LayoutNode): void;
layout(): void;
}
export default Element;

5
node_modules/widget-ui/dist/event.d.ts generated vendored Normal file
View File

@@ -0,0 +1,5 @@
export default class EventEmitter {
emit(event: string, data?: any): void;
on(event: string, callback: any): void;
off(event: string, callback: any): void;
}

1
node_modules/widget-ui/dist/index.js generated vendored Normal file

File diff suppressed because one or more lines are too long

36
node_modules/widget-ui/dist/style.d.ts generated vendored Normal file
View File

@@ -0,0 +1,36 @@
declare const textStyles: string[];
declare const scalableStyles: string[];
declare const layoutAffectedStyles: string[];
declare const getDefaultStyle: () => {
left: undefined;
top: undefined;
right: undefined;
bottom: undefined;
width: undefined;
height: undefined;
maxWidth: undefined;
maxHeight: undefined;
minWidth: undefined;
minHeight: undefined;
margin: undefined;
marginLeft: undefined;
marginRight: undefined;
marginTop: undefined;
marginBottom: undefined;
padding: undefined;
paddingLeft: undefined;
paddingRight: undefined;
paddingTop: undefined;
paddingBottom: undefined;
borderWidth: undefined;
flexDirection: undefined;
justifyContent: undefined;
alignItems: undefined;
alignSelf: undefined;
flex: undefined;
flexWrap: undefined;
position: undefined;
hidden: boolean;
scale: number;
};
export { getDefaultStyle, scalableStyles, textStyles, layoutAffectedStyles };

6
node_modules/widget-ui/jest.config.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
module.exports = {
transform: {
"^.+\\.js$": "babel-jest",
"^.+\\.ts$": "ts-jest"
}
};

52
node_modules/widget-ui/package.json generated vendored Normal file
View File

@@ -0,0 +1,52 @@
{
"_from": "widget-ui@^1.0.2",
"_id": "widget-ui@1.0.2",
"_inBundle": false,
"_integrity": "sha512-gDXosr5mflJdMA1weU1A47aTsTFfMJhfA4EKgO5XFebY3eVklf80KD4GODfrjo8J2WQ+9YjL1Rd9UUmKIzhShw==",
"_location": "/widget-ui",
"_phantomChildren": {},
"_requested": {
"type": "range",
"registry": true,
"raw": "widget-ui@^1.0.2",
"name": "widget-ui",
"escapedName": "widget-ui",
"rawSpec": "^1.0.2",
"saveSpec": null,
"fetchSpec": "^1.0.2"
},
"_requiredBy": [
"/wxml-to-canvas"
],
"_resolved": "https://registry.npmjs.org/widget-ui/-/widget-ui-1.0.2.tgz",
"_shasum": "d65a560b91739fbd0ea7c2f5f2d28fe4c0132470",
"_spec": "widget-ui@^1.0.2",
"_where": "/Users/WebTmm/Desktop/AGuestSaas/node_modules/wxml-to-canvas",
"author": "",
"bundleDependencies": false,
"dependencies": {
"eventemitter3": "^4.0.0"
},
"deprecated": false,
"description": "",
"devDependencies": {
"@babel/preset-env": "^7.6.3",
"@babel/preset-typescript": "^7.6.0",
"@types/jest": "^24.0.18",
"babel-jest": "^24.9.0",
"jest": "^24.9.0",
"ts-jest": "^24.1.0",
"ts-loader": "^6.2.0",
"typescript": "^3.6.4",
"webpack": "^4.41.1",
"webpack-cli": "^3.3.9"
},
"license": "ISC",
"main": "dist/index.js",
"name": "widget-ui",
"scripts": {
"build": "webpack",
"test": "jest"
},
"version": "1.0.2"
}

1186
node_modules/widget-ui/src/css-layout.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

172
node_modules/widget-ui/src/element.ts generated vendored Normal file
View File

@@ -0,0 +1,172 @@
import computeLayout from "./css-layout";
import { getDefaultStyle, scalableStyles, layoutAffectedStyles } from "./style";
type LayoutData = {
left: number,
top: number,
width: number,
height: number
};
type LayoutNode = {
id: number,
style: Object,
children: LayoutNode[],
layout?: LayoutData
};
let uuid = 0;
class Element {
public static uuid(): number {
return uuid++;
}
public parent: Element | null = null;
public id: number = Element.uuid();
public style: { [key: string]: any } = {};
public computedStyle: { [key: string]: any } = {};
public lastComputedStyle: { [key: string]: any } = {};
public children: { [key: string]: Element } = {};
public layoutBox: LayoutData = { left: 0, top: 0, width: 0, height: 0 };
constructor(style: { [key: string]: any } = {}) {
// 拷贝一份,防止被外部逻辑修改
style = Object.assign(getDefaultStyle(), style);
this.computedStyle = Object.assign(getDefaultStyle(), style);
this.lastComputedStyle = Object.assign(getDefaultStyle(), style);
Object.keys(style).forEach(key => {
Object.defineProperty(this.style, key, {
configurable: true,
enumerable: true,
get: () => style[key],
set: (value: any) => {
if (value === style[key] || value === undefined) {
return;
}
this.lastComputedStyle = this.computedStyle[key]
style[key] = value
this.computedStyle[key] = value
// 如果设置的是一个可缩放的属性, 计算自己
if (scalableStyles.includes(key) && this.style.scale) {
this.computedStyle[key] = value * this.style.scale
}
// 如果设置的是 scale, 则把所有可缩放的属性计算
if (key === "scale") {
scalableStyles.forEach(prop => {
if (style[prop]) {
this.computedStyle[prop] = style[prop] * value
}
})
}
if (key === "hidden") {
if (value) {
layoutAffectedStyles.forEach((key: string) => {
this.computedStyle[key] = 0;
});
} else {
layoutAffectedStyles.forEach((key: string) => {
this.computedStyle[key] = this.lastComputedStyle[key];
});
}
}
}
})
})
if (this.style.scale) {
scalableStyles.forEach((key: string) => {
if (this.style[key]) {
const computedValue = this.style[key] * this.style.scale;
this.computedStyle[key] = computedValue;
}
});
}
if (style.hidden) {
layoutAffectedStyles.forEach((key: string) => {
this.computedStyle[key] = 0;
});
}
}
getAbsolutePosition(element: Element) {
if (!element) {
return this.getAbsolutePosition(this)
}
if (!element.parent) {
return {
left: 0,
top: 0
}
}
const {left, top} = this.getAbsolutePosition(element.parent)
return {
left: left + element.layoutBox.left,
top: top + element.layoutBox.top
}
}
public add(element: Element) {
element.parent = this;
this.children[element.id] = element;
}
public remove(element?: Element) {
// 删除自己
if (!element) {
Object.keys(this.children).forEach(id => {
const child = this.children[id]
child.remove()
delete this.children[id]
})
} else if (this.children[element.id]) {
// 是自己的子节点才删除
element.remove()
delete this.children[element.id];
}
}
public getNodeTree(): LayoutNode {
return {
id: this.id,
style: this.computedStyle,
children: Object.keys(this.children).map((id: string) => {
const child = this.children[id];
return child.getNodeTree();
})
}
}
public applyLayout(layoutNode: LayoutNode) {
["left", "top", "width", "height"].forEach((key: string) => {
if (layoutNode.layout && typeof layoutNode.layout[key] === "number") {
this.layoutBox[key] = layoutNode.layout[key];
if (this.parent && (key === "left" || key === "top")) {
this.layoutBox[key] += this.parent.layoutBox[key];
}
}
});
layoutNode.children.forEach((child: LayoutNode) => {
this.children[child.id].applyLayout(child);
});
}
layout() {
const nodeTree = this.getNodeTree();
computeLayout(nodeTree);
this.applyLayout(nodeTree);
}
}
export default Element;

15
node_modules/widget-ui/src/event.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
import _EventEmitter from "eventemitter3";
const emitter = new _EventEmitter();
export default class EventEmitter {
public emit(event: string, data?: any) {
emitter.emit(event, data);
}
public on(event: string, callback) {
emitter.on(event, callback);
}
public off(event: string, callback) {
emitter.off(event, callback);
}
}

87
node_modules/widget-ui/src/style.ts generated vendored Normal file
View File

@@ -0,0 +1,87 @@
const textStyles: string[] = ["color", "fontSize", "textAlign", "fontWeight", "lineHeight", "lineBreak"];
const scalableStyles: string[] = ["left", "top", "right", "bottom", "width", "height",
"margin", "marginLeft", "marginRight", "marginTop", "marginBottom",
"padding", "paddingLeft", "paddingRight", "paddingTop", "paddingBottom",
"borderWidth", "borderLeftWidth", "borderRightWidth", "borderTopWidth", "borderBottomWidth"];
const layoutAffectedStyles: string[] = [
"margin", "marginTop", "marginBottom", "marginLeft", "marginRight",
"padding", "paddingTop", "paddingBottom", "paddingLeft", "paddingRight",
"width", "height"];
type Style = {
left: number,
top: number,
right: number,
bottom: number,
width: number,
height: number,
maxWidth: number,
maxHeight: number,
minWidth: number,
minHeight: number,
margin: number,
marginLeft: number,
marginRight: number,
marginTop: number,
marginBottom: number,
padding: number,
paddingLeft: number,
paddingRight: number,
paddingTop: number,
paddingBottom: number,
borderWidth: number,
borderLeftWidth: number,
borderRightWidth: number,
borderTopWidth: number,
borderBottomWidth: number,
flexDirection: "column" | "row",
justifyContent: "flex-start" | "center" | "flex-end" | "space-between" | "space-around",
alignItems: "flex-start" | "center" | "flex-end" | "stretch",
alignSelf: "flex-start" | "center" | "flex-end" | "stretch",
flex: number,
flexWrap: "wrap" | "nowrap",
position: "relative" | "absolute",
hidden: boolean,
scale: number
}
const getDefaultStyle = () => ({
left: undefined,
top: undefined,
right: undefined,
bottom: undefined,
width: undefined,
height: undefined,
maxWidth: undefined,
maxHeight: undefined,
minWidth: undefined,
minHeight: undefined,
margin: undefined,
marginLeft: undefined,
marginRight: undefined,
marginTop: undefined,
marginBottom: undefined,
padding: undefined,
paddingLeft: undefined,
paddingRight: undefined,
paddingTop: undefined,
paddingBottom: undefined,
borderWidth: undefined,
flexDirection: undefined,
justifyContent: undefined,
alignItems: undefined,
alignSelf: undefined,
flex: undefined,
flexWrap: undefined,
position: undefined,
hidden: false,
scale: 1
})
export {
getDefaultStyle, scalableStyles, textStyles, layoutAffectedStyles
}

183
node_modules/widget-ui/test/css-layout.test.ts generated vendored Normal file
View File

@@ -0,0 +1,183 @@
import Element from "../src/element";
test("layout", () => {
const container = new Element({
width: 100,
height: 100,
padding: 10,
borderWidth: 2
})
const div1 = new Element({
left: 5,
top: 5,
width: 14,
height: 14
})
container.add(div1);
container.layout();
// css-layout 是 border-box
expect(container.layoutBox.left).toBe(0);
expect(container.layoutBox.top).toBe(0);
expect(container.layoutBox.width).toBe(100);
expect(container.layoutBox.height).toBe(100);
expect(div1.layoutBox.left).toBe(10 + 2 + 5);
expect(div1.layoutBox.top).toBe(10 + 2 + 5);
expect(div1.layoutBox.width).toBe(14);
expect(div1.layoutBox.height).toBe(14);
});
test("overflow", () => {
const container = new Element({
width: 100,
height: 100,
padding: 10,
borderWidth: 2
})
const div1 = new Element({
width: 114,
height: 114,
})
container.add(div1);
container.layout();
// 写死尺寸的情况下子元素不收缩父元素不撑开
expect(container.layoutBox.width).toBe(100);
expect(container.layoutBox.height).toBe(100);
expect(div1.layoutBox.left).toBe(10 + 2);
expect(div1.layoutBox.top).toBe(10 + 2);
expect(div1.layoutBox.width).toBe(114);
expect(div1.layoutBox.height).toBe(114);
});
test("right bottom", () => {
const container = new Element({
width: 100,
height: 100,
padding: 10,
borderWidth: 2
})
const div1 = new Element({
width: 14,
height: 14,
right: 13,
bottom: 9,
position: "absolute"
})
container.add(div1);
container.layout();
// right bottom 只有在 position 为 absolute 的情况下才有用
expect(container.layoutBox.width).toBe(100);
expect(container.layoutBox.height).toBe(100);
// 但这时就是以整个父元素为边界,而不是 border + padding 后的边界
expect(div1.layoutBox.left).toBe(100 - 13 - 14);
expect(div1.layoutBox.top).toBe(100 - 9 - 14);
});
test("flex center", () => {
const container = new Element({
width: 100,
height: 100,
padding: 10,
borderWidth: 2,
flexDirection: "row",
justifyContent: "center",
alignItems: "center"
})
const div1 = new Element({
width: 14,
height: 14
})
container.add(div1);
container.layout();
// 使用 flex 水平垂直居中
expect(div1.layoutBox.left).toBe((100 - 14)/2);
expect(div1.layoutBox.top).toBe((100 - 14)/2);
})
test("flex top bottom", () => {
const container = new Element({
width: 100,
height: 100,
padding: 10,
borderWidth: 2,
flexDirection: "column",
justifyContent: "space-between",
alignItems: "stretch"
})
// flex 实现一上一下两行水平填满
const div1 = new Element({
height: 10
})
const div2 = new Element({
height: 20
})
container.add(div1);
container.add(div2);
container.layout();
expect(div1.layoutBox.left).toBe(10 + 2);
expect(div1.layoutBox.top).toBe(10 + 2);
expect(div1.layoutBox.width).toBe(100 - 10*2 - 2*2);
expect(div2.layoutBox.left).toBe(10 + 2);
expect(div2.layoutBox.top).toBe(100 - 10 - 2 - 20);
expect(div2.layoutBox.width).toBe(100 - 10*2 - 2*2);
})
test("rewrite uuid", () => {
// 小程序为了保证 webview 和 service 侧的 coverview 不冲突,所以设置了不同的自增起点
// uuid 静态方法就是为了根据不同的需求去覆写
let uuid = 79648527;
Element.uuid = () => uuid++;
const container = new Element();
expect(container.id).toEqual(79648527);
const div = new Element();
expect(div.id).toEqual(79648528);
});
test("absolute left top", () => {
const container = new Element({
width: 300,
height: 200,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center'
})
const div1 = new Element({
width: 80,
height: 60
})
const div2 = new Element({
width: 40,
height: 30
})
div1.add(div2)
container.add(div1)
container.layout()
expect(div1.layoutBox.left).toBe(110)
expect(div1.layoutBox.top).toBe(70)
expect(div2.layoutBox.left).toBe(110)
expect(div2.layoutBox.top).toBe(70)
})

47
node_modules/widget-ui/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,47 @@
{
"compilerOptions": {
"baseUrl": "src",
"resolveJsonModule": true,
"downlevelIteration": false,
"target": "es5",
"module": "commonjs",
"lib": [
"es5",
"es2015.promise",
"es2016",
"dom"
],
"outDir": "./dist",
"paths": {
"@/*": [
"*"
],
"*": [
"*"
]
},
"typeRoots": [
"./node_modules/@types"
],
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"declaration": true,
"stripInternal": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"alwaysStrict": true,
"noFallthroughCasesInSwitch": true,
"removeComments": false,
"strictNullChecks": true,
"strictFunctionTypes": true,
"skipLibCheck": true,
"pretty": true,
"strictPropertyInitialization": true
},
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules"
]
}

206
node_modules/widget-ui/tslint.json generated vendored Normal file
View File

@@ -0,0 +1,206 @@
{
"defaultSeverity": "error",
"extends": [],
"rules": {
"adjacent-overload-signatures": true,
"align": {
"options": [
"parameters",
"statements"
]
},
"arrow-return-shorthand": true,
"ban-types": {
"options": [
[
"Object",
"Avoid using the `Object` type. Did you mean `object`?"
],
[
"Function",
"Avoid using the `Function` type. Prefer a specific function type, like `() => void`."
],
[
"Boolean",
"Avoid using the `Boolean` type. Did you mean `boolean`?"
],
[
"Number",
"Avoid using the `Number` type. Did you mean `number`?"
],
[
"String",
"Avoid using the `String` type. Did you mean `string`?"
],
[
"Symbol",
"Avoid using the `Symbol` type. Did you mean `symbol`?"
]
]
},
"comment-format": {
"options": [
"check-space"
]
},
"curly": {
"options": [
"ignore-same-line"
]
},
"cyclomatic-complexity": false,
"import-spacing": true,
"indent": {
"options": [
"spaces"
]
},
"interface-over-type-literal": true,
"member-ordering": [
true,
{
"order": [
"public-static-field",
"public-instance-field",
"private-static-field",
"private-instance-field",
"public-constructor",
"private-constructor",
"public-instance-method",
"protected-instance-method",
"private-instance-method"
],
"alphabetize": false
}
],
"no-angle-bracket-type-assertion": true,
"no-arg": true,
"no-conditional-assignment": true,
"no-debugger": true,
"no-duplicate-super": true,
"no-eval": true,
"no-internal-module": true,
"no-misused-new": true,
"no-reference-import": true,
"no-string-literal": true,
"no-string-throw": true,
"no-unnecessary-initializer": true,
"no-unsafe-finally": true,
"no-unused-expression": true,
"no-use-before-declare": false,
"no-var-keyword": true,
"no-var-requires": true,
"one-line": {
"options": [
"check-catch",
"check-else",
"check-finally",
"check-open-brace",
"check-whitespace"
]
},
"one-variable-per-declaration": {
"options": [
"ignore-for-loop"
]
},
"ordered-imports": {
"options": {
"import-sources-order": "case-insensitive",
"module-source-path": "full",
"named-imports-order": "case-insensitive"
}
},
"prefer-const": true,
"prefer-for-of": false,
"quotemark": {
"options": [
"double",
"avoid-escape"
]
},
"radix": true,
"semicolon": {
"options": [
"always"
]
},
"space-before-function-paren": {
"options": {
"anonymous": "never",
"asyncArrow": "always",
"constructor": "never",
"method": "never",
"named": "never"
}
},
"trailing-comma": {
"options": {
"esSpecCompliant": true,
"multiline": {
"objects": "always",
"arrays": "always",
"functions": "always",
"typeLiterals": "always"
},
"singleline": "never"
}
},
"triple-equals": {
"options": [
"allow-null-check"
]
},
"typedef": false,
"typedef-whitespace": {
"options": [
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
]
},
"typeof-compare": false,
"unified-signatures": true,
"use-isnan": true,
"whitespace": {
"options": [
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type",
"check-typecast"
]
}
},
"jsRules": {},
"rulesDirectory": [],
"no-var-requires": false,
"trailing-comma": [
true,
{
"multiline": {
"objects": "always",
"arrays": "always",
"functions": "always",
"typeLiterals": "ignore"
},
"esSpecCompliant": true
}
],
"no-unused-expression": [
true,
"allow-fast-null-checks"
]
}

25
node_modules/widget-ui/webpack.config.js generated vendored Normal file
View File

@@ -0,0 +1,25 @@
const path = require("path");
module.exports = {
mode: "production",
entry: path.resolve(__dirname, "src/element.ts"),
module: {
rules: [
{
test: /\.ts$/,
use: "ts-loader",
exclude: /node_modules/
}
]
},
resolve: {
extensions: [".js", ".ts"]
},
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist"),
libraryTarget: "umd", // 采用通用模块定义
libraryExport: "default", // 兼容 ES6(ES2015) 的模块系统、CommonJS 和 AMD 模块规范
globalObject: "this" // 兼容node和浏览器运行避免window is not undefined情况
}
};