finished state management ui for v2
This commit is contained in:
parent
412aa6739f
commit
395dcd1cc2
|
@ -22,6 +22,6 @@
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
height: 50px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -23,11 +23,9 @@
|
||||||
|
|
||||||
select {
|
select {
|
||||||
display: block;
|
display: block;
|
||||||
font-size: 14px;
|
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #163057;
|
color: #163057;
|
||||||
line-height: 1.3;
|
|
||||||
padding: 1em 2.6em 0.9em 1.4em;
|
padding: 1em 2.6em 0.9em 1.4em;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
@ -38,7 +36,7 @@
|
||||||
appearance: none;
|
appearance: none;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
height: 50px;
|
height: 35px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.arrow {
|
.arrow {
|
||||||
|
|
|
@ -50,31 +50,8 @@
|
||||||
const handlerParams = {}
|
const handlerParams = {}
|
||||||
for (let param of params) {
|
for (let param of params) {
|
||||||
handlerParams[param.name] = param.value
|
handlerParams[param.name] = param.value
|
||||||
|
|
||||||
if (param.value.startsWith("context")) {
|
|
||||||
const [_, contextKey] = param.value.split(".");
|
|
||||||
|
|
||||||
handlerParams[param.name] = {
|
|
||||||
"##bbstate": contextKey,
|
|
||||||
"##bbsource": "context",
|
|
||||||
"##bbstatefallback": "balls to that",
|
|
||||||
}
|
|
||||||
console.log("Param starts with context", param.value);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (param.value.startsWith("event")) {
|
|
||||||
const [_, eventKey] = param.value.split(".");
|
|
||||||
handlerParams[param.name] = {
|
|
||||||
"##bbstate": eventKey,
|
|
||||||
"##bbsource": "event",
|
|
||||||
"##bbstatefallback": "balls to that",
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(type, params, handlerParams);
|
|
||||||
|
|
||||||
const updatedHandler = {
|
const updatedHandler = {
|
||||||
[EVENT_TYPE_MEMBER_NAME]: type,
|
[EVENT_TYPE_MEMBER_NAME]: type,
|
||||||
parameters: handlerParams,
|
parameters: handlerParams,
|
||||||
|
@ -96,9 +73,9 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const onParameterChanged = index => e => {
|
const onParameterChanged = index => e => {
|
||||||
const value = e.target.value
|
const value = e.target ? e.target.value : e
|
||||||
const newParams = [...parameters]
|
const newParams = [...parameters]
|
||||||
newParams[index].value = e.target.value
|
newParams[index].value = value
|
||||||
handlerChanged(handlerType, newParams)
|
handlerChanged(handlerType, newParams)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -116,7 +93,7 @@
|
||||||
</div>
|
</div>
|
||||||
{#if parameters}
|
{#if parameters}
|
||||||
{#each parameters as parameter, idx}
|
{#each parameters as parameter, idx}
|
||||||
<StateBindingCascader on:change={onParameterChanged(idx)} {parameter} />
|
<StateBindingCascader onChange={onParameterChanged(idx)} {parameter} />
|
||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,20 +11,61 @@
|
||||||
allHandlers,
|
allHandlers,
|
||||||
} from "../../common/eventHandlers"
|
} from "../../common/eventHandlers"
|
||||||
import { store } from "../../builderStore"
|
import { store } from "../../builderStore"
|
||||||
|
import StateBindingOptions from "../PropertyCascader/StateBindingOptions.svelte"
|
||||||
|
import { ArrowDownIcon } from "../../common/Icons/"
|
||||||
|
|
||||||
export let parameter
|
export let parameter
|
||||||
|
export let onChange
|
||||||
|
|
||||||
|
let isOpen = false
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="handler-option">
|
<div class="handler-option">
|
||||||
<span>{parameter.name}</span>
|
<span>{parameter.name}</span>
|
||||||
<Input on:change value={parameter.value} />
|
<div class="handler-input">
|
||||||
|
<Input on:change={onChange} value={parameter.value} />
|
||||||
|
<button on:click={() => (isOpen = !isOpen)}>
|
||||||
|
<div class="icon" style={`transform: rotate(${isOpen ? 0 : 90}deg);`}>
|
||||||
|
<ArrowDownIcon size={36} />
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
{#if isOpen}
|
||||||
|
<StateBindingOptions
|
||||||
|
onSelect={option => {
|
||||||
|
onChange(option)
|
||||||
|
isOpen = false
|
||||||
|
}} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
border-radius: 5px;
|
||||||
|
background: rgba(249, 249, 249, 1);
|
||||||
|
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: rgba(22, 48, 87, 1);
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.handler-option {
|
.handler-option {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.handler-input {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
span {
|
span {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
<script>
|
<script>
|
||||||
import { ArrowDownIcon } from "../common/Icons/"
|
import { ArrowDownIcon } from "../../common/Icons/"
|
||||||
import { store } from "../builderStore"
|
import { store } from "../../builderStore"
|
||||||
import { buildStateOrigins } from "../builderStore/buildStateOrigins"
|
import { buildStateOrigins } from "../../builderStore/buildStateOrigins"
|
||||||
import { isBinding, getBinding, setBinding } from "../common/binding"
|
import { isBinding, getBinding, setBinding } from "../../common/binding"
|
||||||
|
import StateBindingOptions from "./StateBindingOptions.svelte";
|
||||||
|
|
||||||
export let onChanged = () => {}
|
export let onChanged = () => {}
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
@ -58,29 +59,20 @@
|
||||||
setBindingFallback(e.target.value)
|
setBindingFallback(e.target.value)
|
||||||
onChanged(e.target.value)
|
onChanged(e.target.value)
|
||||||
}} />
|
}} />
|
||||||
{#if stateBindings.length}
|
<button on:click={() => (isOpen = !isOpen)}>
|
||||||
<button on:click={() => (isOpen = !isOpen)}>
|
<div
|
||||||
<div
|
class="icon"
|
||||||
class="icon"
|
class:highlighted={bindingPath}
|
||||||
class:highlighted={bindingPath}
|
style={`transform: rotate(${isOpen ? 0 : 90}deg);`}>
|
||||||
style={`transform: rotate(${isOpen ? 0 : 90}deg);`}>
|
<ArrowDownIcon size={36} />
|
||||||
<ArrowDownIcon size={36} />
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
{#if isOpen}
|
{#if isOpen}
|
||||||
<ul class="options">
|
<StateBindingOptions onSelect={option => {
|
||||||
{#each stateBindings as stateBinding}
|
onChanged(option);
|
||||||
<li
|
isOpen = false;
|
||||||
class:bold={stateBinding === bindingPath}
|
}} />
|
||||||
on:click={() => {
|
|
||||||
setBindingPath(stateBinding === bindingPath ? null : stateBinding)
|
|
||||||
}}>
|
|
||||||
{stateBinding}
|
|
||||||
</li>
|
|
||||||
{/each}
|
|
||||||
</ul>
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -115,27 +107,6 @@
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.options {
|
|
||||||
width: 172px;
|
|
||||||
margin: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 35px;
|
|
||||||
padding: 10px;
|
|
||||||
z-index: 1;
|
|
||||||
background: rgba(249, 249, 249, 1);
|
|
||||||
min-height: 50px;
|
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
input {
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
border: 1px solid #dbdbdb;
|
border: 1px solid #dbdbdb;
|
||||||
|
@ -143,4 +114,8 @@
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script>
|
||||||
|
export let onSelect = () => {}
|
||||||
|
|
||||||
|
let options = [
|
||||||
|
{
|
||||||
|
name: "state",
|
||||||
|
description: "Front-end client state.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context",
|
||||||
|
description: "The component context object.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "event",
|
||||||
|
description: "DOM event handler arguments.",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class="options">
|
||||||
|
{#each options as option}
|
||||||
|
<li on:click={() => onSelect(`${option.name}.`)}>
|
||||||
|
<span class="name">{option.name}</span>
|
||||||
|
<span class="description">{option.description}</span>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.options {
|
||||||
|
width: 172px;
|
||||||
|
margin: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 35px;
|
||||||
|
padding: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
background: rgba(249, 249, 249, 1);
|
||||||
|
min-height: 50px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
color: rgba(22, 48, 87, 0.6);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 5px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
export { default } from "./PropertyCascader.svelte"
|
|
@ -1,7 +1,7 @@
|
||||||
<script>
|
<script>
|
||||||
import IconButton from "../common/IconButton.svelte"
|
import IconButton from "../common/IconButton.svelte"
|
||||||
import Input from "../common/Input.svelte"
|
import Input from "../common/Input.svelte"
|
||||||
import PropertyCascader from "./PropertyCascader.svelte"
|
import PropertyCascader from "./PropertyCascader"
|
||||||
import { isBinding, getBinding, setBinding } from "../common/binding"
|
import { isBinding, getBinding, setBinding } from "../common/binding"
|
||||||
|
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
|
|
@ -4,8 +4,14 @@ export const BB_STATE_FALLBACK = "##bbstatefallback"
|
||||||
|
|
||||||
export const isBound = prop => !!parseBinding(prop)
|
export const isBound = prop => !!parseBinding(prop)
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {object|string|number} prop - component property to parse for a dynamic state binding
|
||||||
|
* @returns {object|boolean}
|
||||||
|
*/
|
||||||
export const parseBinding = prop => {
|
export const parseBinding = prop => {
|
||||||
if (!prop) return false
|
if (!prop) return false
|
||||||
|
|
||||||
if (isBindingExpression(prop)) {
|
if (isBindingExpression(prop)) {
|
||||||
return parseBindingExpression(prop)
|
return parseBindingExpression(prop)
|
||||||
}
|
}
|
||||||
|
@ -38,21 +44,22 @@ const isAlreadyBinding = prop => typeof prop === "object" && prop.path
|
||||||
|
|
||||||
const isBindingExpression = prop =>
|
const isBindingExpression = prop =>
|
||||||
typeof prop === "string" &&
|
typeof prop === "string" &&
|
||||||
(prop.startsWith("store.") ||
|
(prop.startsWith("state.") ||
|
||||||
prop.startsWith("context.") ||
|
prop.startsWith("context.") ||
|
||||||
prop.startsWith("event.") ||
|
prop.startsWith("event.") ||
|
||||||
prop.startsWith("route."))
|
prop.startsWith("route."))
|
||||||
|
|
||||||
const parseBindingExpression = prop => {
|
const parseBindingExpression = prop => {
|
||||||
let source = prop.split(".")[0]
|
let [source, ...rest] = prop.split(".");
|
||||||
let path = prop.replace(`${source}.`, "")
|
let path = rest.join(".")
|
||||||
|
|
||||||
if (source === "route") {
|
if (source === "route") {
|
||||||
source = "store"
|
source = "state"
|
||||||
path = `##routeParams.${path}`
|
path = `##routeParams.${path}`
|
||||||
}
|
}
|
||||||
const fallback = ""
|
|
||||||
return {
|
return {
|
||||||
fallback,
|
fallback: "", // TODO: provide fallback support
|
||||||
source,
|
source,
|
||||||
path,
|
path,
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,13 +171,14 @@ const _setup = (
|
||||||
for (let propName in props) {
|
for (let propName in props) {
|
||||||
if (isMetaProp(propName)) continue
|
if (isMetaProp(propName)) continue
|
||||||
|
|
||||||
const val = props[propName]
|
const propValue = props[propName]
|
||||||
|
|
||||||
const binding = parseBinding(val)
|
const binding = parseBinding(propValue)
|
||||||
const isBound = !!binding
|
const isBound = !!binding
|
||||||
|
|
||||||
if (isBound) binding.propName = propName
|
if (isBound) binding.propName = propName
|
||||||
|
|
||||||
if (isBound && binding.source === "store") {
|
if (isBound && binding.source === "state") {
|
||||||
storeBoundProps.push(binding)
|
storeBoundProps.push(binding)
|
||||||
|
|
||||||
initialProps[propName] = !currentStoreState
|
initialProps[propName] = !currentStoreState
|
||||||
|
@ -188,16 +189,20 @@ const _setup = (
|
||||||
binding.fallback,
|
binding.fallback,
|
||||||
binding.source
|
binding.source
|
||||||
)
|
)
|
||||||
} else if (isBound && binding.source === "context") {
|
}
|
||||||
|
|
||||||
|
if (isBound && binding.source === "context") {
|
||||||
initialProps[propName] = !context
|
initialProps[propName] = !context
|
||||||
? val
|
? propValue
|
||||||
: getState(context, binding.path, binding.fallback, binding.source)
|
: getState(context, binding.path, binding.fallback, binding.source)
|
||||||
} else if (isEventType(val)) {
|
}
|
||||||
|
|
||||||
|
if (isEventType(propValue)) {
|
||||||
const handlersInfos = []
|
const handlersInfos = []
|
||||||
for (let e of val) {
|
for (let event of propValue) {
|
||||||
const handlerInfo = {
|
const handlerInfo = {
|
||||||
handlerType: e[EVENT_TYPE_MEMBER_NAME],
|
handlerType: event[EVENT_TYPE_MEMBER_NAME],
|
||||||
parameters: e.parameters,
|
parameters: event.parameters,
|
||||||
}
|
}
|
||||||
const resolvedParams = {}
|
const resolvedParams = {}
|
||||||
for (let paramName in handlerInfo.parameters) {
|
for (let paramName in handlerInfo.parameters) {
|
||||||
|
@ -206,26 +211,20 @@ const _setup = (
|
||||||
if (!paramBinding) {
|
if (!paramBinding) {
|
||||||
resolvedParams[paramName] = () => paramValue
|
resolvedParams[paramName] = () => paramValue
|
||||||
continue
|
continue
|
||||||
} else if (paramBinding.source === "context") {
|
}
|
||||||
const val = getState(
|
|
||||||
context,
|
let paramValueSource;
|
||||||
paramBinding.path,
|
|
||||||
paramBinding.fallback
|
if (paramBinding.source === "context") paramValueSource = context;
|
||||||
)
|
if (paramBinding.source === "state") paramValueSource = getCurrentState();
|
||||||
resolvedParams[paramName] = () => val
|
if (paramBinding.source === "context") paramValueSource = context;
|
||||||
} else if (paramBinding.source === "store") {
|
|
||||||
resolvedParams[paramName] = () =>
|
// The new dynamic event parameter bound to the relevant source
|
||||||
getState(
|
resolvedParams[paramName] = () => getState(
|
||||||
getCurrentState(),
|
paramValueSource,
|
||||||
paramBinding.path,
|
paramBinding.path,
|
||||||
paramBinding.fallback
|
paramBinding.fallback
|
||||||
)
|
);
|
||||||
continue
|
|
||||||
} else if (paramBinding.source === "event") {
|
|
||||||
resolvedParams[paramName] = eventContext => {
|
|
||||||
getState(eventContext, paramBinding.path, paramBinding.fallback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handlerInfo.parameters = resolvedParams
|
handlerInfo.parameters = resolvedParams
|
||||||
|
@ -256,9 +255,9 @@ const makeHandler = (handlerTypes, handlerInfo) => {
|
||||||
const handlerType = handlerTypes[handlerInfo.handlerType]
|
const handlerType = handlerTypes[handlerInfo.handlerType]
|
||||||
return context => {
|
return context => {
|
||||||
const parameters = {}
|
const parameters = {}
|
||||||
for (let p in handlerInfo.parameters) {
|
for (let paramName in handlerInfo.parameters) {
|
||||||
parameters[p] = handlerInfo.parameters[p](context)
|
parameters[paramName] = handlerInfo.parameters[paramName](context)
|
||||||
}
|
}
|
||||||
handlerType.execute(parameters)
|
handlerType.execute(parameters)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,274 @@
|
||||||
|
import {
|
||||||
|
isEventType,
|
||||||
|
eventHandlers,
|
||||||
|
EVENT_TYPE_MEMBER_NAME,
|
||||||
|
} from "./eventHandlers"
|
||||||
|
import { bbFactory } from "./bbComponentApi"
|
||||||
|
import { getState } from "./getState"
|
||||||
|
import { attachChildren } from "../render/attachChildren"
|
||||||
|
|
||||||
|
import { parseBinding } from "./parseBinding"
|
||||||
|
|
||||||
|
const doNothing = () => {}
|
||||||
|
doNothing.isPlaceholder = true
|
||||||
|
|
||||||
|
const isMetaProp = propName =>
|
||||||
|
propName === "_component" ||
|
||||||
|
propName === "_children" ||
|
||||||
|
propName === "_id" ||
|
||||||
|
propName === "_style" ||
|
||||||
|
propName === "_code" ||
|
||||||
|
propName === "_codeMeta"
|
||||||
|
|
||||||
|
export const createStateManager = ({
|
||||||
|
store,
|
||||||
|
coreApi,
|
||||||
|
rootPath,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
routeTo,
|
||||||
|
}) => {
|
||||||
|
let handlerTypes = eventHandlers(store, coreApi, rootPath, routeTo)
|
||||||
|
let currentState
|
||||||
|
|
||||||
|
// any nodes that have props that are bound to the store
|
||||||
|
let nodesBoundByProps = []
|
||||||
|
|
||||||
|
// any node whose children depend on code, that uses the store
|
||||||
|
let nodesWithCodeBoundChildren = []
|
||||||
|
|
||||||
|
const getCurrentState = () => currentState
|
||||||
|
const registerBindings = _registerBindings(
|
||||||
|
nodesBoundByProps,
|
||||||
|
nodesWithCodeBoundChildren
|
||||||
|
)
|
||||||
|
const bb = bbFactory({
|
||||||
|
store,
|
||||||
|
getCurrentState,
|
||||||
|
frontendDefinition,
|
||||||
|
componentLibraries,
|
||||||
|
uiFunctions,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
})
|
||||||
|
|
||||||
|
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb)
|
||||||
|
|
||||||
|
const unsubscribe = store.subscribe(
|
||||||
|
onStoreStateUpdated({
|
||||||
|
setCurrentState: s => (currentState = s),
|
||||||
|
getCurrentState,
|
||||||
|
nodesWithCodeBoundChildren,
|
||||||
|
nodesBoundByProps,
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState: setup,
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
setup,
|
||||||
|
destroy: () => unsubscribe(),
|
||||||
|
getCurrentState,
|
||||||
|
store,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onStoreStateUpdated = ({
|
||||||
|
setCurrentState,
|
||||||
|
getCurrentState,
|
||||||
|
nodesWithCodeBoundChildren,
|
||||||
|
nodesBoundByProps,
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
}) => s => {
|
||||||
|
setCurrentState(s)
|
||||||
|
|
||||||
|
// the original array gets changed by components' destroy()
|
||||||
|
// so we make a clone and check if they are still in the original
|
||||||
|
const nodesWithBoundChildren_clone = [...nodesWithCodeBoundChildren]
|
||||||
|
for (let node of nodesWithBoundChildren_clone) {
|
||||||
|
if (!nodesWithCodeBoundChildren.includes(node)) continue
|
||||||
|
attachChildren({
|
||||||
|
uiFunctions,
|
||||||
|
componentLibraries,
|
||||||
|
treeNode: node,
|
||||||
|
onScreenSlotRendered,
|
||||||
|
setupState,
|
||||||
|
getCurrentState,
|
||||||
|
})(node.rootElement, { hydrate: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let node of nodesBoundByProps) {
|
||||||
|
setNodeState(s, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _registerBindings = (nodesBoundByProps, nodesWithCodeBoundChildren) => (
|
||||||
|
node,
|
||||||
|
bindings
|
||||||
|
) => {
|
||||||
|
if (bindings.length > 0) {
|
||||||
|
node.bindings = bindings
|
||||||
|
nodesBoundByProps.push(node)
|
||||||
|
const onDestroy = () => {
|
||||||
|
nodesBoundByProps = nodesBoundByProps.filter(n => n === node)
|
||||||
|
node.onDestroy = node.onDestroy.filter(d => d === onDestroy)
|
||||||
|
}
|
||||||
|
node.onDestroy.push(onDestroy)
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
node.props._children &&
|
||||||
|
node.props._children.filter(c => c._codeMeta && c._codeMeta.dependsOnStore)
|
||||||
|
.length > 0
|
||||||
|
) {
|
||||||
|
nodesWithCodeBoundChildren.push(node)
|
||||||
|
const onDestroy = () => {
|
||||||
|
nodesWithCodeBoundChildren = nodesWithCodeBoundChildren.filter(
|
||||||
|
n => n === node
|
||||||
|
)
|
||||||
|
node.onDestroy = node.onDestroy.filter(d => d === onDestroy)
|
||||||
|
}
|
||||||
|
node.onDestroy.push(onDestroy)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const setNodeState = (storeState, node) => {
|
||||||
|
if (!node.component) return
|
||||||
|
const newProps = { ...node.bindings.initialProps }
|
||||||
|
|
||||||
|
for (let binding of node.bindings) {
|
||||||
|
const val = getState(storeState, binding.path, binding.fallback)
|
||||||
|
|
||||||
|
if (val === undefined && newProps[binding.propName] !== undefined) {
|
||||||
|
delete newProps[binding.propName]
|
||||||
|
}
|
||||||
|
|
||||||
|
if (val !== undefined) {
|
||||||
|
newProps[binding.propName] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
node.component.$set(newProps)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind a components event handler parameters to state, context or the event itself.
|
||||||
|
* @param {Array} eventHandlerProp - event handler array from component definition
|
||||||
|
*/
|
||||||
|
function bindComponentEventHandlers(eventHandlerProp) {
|
||||||
|
const boundEventHandlers = []
|
||||||
|
for (let event of eventHandlerProp) {
|
||||||
|
const boundEventHandler = {
|
||||||
|
handlerType: event[EVENT_TYPE_MEMBER_NAME],
|
||||||
|
parameters: event.parameters,
|
||||||
|
}
|
||||||
|
|
||||||
|
const boundParameters = {}
|
||||||
|
for (let paramName in boundEventHandler.parameters) {
|
||||||
|
const paramValue = boundEventHandler.parameters[paramName]
|
||||||
|
const paramBinding = parseBinding(paramValue)
|
||||||
|
if (!paramBinding) {
|
||||||
|
boundParameters[paramName] = () => paramValue
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let paramValueSource;
|
||||||
|
|
||||||
|
if (paramBinding.source === "context") paramValueSource = context;
|
||||||
|
if (paramBinding.source === "state") paramValueSource = getCurrentState();
|
||||||
|
|
||||||
|
// The new dynamic event parameter bound to the relevant source
|
||||||
|
boundParameters[paramName] = eventContext => getState(
|
||||||
|
paramBinding.source === "event" ? eventContext : paramValueSource,
|
||||||
|
paramBinding.path,
|
||||||
|
paramBinding.fallback
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
boundEventHandler.parameters = boundParameters
|
||||||
|
boundEventHandlers.push(boundEventHandlers)
|
||||||
|
|
||||||
|
return boundEventHandlers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _setup = (
|
||||||
|
handlerTypes,
|
||||||
|
getCurrentState,
|
||||||
|
registerBindings,
|
||||||
|
bb
|
||||||
|
) => node => {
|
||||||
|
const props = node.props
|
||||||
|
const context = node.context || {}
|
||||||
|
const initialProps = { ...props }
|
||||||
|
const storeBoundProps = []
|
||||||
|
const currentStoreState = getCurrentState()
|
||||||
|
|
||||||
|
for (let propName in props) {
|
||||||
|
if (isMetaProp(propName)) continue
|
||||||
|
|
||||||
|
const propValue = props[propName]
|
||||||
|
|
||||||
|
const binding = parseBinding(propValue)
|
||||||
|
const isBound = !!binding
|
||||||
|
|
||||||
|
if (isBound) binding.propName = propName
|
||||||
|
|
||||||
|
if (isBound && binding.source === "state") {
|
||||||
|
storeBoundProps.push(binding)
|
||||||
|
|
||||||
|
initialProps[propName] = !currentStoreState
|
||||||
|
? binding.fallback
|
||||||
|
: getState(
|
||||||
|
currentStoreState,
|
||||||
|
binding.path,
|
||||||
|
binding.fallback,
|
||||||
|
binding.source
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isBound && binding.source === "context") {
|
||||||
|
initialProps[propName] = !context
|
||||||
|
? propValue
|
||||||
|
: getState(context, binding.path, binding.fallback, binding.source)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isEventType(propValue)) {
|
||||||
|
const boundEventHandlers = bindComponentEventHandlers(propValue);
|
||||||
|
|
||||||
|
if (boundEventHandlers.length === 0) {
|
||||||
|
initialProps[propName] = doNothing
|
||||||
|
} else {
|
||||||
|
initialProps[propName] = async context => {
|
||||||
|
for (let handlerInfo of handlersInfos) {
|
||||||
|
const handler = makeHandler(handlerTypes, handlerInfo)
|
||||||
|
await handler(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
registerBindings(node, storeBoundProps)
|
||||||
|
|
||||||
|
const setup = _setup(handlerTypes, getCurrentState, registerBindings, bb)
|
||||||
|
initialProps._bb = bb(node, setup)
|
||||||
|
|
||||||
|
return initialProps
|
||||||
|
}
|
||||||
|
|
||||||
|
const makeHandler = (handlerTypes, handlerInfo) => {
|
||||||
|
const handlerType = handlerTypes[handlerInfo.handlerType]
|
||||||
|
return context => {
|
||||||
|
const parameters = {}
|
||||||
|
for (let paramName in handlerInfo.parameters) {
|
||||||
|
parameters[paramName] = handlerInfo.parameters[paramName](context)
|
||||||
|
}
|
||||||
|
handlerType.execute(parameters)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue