move dir
This commit is contained in:
46
doric-h5/src/DoricContext.ts
Normal file
46
doric-h5/src/DoricContext.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import { jsObtainContext, jsCallEntityMethod } from 'doric/src/runtime/sandbox'
|
||||
import { Panel } from 'doric'
|
||||
import { DoricPlugin } from "./DoricPlugin"
|
||||
import { createContext } from "./DoricDriver"
|
||||
import { DoricStackNode } from './shader/DoricStackNode'
|
||||
const doricContexts: Map<string, DoricContext> = new Map
|
||||
|
||||
let __contextId__ = 0
|
||||
function getContextId() {
|
||||
return `context_${__contextId__++}`
|
||||
}
|
||||
|
||||
export function getDoricContext(contextId: string) {
|
||||
return doricContexts.get(contextId)
|
||||
}
|
||||
|
||||
export class DoricContext {
|
||||
contextId = getContextId()
|
||||
pluginInstances: Map<string, DoricPlugin> = new Map
|
||||
rootNode: DoricStackNode
|
||||
constructor(content: string) {
|
||||
createContext(this.contextId, content)
|
||||
doricContexts.set(this.contextId, this)
|
||||
this.rootNode = new DoricStackNode(this)
|
||||
}
|
||||
|
||||
get panel() {
|
||||
return jsObtainContext(this.contextId)?.entity as Panel
|
||||
}
|
||||
|
||||
invokeEntityMethod(method: string, ...otherArgs: any) {
|
||||
const argumentsList: any = [this.contextId]
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
Reflect.apply(jsCallEntityMethod, this.panel, argumentsList)
|
||||
}
|
||||
|
||||
init(frame: {
|
||||
width: number,
|
||||
height: number,
|
||||
}, extra?: object) {
|
||||
this.invokeEntityMethod("__init__", frame, extra ? JSON.stringify(extra) : undefined)
|
||||
}
|
||||
|
||||
}
|
137
doric-h5/src/DoricDriver.ts
Normal file
137
doric-h5/src/DoricDriver.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import { jsCallResolve, jsCallReject, jsCallbackTimer } from 'doric/src/runtime/sandbox'
|
||||
import { acquireJSBundle, acquirePlugin } from './DoricRegistry'
|
||||
import { getDoricContext } from './DoricContext'
|
||||
import { DoricPlugin } from './DoricPlugin'
|
||||
|
||||
let __scriptId__ = 0
|
||||
function getScriptId() {
|
||||
return `script_${__scriptId__++}`
|
||||
}
|
||||
|
||||
const originSetTimeout = window.setTimeout
|
||||
const originClearTimeout = window.clearTimeout
|
||||
const originSetInterval = window.setInterval
|
||||
const originClearInterval = window.clearInterval
|
||||
|
||||
const timers: Map<number, { handleId: number, repeat: boolean }> = new Map
|
||||
|
||||
export function injectGlobalObject(name: string, value: any) {
|
||||
Reflect.set(window, name, value, window)
|
||||
}
|
||||
|
||||
export function loadJS(script: string) {
|
||||
const scriptElement = document.createElement('script')
|
||||
scriptElement.text = script
|
||||
scriptElement.id = getScriptId()
|
||||
document.body.appendChild(scriptElement)
|
||||
}
|
||||
|
||||
function packageModuleScript(name: string, content: string) {
|
||||
return `Reflect.apply(doric.jsRegisterModule,this,[${name},Reflect.apply(function(__module){(function(module,exports,require,setTimeout,setInterval,clearTimeout,clearInterval){
|
||||
${content}
|
||||
})(__module,__module.exports,doric.__require__,doricSetTimeout,doricSetInterval,doricClearTimeout,doricClearInterval);
|
||||
return __module.exports;},this,[{exports:{}}])])`
|
||||
}
|
||||
|
||||
function packageCreateContext(contextId: string, content: string) {
|
||||
return `Reflect.apply(function(doric,context,Entry,require,exports,setTimeout,setInterval,clearTimeout,clearInterval){
|
||||
${content}
|
||||
},doric.jsObtainContext("${contextId}"),[undefined,doric.jsObtainContext("${contextId}"),doric.jsObtainEntry("${contextId}"),doric.__require__,{},doricSetTimeout,doricSetInterval,doricClearTimeout,doricClearInterval])`
|
||||
}
|
||||
|
||||
function initDoric() {
|
||||
injectGlobalObject("nativeEmpty", () => undefined)
|
||||
|
||||
injectGlobalObject('nativeLog', (type: 'd' | 'w' | 'e', message: string) => {
|
||||
switch (type) {
|
||||
case 'd':
|
||||
console.log(message)
|
||||
break
|
||||
case 'w':
|
||||
console.warn(message)
|
||||
break
|
||||
case 'e':
|
||||
console.error(message)
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
injectGlobalObject('nativeRequire', (moduleName: string) => {
|
||||
const bundle = acquireJSBundle(moduleName)
|
||||
if (bundle === undefined || bundle.length === 0) {
|
||||
console.log(`Cannot require JS Bundle :${moduleName}`)
|
||||
return false
|
||||
} else {
|
||||
loadJS(packageModuleScript(moduleName, packageModuleScript(name, bundle)))
|
||||
return true
|
||||
}
|
||||
})
|
||||
injectGlobalObject('nativeBridge', (contextId: string, namespace: string, method: string, callbackId: string, args?: any) => {
|
||||
const pluginClass = acquirePlugin(namespace)
|
||||
const doricContext = getDoricContext(contextId)
|
||||
if (pluginClass === undefined) {
|
||||
console.error(`Cannot find Plugin:${namespace}`)
|
||||
return false
|
||||
}
|
||||
if (doricContext === undefined) {
|
||||
console.error(`Cannot find Doric Context:${contextId}`)
|
||||
return false
|
||||
}
|
||||
let plugin = doricContext.pluginInstances.get(namespace)
|
||||
if (plugin === undefined) {
|
||||
plugin = new pluginClass(doricContext) as DoricPlugin
|
||||
doricContext.pluginInstances.set(namespace, plugin)
|
||||
}
|
||||
if (!Reflect.has(plugin, method)) {
|
||||
console.error(`Cannot find Method:${method} in plugin ${namespace}`)
|
||||
return false
|
||||
}
|
||||
const pluginMethod = Reflect.get(plugin, method, plugin)
|
||||
if (typeof pluginMethod !== 'function') {
|
||||
console.error(`Plugin ${namespace}'s property ${method}'s type is ${typeof pluginMethod} not function,`)
|
||||
}
|
||||
const ret = Reflect.apply(pluginMethod, plugin, [args])
|
||||
if (ret instanceof Promise) {
|
||||
ret.then(
|
||||
e => {
|
||||
jsCallResolve(contextId, callbackId, e)
|
||||
},
|
||||
e => {
|
||||
jsCallReject(contextId, callbackId, e)
|
||||
})
|
||||
} else if (ret !== undefined) {
|
||||
jsCallResolve(contextId, callbackId, ret)
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
injectGlobalObject('nativeSetTimer', (timerId: number, time: number, repeat: boolean) => {
|
||||
if (repeat) {
|
||||
const handleId = originSetInterval(() => {
|
||||
jsCallbackTimer(timerId)
|
||||
}, time)
|
||||
timers.set(timerId, { handleId, repeat })
|
||||
} else {
|
||||
const handleId = originSetTimeout(() => {
|
||||
jsCallbackTimer(timerId)
|
||||
}, time)
|
||||
timers.set(timerId, { handleId, repeat })
|
||||
}
|
||||
})
|
||||
injectGlobalObject('nativeClearTimer', (timerId: number) => {
|
||||
const timerInfo = timers.get(timerId)
|
||||
if (timerInfo) {
|
||||
if (timerInfo.repeat) {
|
||||
originClearInterval(timerInfo.handleId)
|
||||
} else {
|
||||
originClearTimeout(timerInfo.handleId)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function createContext(contextId: string, content: string) {
|
||||
loadJS(packageCreateContext(contextId, content))
|
||||
}
|
||||
|
||||
initDoric()
|
30
doric-h5/src/DoricElement.ts
Normal file
30
doric-h5/src/DoricElement.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import axios from 'axios'
|
||||
import { DoricContext } from './DoricContext'
|
||||
|
||||
|
||||
export class DoricElement extends HTMLElement {
|
||||
source: string
|
||||
alias: string
|
||||
context?: DoricContext
|
||||
constructor() {
|
||||
super()
|
||||
this.source = this.getAttribute('src') || ""
|
||||
this.alias = this.getAttribute('alias') || this.source
|
||||
axios.get<string>(this.source).then(result => {
|
||||
this.load(result.data)
|
||||
})
|
||||
}
|
||||
|
||||
load(content: string) {
|
||||
this.context = new DoricContext(content)
|
||||
|
||||
const divElement = document.createElement('div')
|
||||
divElement.style.height = '100%'
|
||||
this.append(divElement)
|
||||
this.context.rootNode.view = divElement
|
||||
this.context.init({
|
||||
width: divElement.offsetWidth,
|
||||
height: divElement.offsetHeight,
|
||||
})
|
||||
}
|
||||
}
|
9
doric-h5/src/DoricPlugin.ts
Normal file
9
doric-h5/src/DoricPlugin.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DoricContext } from "./DoricContext"
|
||||
|
||||
export type DoricPluginClass = { new(...args: any[]): {} }
|
||||
export class DoricPlugin {
|
||||
context: DoricContext
|
||||
constructor(context: DoricContext) {
|
||||
this.context = context
|
||||
}
|
||||
}
|
51
doric-h5/src/DoricRegistry.ts
Normal file
51
doric-h5/src/DoricRegistry.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
import { DoricPluginClass } from "./DoricPlugin"
|
||||
import { ShaderPlugin } from "./plugins/ShaderPlugin"
|
||||
import { DoricViewNodeClass } from "./shader/DoricViewNode"
|
||||
import { DoricStackNode } from "./shader/DoricStackNode"
|
||||
import { DoricVLayoutNode } from './shader/DoricVLayoutNode'
|
||||
import { DoricHLayoutNode } from './shader/DoricHLayoutNode'
|
||||
import { DoricTextNode } from "./shader/DoricTextNode"
|
||||
import { DoricImageNode } from "./shader/DoricImageNode"
|
||||
import { DoricScrollerNode } from "./shader/DoricScrollerNode"
|
||||
import { ModalPlugin } from './plugins/ModalPlugin'
|
||||
|
||||
const bundles: Map<string, string> = new Map
|
||||
|
||||
const plugins: Map<string, DoricPluginClass> = new Map
|
||||
|
||||
const nodes: Map<string, DoricViewNodeClass> = new Map
|
||||
|
||||
|
||||
export function acquireJSBundle(name: string) {
|
||||
return bundles.get(name)
|
||||
}
|
||||
|
||||
export function registerJSBundle(name: string, bundle: string) {
|
||||
bundles.set(name, bundle)
|
||||
}
|
||||
|
||||
export function registerPlugin(name: string, plugin: DoricPluginClass) {
|
||||
plugins.set(name, plugin)
|
||||
}
|
||||
|
||||
export function acquirePlugin(name: string) {
|
||||
return plugins.get(name)
|
||||
}
|
||||
|
||||
export function registerViewNode(name: string, node: DoricViewNodeClass) {
|
||||
nodes.set(name, node)
|
||||
}
|
||||
|
||||
export function acquireViewNode(name: string) {
|
||||
return nodes.get(name)
|
||||
}
|
||||
|
||||
registerPlugin('shader', ShaderPlugin)
|
||||
registerPlugin('modal', ModalPlugin)
|
||||
|
||||
registerViewNode('Stack', DoricStackNode)
|
||||
registerViewNode('VLayout', DoricVLayoutNode)
|
||||
registerViewNode('HLayout', DoricHLayoutNode)
|
||||
registerViewNode('Text', DoricTextNode)
|
||||
registerViewNode('Image', DoricImageNode)
|
||||
registerViewNode('Scroller', DoricScrollerNode)
|
72
doric-h5/src/plugins/ModalPlugin.ts
Normal file
72
doric-h5/src/plugins/ModalPlugin.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { DoricPlugin } from '../DoricPlugin'
|
||||
import { TOP, CENTER_Y, BOTTOM, toPixelString } from '../shader/DoricViewNode'
|
||||
|
||||
export class ModalPlugin extends DoricPlugin {
|
||||
toast(args: {
|
||||
msg?: string,
|
||||
gravity?: number
|
||||
}) {
|
||||
const toastElement = document.createElement('div')
|
||||
toastElement.style.position = "absolute"
|
||||
toastElement.style.textAlign = "center"
|
||||
toastElement.style.width = "100%"
|
||||
|
||||
const textElement = document.createElement('span')
|
||||
textElement.innerText = args.msg || ""
|
||||
textElement.style.backgroundColor = "#777777"
|
||||
textElement.style.color = "white"
|
||||
textElement.style.paddingLeft = '20px'
|
||||
textElement.style.paddingRight = '20px'
|
||||
textElement.style.paddingTop = '10px'
|
||||
textElement.style.paddingBottom = '10px'
|
||||
toastElement.appendChild(textElement)
|
||||
document.body.appendChild(toastElement)
|
||||
const gravity = args.gravity || BOTTOM
|
||||
if ((gravity & TOP) == TOP) {
|
||||
toastElement.style.top = toPixelString(30)
|
||||
} else if ((gravity & BOTTOM) == BOTTOM) {
|
||||
toastElement.style.bottom = toPixelString(30)
|
||||
} else if ((gravity & CENTER_Y) == CENTER_Y) {
|
||||
toastElement.style.top = toPixelString(document.body.offsetHeight / 2 - toastElement.offsetHeight / 2)
|
||||
}
|
||||
setTimeout(() => {
|
||||
document.body.removeChild(toastElement)
|
||||
}, 2000)
|
||||
return Promise.resolve()
|
||||
}
|
||||
alert(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
}) {
|
||||
window.alert(args.msg || "")
|
||||
return Promise.resolve()
|
||||
}
|
||||
confirm(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
cancelLabel?: string,
|
||||
}) {
|
||||
if (window.confirm(args.msg || "")) {
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
prompt(args: {
|
||||
title?: string,
|
||||
msg?: string,
|
||||
okLabel?: string,
|
||||
cancelLabel?: string,
|
||||
defaultText?: string
|
||||
text?: string
|
||||
}) {
|
||||
const result = window.prompt(args.msg || "", args.defaultText)
|
||||
if (result) {
|
||||
return Promise.resolve(result)
|
||||
} else {
|
||||
return Promise.reject(result)
|
||||
}
|
||||
}
|
||||
}
|
9
doric-h5/src/plugins/ShaderPlugin.ts
Normal file
9
doric-h5/src/plugins/ShaderPlugin.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { DoricPlugin } from "../DoricPlugin";
|
||||
import { DVModel } from "../shader/DoricViewNode";
|
||||
|
||||
export class ShaderPlugin extends DoricPlugin {
|
||||
render(ret: DVModel) {
|
||||
this.context.rootNode.viewId = ret.id
|
||||
this.context.rootNode.blend(ret.props)
|
||||
}
|
||||
}
|
53
doric-h5/src/shader/DoricHLayoutNode.ts
Normal file
53
doric-h5/src/shader/DoricHLayoutNode.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { DoricGroupViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricHLayoutNode extends DoricGroupViewNode {
|
||||
space = 0
|
||||
gravity = 0
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.display = "flex"
|
||||
ret.style.flexDirection = "row"
|
||||
ret.style.flexWrap = "nowrap"
|
||||
return ret
|
||||
}
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'space') {
|
||||
this.space = prop
|
||||
} else if (propName === 'gravity') {
|
||||
this.gravity = prop
|
||||
this.gravity = prop
|
||||
if ((this.gravity & LEFT) === LEFT) {
|
||||
this.view.style.justifyContent = "flex-start"
|
||||
} else if ((this.gravity & RIGHT) === RIGHT) {
|
||||
this.view.style.justifyContent = "flex-end"
|
||||
} else if ((this.gravity & CENTER_X) === CENTER_X) {
|
||||
this.view.style.justifyContent = "center"
|
||||
}
|
||||
if ((this.gravity & TOP) === TOP) {
|
||||
this.view.style.alignItems = "flex-start"
|
||||
} else if ((this.gravity & BOTTOM) === BOTTOM) {
|
||||
this.view.style.alignItems = "flex-end"
|
||||
} else if ((this.gravity & CENTER_Y) === CENTER_Y) {
|
||||
this.view.style.alignItems = "center"
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
layout() {
|
||||
super.layout()
|
||||
this.childNodes.forEach((e, idx) => {
|
||||
e.view.style.flexShrink = "0"
|
||||
if (e.layoutConfig?.weight) {
|
||||
e.view.style.flex = `${e.layoutConfig?.weight}`
|
||||
}
|
||||
e.view.style.marginLeft = toPixelString(e.layoutConfig?.margin?.left || 0)
|
||||
e.view.style.marginRight = toPixelString(
|
||||
(idx === this.childNodes.length - 1) ? 0 : this.space
|
||||
+ (e.layoutConfig?.margin?.right || 0))
|
||||
e.view.style.marginTop = toPixelString(e.layoutConfig?.margin?.top || 0)
|
||||
e.view.style.marginBottom = toPixelString(e.layoutConfig?.margin?.bottom || 0)
|
||||
})
|
||||
}
|
||||
}
|
59
doric-h5/src/shader/DoricImageNode.ts
Normal file
59
doric-h5/src/shader/DoricImageNode.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { DoricViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, toRGBAString } from "./DoricViewNode";
|
||||
|
||||
enum ScaleType {
|
||||
ScaleToFill = 0,
|
||||
ScaleAspectFit,
|
||||
ScaleAspectFill,
|
||||
}
|
||||
|
||||
export class DoricImageNode extends DoricViewNode {
|
||||
|
||||
build(): HTMLElement {
|
||||
const ret = document.createElement('img')
|
||||
ret.style.objectFit = "fill"
|
||||
return ret
|
||||
}
|
||||
|
||||
blendProps(v: HTMLImageElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case 'imageUrl':
|
||||
v.setAttribute('src', prop)
|
||||
break
|
||||
case 'imageBase64':
|
||||
v.setAttribute('src', prop)
|
||||
break
|
||||
case 'loadCallback':
|
||||
v.onload = () => {
|
||||
this.callJSResponse(prop, {
|
||||
width: v.width,
|
||||
height: v.height
|
||||
})
|
||||
}
|
||||
break
|
||||
case 'scaleType':
|
||||
switch (prop) {
|
||||
case ScaleType.ScaleToFill:
|
||||
v.style.objectFit = "fill"
|
||||
break
|
||||
case ScaleType.ScaleAspectFit:
|
||||
v.style.objectFit = "contain"
|
||||
break
|
||||
case ScaleType.ScaleAspectFill:
|
||||
v.style.objectFit = "cover"
|
||||
break
|
||||
}
|
||||
break
|
||||
case 'isBlur':
|
||||
if (prop) {
|
||||
v.style.filter = 'blur(8px)'
|
||||
} else {
|
||||
v.style.filter = ''
|
||||
}
|
||||
break
|
||||
default:
|
||||
super.blendProps(v, propName, prop)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
69
doric-h5/src/shader/DoricScrollerNode.ts
Normal file
69
doric-h5/src/shader/DoricScrollerNode.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
import { LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, DoricSuperViewNode, DVModel, DoricViewNode } from "./DoricViewNode";
|
||||
|
||||
export class DoricScrollerNode extends DoricSuperViewNode {
|
||||
|
||||
|
||||
childViewId: string = ""
|
||||
childNode?: DoricViewNode
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.overflow = "scroll"
|
||||
return ret
|
||||
}
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'content') {
|
||||
this.childViewId = prop
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
blendSubNode(model: DVModel): void {
|
||||
this.childNode?.blend(model.props)
|
||||
}
|
||||
getSubNodeById(viewId: string) {
|
||||
return viewId === this.childViewId ? this.childNode : undefined
|
||||
}
|
||||
|
||||
onBlended() {
|
||||
super.onBlended()
|
||||
const model = this.getSubModel(this.childViewId)
|
||||
if (model === undefined) {
|
||||
return
|
||||
}
|
||||
if (this.childNode) {
|
||||
if (this.childNode.viewId === this.childViewId) {
|
||||
///skip
|
||||
} else {
|
||||
if (this.reusable && this.childNode.viewType === model.type) {
|
||||
this.childNode.viewId = model.id
|
||||
this.childNode.blend(model.props)
|
||||
} else {
|
||||
this.view.removeChild(this.childNode.view)
|
||||
const childNode = DoricViewNode.create(this.context, model.type)
|
||||
if (childNode === undefined) {
|
||||
return
|
||||
}
|
||||
childNode.viewId = model.id
|
||||
childNode.init(this)
|
||||
childNode.blend(model.props)
|
||||
this.view.appendChild(childNode.view)
|
||||
this.childNode = childNode
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const childNode = DoricViewNode.create(this.context, model.type)
|
||||
if (childNode === undefined) {
|
||||
return
|
||||
}
|
||||
childNode.viewId = model.id
|
||||
childNode.init(this)
|
||||
childNode.blend(model.props)
|
||||
this.view.appendChild(childNode.view)
|
||||
this.childNode = childNode
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
}
|
||||
}
|
38
doric-h5/src/shader/DoricStackNode.ts
Normal file
38
doric-h5/src/shader/DoricStackNode.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { DoricGroupViewNode, LayoutSpec, FrameSize, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricStackNode extends DoricGroupViewNode {
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.position = "relative"
|
||||
return ret
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
this.configOffset()
|
||||
}
|
||||
|
||||
configOffset() {
|
||||
this.childNodes.forEach(e => {
|
||||
e.view.style.position = "absolute"
|
||||
e.view.style.left = toPixelString(e.offsetX + this.paddingLeft)
|
||||
e.view.style.top = toPixelString(e.offsetY + this.paddingTop)
|
||||
const gravity = e.layoutConfig.alignment
|
||||
if ((gravity & LEFT) === LEFT) {
|
||||
e.view.style.left = toPixelString(0)
|
||||
} else if ((gravity & RIGHT) === RIGHT) {
|
||||
e.view.style.left = toPixelString(this.view.offsetWidth - e.view.offsetWidth)
|
||||
} else if ((gravity & CENTER_X) === CENTER_X) {
|
||||
e.view.style.left = toPixelString(this.view.offsetWidth / 2 - e.view.offsetWidth / 2)
|
||||
}
|
||||
if ((gravity & TOP) === TOP) {
|
||||
e.view.style.top = toPixelString(0)
|
||||
} else if ((gravity & BOTTOM) === BOTTOM) {
|
||||
e.view.style.top = toPixelString(this.view.offsetHeight - e.view.offsetHeight)
|
||||
} else if ((gravity & CENTER_Y) === CENTER_Y) {
|
||||
e.view.style.top = toPixelString(this.view.offsetHeight / 2 - e.view.offsetHeight / 2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
49
doric-h5/src/shader/DoricTextNode.ts
Normal file
49
doric-h5/src/shader/DoricTextNode.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { DoricViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString, toRGBAString } from "./DoricViewNode";
|
||||
|
||||
export class DoricTextNode extends DoricViewNode {
|
||||
textElement!: HTMLElement
|
||||
build(): HTMLElement {
|
||||
const div = document.createElement('div')
|
||||
div.style.display = "flex"
|
||||
this.textElement = document.createElement('span')
|
||||
div.appendChild(this.textElement)
|
||||
div.style.justifyContent = "center"
|
||||
div.style.alignItems = "center"
|
||||
return div
|
||||
}
|
||||
|
||||
blendProps(v: HTMLParagraphElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case 'text':
|
||||
this.textElement.innerText = prop
|
||||
break
|
||||
case 'textSize':
|
||||
v.style.fontSize = toPixelString(prop)
|
||||
break
|
||||
case 'textColor':
|
||||
v.style.color = toRGBAString(prop)
|
||||
break
|
||||
case 'textAlignment':
|
||||
const gravity: number = prop
|
||||
if ((gravity & LEFT) === LEFT) {
|
||||
v.style.justifyContent = "flex-start"
|
||||
} else if ((gravity & RIGHT) === RIGHT) {
|
||||
v.style.justifyContent = "flex-end"
|
||||
} else if ((gravity & CENTER_X) === CENTER_X) {
|
||||
v.style.justifyContent = "center"
|
||||
}
|
||||
if ((gravity & TOP) === TOP) {
|
||||
v.style.alignItems = "flex-start"
|
||||
} else if ((gravity & BOTTOM) === BOTTOM) {
|
||||
v.style.alignItems = "flex-end"
|
||||
} else if ((gravity & CENTER_Y) === CENTER_Y) {
|
||||
v.style.alignItems = "center"
|
||||
}
|
||||
break
|
||||
default:
|
||||
super.blendProps(v, propName, prop)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
53
doric-h5/src/shader/DoricVLayoutNode.ts
Normal file
53
doric-h5/src/shader/DoricVLayoutNode.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import { DoricGroupViewNode, LEFT, RIGHT, CENTER_X, CENTER_Y, TOP, BOTTOM, toPixelString } from "./DoricViewNode";
|
||||
|
||||
export class DoricVLayoutNode extends DoricGroupViewNode {
|
||||
space = 0
|
||||
gravity = 0
|
||||
|
||||
build() {
|
||||
const ret = document.createElement('div')
|
||||
ret.style.display = "flex"
|
||||
ret.style.flexDirection = "column"
|
||||
ret.style.flexWrap = "nowrap"
|
||||
return ret
|
||||
}
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'space') {
|
||||
this.space = prop
|
||||
} else if (propName === 'gravity') {
|
||||
this.gravity = prop
|
||||
if ((this.gravity & LEFT) === LEFT) {
|
||||
this.view.style.alignItems = "flex-start"
|
||||
} else if ((this.gravity & RIGHT) === RIGHT) {
|
||||
this.view.style.alignItems = "flex-end"
|
||||
} else if ((this.gravity & CENTER_X) === CENTER_X) {
|
||||
this.view.style.alignItems = "center"
|
||||
}
|
||||
if ((this.gravity & TOP) === TOP) {
|
||||
this.view.style.justifyContent = "flex-start"
|
||||
} else if ((this.gravity & BOTTOM) === BOTTOM) {
|
||||
this.view.style.justifyContent = "flex-end"
|
||||
} else if ((this.gravity & CENTER_Y) === CENTER_Y) {
|
||||
this.view.style.justifyContent = "center"
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
super.layout()
|
||||
this.childNodes.forEach((e, idx) => {
|
||||
e.view.style.flexShrink = "0"
|
||||
if (e.layoutConfig?.weight) {
|
||||
e.view.style.flex = `${e.layoutConfig?.weight}`
|
||||
}
|
||||
e.view.style.marginTop = toPixelString(e.layoutConfig?.margin?.top || 0)
|
||||
e.view.style.marginBottom = toPixelString(
|
||||
(idx === this.childNodes.length - 1) ? 0 : this.space
|
||||
+ (e.layoutConfig?.margin?.bottom || 0))
|
||||
e.view.style.marginLeft = toPixelString(e.layoutConfig?.margin?.left || 0)
|
||||
e.view.style.marginRight = toPixelString(e.layoutConfig?.margin?.right || 0)
|
||||
})
|
||||
}
|
||||
}
|
481
doric-h5/src/shader/DoricViewNode.ts
Normal file
481
doric-h5/src/shader/DoricViewNode.ts
Normal file
@@ -0,0 +1,481 @@
|
||||
import { DoricContext } from "../DoricContext";
|
||||
import { acquireViewNode } from "../DoricRegistry";
|
||||
|
||||
export enum LayoutSpec {
|
||||
EXACTLY = 0,
|
||||
WRAP_CONTENT = 1,
|
||||
AT_MOST = 2,
|
||||
}
|
||||
|
||||
const SPECIFIED = 1
|
||||
const START = 1 << 1
|
||||
const END = 1 << 2
|
||||
|
||||
const SHIFT_X = 0
|
||||
const SHIFT_Y = 4
|
||||
|
||||
export const LEFT = (START | SPECIFIED) << SHIFT_X
|
||||
export const RIGHT = (END | SPECIFIED) << SHIFT_X
|
||||
|
||||
export const TOP = (START | SPECIFIED) << SHIFT_Y
|
||||
export const BOTTOM = (END | SPECIFIED) << SHIFT_Y
|
||||
|
||||
export const CENTER_X = SPECIFIED << SHIFT_X
|
||||
export const CENTER_Y = SPECIFIED << SHIFT_Y
|
||||
|
||||
export const CENTER = CENTER_X | CENTER_Y
|
||||
|
||||
export type FrameSize = {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
export function toPixelString(v: number) {
|
||||
return `${v}px`
|
||||
}
|
||||
|
||||
export function toRGBAString(color: number) {
|
||||
let strs = []
|
||||
for (let i = 0; i < 32; i += 8) {
|
||||
|
||||
strs.push(((color >> i) & 0xff).toString(16))
|
||||
}
|
||||
strs = strs.map(e => {
|
||||
if (e.length === 1) {
|
||||
return '0' + e
|
||||
}
|
||||
return e
|
||||
}).reverse()
|
||||
/// RGBA
|
||||
return `#${strs[1]}${strs[2]}${strs[3]}${strs[0]}`
|
||||
}
|
||||
|
||||
|
||||
export type DoricViewNodeClass = { new(...args: any[]): {} }
|
||||
|
||||
export interface DVModel {
|
||||
id: string,
|
||||
type: string,
|
||||
props: {
|
||||
[index: string]: any
|
||||
},
|
||||
}
|
||||
|
||||
export abstract class DoricViewNode {
|
||||
viewId = ""
|
||||
viewType = "View"
|
||||
context: DoricContext
|
||||
superNode?: DoricSuperViewNode
|
||||
layoutConfig = {
|
||||
widthSpec: LayoutSpec.EXACTLY,
|
||||
heightSpec: LayoutSpec.EXACTLY,
|
||||
alignment: 0,
|
||||
weight: 0,
|
||||
margin: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0
|
||||
}
|
||||
}
|
||||
padding = {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
}
|
||||
|
||||
border?: {
|
||||
width: number,
|
||||
color: number,
|
||||
}
|
||||
|
||||
frameWidth = 0
|
||||
|
||||
frameHeight = 0
|
||||
|
||||
offsetX = 0
|
||||
|
||||
offsetY = 0
|
||||
|
||||
view!: HTMLElement
|
||||
|
||||
|
||||
constructor(context: DoricContext) {
|
||||
this.context = context
|
||||
}
|
||||
|
||||
init(superNode: DoricSuperViewNode) {
|
||||
this.superNode = superNode
|
||||
if (this instanceof DoricSuperViewNode) {
|
||||
this.reusable = superNode.reusable
|
||||
}
|
||||
this.view = this.build()
|
||||
}
|
||||
|
||||
abstract build(): HTMLElement
|
||||
|
||||
get paddingLeft() {
|
||||
return this.padding.left || 0
|
||||
}
|
||||
|
||||
get paddingRight() {
|
||||
return this.padding.right || 0
|
||||
}
|
||||
|
||||
get paddingTop() {
|
||||
return this.padding.top || 0
|
||||
}
|
||||
|
||||
get paddingBottom() {
|
||||
return this.padding.bottom || 0
|
||||
}
|
||||
|
||||
get borderWidth() {
|
||||
return this.border?.width || 0
|
||||
}
|
||||
|
||||
blend(props: { [index: string]: any }) {
|
||||
for (let key in props) {
|
||||
this.blendProps(this.view, key, props[key])
|
||||
}
|
||||
this.onBlended()
|
||||
this.layout()
|
||||
}
|
||||
onBlended() {
|
||||
|
||||
}
|
||||
configBorder() {
|
||||
if (this.border) {
|
||||
this.view.style.borderStyle = "solid"
|
||||
this.view.style.borderWidth = toPixelString(this.border.width)
|
||||
this.view.style.borderColor = toRGBAString(this.border.color)
|
||||
}
|
||||
}
|
||||
|
||||
configWidth() {
|
||||
switch (this.layoutConfig.widthSpec) {
|
||||
case LayoutSpec.WRAP_CONTENT:
|
||||
this.view.style.width = "fit-content"
|
||||
break
|
||||
|
||||
case LayoutSpec.AT_MOST:
|
||||
this.view.style.width = "100%"
|
||||
break
|
||||
|
||||
case LayoutSpec.EXACTLY:
|
||||
default:
|
||||
this.view.style.width = toPixelString(this.frameWidth
|
||||
- this.paddingLeft - this.paddingRight
|
||||
- this.borderWidth * 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
configHeight() {
|
||||
switch (this.layoutConfig.heightSpec) {
|
||||
case LayoutSpec.WRAP_CONTENT:
|
||||
this.view.style.height = "fit-content"
|
||||
break
|
||||
|
||||
case LayoutSpec.AT_MOST:
|
||||
this.view.style.height = "100%"
|
||||
break
|
||||
|
||||
case LayoutSpec.EXACTLY:
|
||||
default:
|
||||
this.view.style.height = toPixelString(this.frameHeight
|
||||
- this.paddingTop - this.paddingBottom
|
||||
- this.borderWidth * 2)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
configMargin() {
|
||||
if (this.layoutConfig.margin) {
|
||||
this.view.style.marginLeft = toPixelString(this.layoutConfig.margin.left || 0)
|
||||
this.view.style.marginRight = toPixelString(this.layoutConfig.margin.right || 0)
|
||||
this.view.style.marginTop = toPixelString(this.layoutConfig.margin.top || 0)
|
||||
this.view.style.marginBottom = toPixelString(this.layoutConfig.margin.bottom || 0)
|
||||
}
|
||||
}
|
||||
|
||||
configPadding() {
|
||||
if (this.padding) {
|
||||
this.view.style.paddingLeft = toPixelString(this.paddingLeft)
|
||||
this.view.style.paddingRight = toPixelString(this.paddingRight)
|
||||
this.view.style.paddingTop = toPixelString(this.paddingTop)
|
||||
this.view.style.paddingBottom = toPixelString(this.paddingBottom)
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.configMargin()
|
||||
this.configBorder()
|
||||
this.configPadding()
|
||||
this.configWidth()
|
||||
this.configHeight()
|
||||
}
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
switch (propName) {
|
||||
case "border":
|
||||
this.border = prop
|
||||
break
|
||||
case "padding":
|
||||
this.padding = prop
|
||||
break
|
||||
case 'width':
|
||||
this.frameWidth = prop as number
|
||||
break
|
||||
case 'height':
|
||||
this.frameHeight = prop as number
|
||||
break
|
||||
case 'backgroundColor':
|
||||
this.backgroundColor = prop as number
|
||||
break
|
||||
case 'layoutConfig':
|
||||
const layoutConfig = prop
|
||||
for (let key in layoutConfig) {
|
||||
Reflect.set(this.layoutConfig, key, Reflect.get(layoutConfig, key, layoutConfig))
|
||||
}
|
||||
break
|
||||
case 'x':
|
||||
this.offsetX = prop as number
|
||||
break
|
||||
case 'y':
|
||||
this.offsetY = prop as number
|
||||
break
|
||||
case 'onClick':
|
||||
this.view.onclick = () => {
|
||||
this.callJSResponse(prop as string)
|
||||
}
|
||||
break
|
||||
case 'corners':
|
||||
if (typeof prop === 'object') {
|
||||
this.view.style.borderTopLeftRadius = toPixelString(prop.leftTop)
|
||||
this.view.style.borderTopRightRadius = toPixelString(prop.rightTop)
|
||||
this.view.style.borderBottomRightRadius = toPixelString(prop.rightBottom)
|
||||
this.view.style.borderBottomLeftRadius = toPixelString(prop.leftBottom)
|
||||
} else {
|
||||
this.view.style.borderRadius = toPixelString(prop)
|
||||
}
|
||||
break
|
||||
case 'shadow':
|
||||
const opacity = prop.opacity || 0
|
||||
if (opacity > 0) {
|
||||
const offsetX = prop.offsetX || 0
|
||||
const offsetY = prop.offsetY || 0
|
||||
const shadowColor = prop.color || 0xff000000
|
||||
const shadowRadius = prop.radius
|
||||
const alpha = opacity * 255
|
||||
this.view.style.boxShadow = `${toPixelString(offsetX)} ${toPixelString(offsetY)} ${toPixelString(shadowRadius)} ${toRGBAString((shadowColor & 0xffffff) | ((alpha & 0xff) << 24))} `
|
||||
} else {
|
||||
this.view.style.boxShadow = ""
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
set backgroundColor(v: number) {
|
||||
this.view.style.backgroundColor = toRGBAString(v)
|
||||
}
|
||||
|
||||
static create(context: DoricContext, type: string) {
|
||||
const viewNodeClass = acquireViewNode(type)
|
||||
if (viewNodeClass === undefined) {
|
||||
console.error(`Cannot find ViewNode for ${type}`)
|
||||
return undefined
|
||||
}
|
||||
const ret = new viewNodeClass(context) as DoricViewNode
|
||||
ret.viewType = type
|
||||
return ret
|
||||
}
|
||||
|
||||
getIdList() {
|
||||
const ids: string[] = []
|
||||
let viewNode: DoricViewNode | undefined = this
|
||||
do {
|
||||
ids.push(viewNode.viewId)
|
||||
viewNode = viewNode.superNode
|
||||
} while (viewNode)
|
||||
return ids.reverse()
|
||||
}
|
||||
|
||||
callJSResponse(funcId: string, ...args: any) {
|
||||
const argumentsList: any = ['__response__', this.getIdList(), funcId]
|
||||
for (let i = 1; i < arguments.length; i++) {
|
||||
argumentsList.push(arguments[i])
|
||||
}
|
||||
Reflect.apply(this.context.invokeEntityMethod, this.context, argumentsList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export abstract class DoricSuperViewNode extends DoricViewNode {
|
||||
reusable = false
|
||||
|
||||
subModels: Map<String, DVModel> = new Map
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'subviews') {
|
||||
if (prop instanceof Array) {
|
||||
prop.forEach((e: DVModel) => {
|
||||
this.mixinSubModel(e)
|
||||
this.blendSubNode(e)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
mixinSubModel(subNode: DVModel) {
|
||||
const oldValue = this.getSubModel(subNode.id)
|
||||
if (oldValue) {
|
||||
this.mixin(subNode, oldValue)
|
||||
} else {
|
||||
this.subModels.set(subNode.id, subNode)
|
||||
}
|
||||
}
|
||||
|
||||
getSubModel(id: string) {
|
||||
return this.subModels.get(id)
|
||||
}
|
||||
|
||||
mixin(src: DVModel, target: DVModel) {
|
||||
for (let key in src.props) {
|
||||
if (key === "subviews") {
|
||||
continue
|
||||
}
|
||||
Reflect.set(target.props, key, Reflect.get(src.props, key))
|
||||
}
|
||||
}
|
||||
clearSubModels() {
|
||||
this.subModels.clear()
|
||||
}
|
||||
|
||||
removeSubModel(id: string) {
|
||||
this.subModels.delete(id)
|
||||
}
|
||||
|
||||
abstract blendSubNode(model: DVModel): void
|
||||
|
||||
abstract getSubNodeById(viewId: string): DoricViewNode | undefined
|
||||
}
|
||||
|
||||
export abstract class DoricGroupViewNode extends DoricSuperViewNode {
|
||||
childNodes: DoricViewNode[] = []
|
||||
childViewIds: string[] = []
|
||||
|
||||
blendProps(v: HTMLElement, propName: string, prop: any) {
|
||||
if (propName === 'children') {
|
||||
if (prop instanceof Array) {
|
||||
this.childViewIds = prop
|
||||
}
|
||||
} else {
|
||||
super.blendProps(v, propName, prop)
|
||||
}
|
||||
}
|
||||
|
||||
blend(props: { [index: string]: any }) {
|
||||
super.blend(props)
|
||||
}
|
||||
onBlended() {
|
||||
super.onBlended()
|
||||
this.configChildNode()
|
||||
}
|
||||
configChildNode() {
|
||||
this.childViewIds.forEach((childViewId, index) => {
|
||||
const model = this.getSubModel(childViewId)
|
||||
if (model === undefined) {
|
||||
return
|
||||
}
|
||||
if (index < this.childNodes.length) {
|
||||
const oldNode = this.childNodes[index]
|
||||
if (oldNode.viewId === childViewId) {
|
||||
//The same,skip
|
||||
} else {
|
||||
if (this.reusable) {
|
||||
if (oldNode.viewType === model.type) {
|
||||
//Same type,can be reused
|
||||
oldNode.viewId = childViewId
|
||||
oldNode.blend(model.props)
|
||||
} else {
|
||||
//Replace this view
|
||||
this.view.removeChild(oldNode.view)
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes[index] = newNode
|
||||
this.view.replaceChild(newNode.view, oldNode.view)
|
||||
}
|
||||
} else {
|
||||
//Find in remain nodes
|
||||
let position = -1
|
||||
for (let start = index + 1; start < this.childNodes.length; start++) {
|
||||
if (childViewId === this.childNodes[start].viewId) {
|
||||
//Found
|
||||
position = start
|
||||
break
|
||||
}
|
||||
}
|
||||
if (position >= 0) {
|
||||
//Found swap idx,position
|
||||
const reused = this.childNodes[position]
|
||||
const abandoned = this.childNodes[index]
|
||||
this.childNodes[index] = reused
|
||||
this.childNodes[position] = abandoned
|
||||
this.view.removeChild(reused.view)
|
||||
this.view.insertBefore(reused.view, abandoned.view)
|
||||
this.view.removeChild(abandoned.view)
|
||||
if (position === this.view.childElementCount - 1) {
|
||||
this.view.appendChild(abandoned.view)
|
||||
} else {
|
||||
this.view.insertBefore(abandoned.view, this.view.children[position])
|
||||
}
|
||||
} else {
|
||||
//Not found,insert
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes[index] = newNode
|
||||
this.view.insertBefore(newNode.view, this.view.children[index])
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//Insert
|
||||
const newNode = DoricViewNode.create(this.context, model.type)
|
||||
if (newNode === undefined) {
|
||||
return
|
||||
}
|
||||
newNode.viewId = childViewId
|
||||
newNode.init(this)
|
||||
newNode.blend(model.props)
|
||||
this.childNodes.push(newNode)
|
||||
this.view.appendChild(newNode.view)
|
||||
}
|
||||
})
|
||||
let size = this.childNodes.length
|
||||
for (let idx = this.childViewIds.length; idx < size; idx++) {
|
||||
this.view.removeChild(this.childNodes[idx].view)
|
||||
}
|
||||
this.childNodes = this.childNodes.slice(0, this.childViewIds.length)
|
||||
}
|
||||
|
||||
blendSubNode(model: DVModel) {
|
||||
this.getSubNodeById(model.id)?.blend(model.props)
|
||||
}
|
||||
|
||||
getSubNodeById(viewId: string) {
|
||||
return this.childNodes.filter(e => e.viewId === viewId)[0]
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user