forked from tsai/budibase
10 changed files with 525 additions and 2 deletions
|
After Width: | Height: | Size: 181 B |
|
After Width: | Height: | Size: 446 B |
@ -0,0 +1,49 @@ |
|||||
|
<script> |
||||
|
import Modal from "../../common/Modal.svelte"; |
||||
|
import EventSelector from "../EventSelector.svelte"; |
||||
|
|
||||
|
export let event; |
||||
|
export let open; |
||||
|
export let closeModal; |
||||
|
|
||||
|
$: action = open && event ? "Edit" : "Create"; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<Modal isOpen={open} onClosed={closeModal}> |
||||
|
<h2>{action} Event</h2> |
||||
|
<EventSelector |
||||
|
onChanged={console.log} |
||||
|
onRemoved={console.log} |
||||
|
{event} |
||||
|
/> |
||||
|
</Modal> |
||||
|
|
||||
|
<style> |
||||
|
h3 { |
||||
|
text-transform: uppercase; |
||||
|
font-size: 12px; |
||||
|
font-weight: 700; |
||||
|
color: #8997ab; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.root { |
||||
|
font-size: 10pt; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.form-root { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.prop-container { |
||||
|
flex: 1 1 auto; |
||||
|
min-width: 250px; |
||||
|
} |
||||
|
|
||||
|
.edit-icon:hover { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,110 @@ |
|||||
|
<script> |
||||
|
import IconButton from "../../common/IconButton.svelte"; |
||||
|
import StateBindingControl from "./StateBindingControl.svelte"; |
||||
|
import { find, map, keys, reduce, keyBy } from "lodash/fp"; |
||||
|
import { pipe, userWithFullAccess } from "../../common/core"; |
||||
|
import { |
||||
|
EVENT_TYPE_MEMBER_NAME, |
||||
|
allHandlers |
||||
|
} from "../../common/eventHandlers"; |
||||
|
import { store } from "../../builderStore"; |
||||
|
|
||||
|
export let event; |
||||
|
export let onChanged; |
||||
|
export let onRemoved; |
||||
|
|
||||
|
let events; |
||||
|
let eventType; |
||||
|
let parameters = []; |
||||
|
|
||||
|
store.subscribe(s => { |
||||
|
events = allHandlers( |
||||
|
{ hierarchy: s.hierarchy }, |
||||
|
userWithFullAccess({ |
||||
|
hierarchy: s.hierarchy, |
||||
|
actions: keyBy("name")(s.actions) |
||||
|
}) |
||||
|
); |
||||
|
}); |
||||
|
|
||||
|
// TODO: refactor |
||||
|
$: { |
||||
|
if (event) { |
||||
|
eventType = event[EVENT_TYPE_MEMBER_NAME]; |
||||
|
|
||||
|
parameters = pipe( |
||||
|
event.parameters, |
||||
|
[keys, map(key => ({ name: key, value: event.parameters[key] }))] |
||||
|
); |
||||
|
} else { |
||||
|
eventType = ""; |
||||
|
parameters = []; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// TODO: refactor |
||||
|
const eventChanged = (type, parameters) => { |
||||
|
const paramsAsObject = reduce((obj, p) => { |
||||
|
obj[p.name] = p.value; |
||||
|
return obj; |
||||
|
}, {})(parameters); |
||||
|
|
||||
|
const ev = {}; |
||||
|
ev[EVENT_TYPE_MEMBER_NAME] = type; |
||||
|
ev.parameters = paramsAsObject; |
||||
|
|
||||
|
onChanged(ev); |
||||
|
}; |
||||
|
|
||||
|
// TODO: refactor |
||||
|
const eventTypeChanged = ev => { |
||||
|
const eType = find(e => e.name === ev.target.value)(events); |
||||
|
const emptyParameters = map(p => ({ name: p, value: "" }))( |
||||
|
eType.parameters |
||||
|
); |
||||
|
eventChanged(eType.name, emptyParameters); |
||||
|
}; |
||||
|
|
||||
|
const onParameterChanged = index => val => { |
||||
|
const newparameters = [...parameters]; |
||||
|
newparameters[index].value = val; |
||||
|
eventChanged(eventType, newparameters); |
||||
|
}; |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
.type-selector-container { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.type-selector { |
||||
|
border-color: var(--primary50); |
||||
|
border-radius: 2px; |
||||
|
width: 50px; |
||||
|
flex: 1 0 auto; |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
|
<div class="type-selector-container"> |
||||
|
<select |
||||
|
class="type-selector uk-select uk-form-small " |
||||
|
value={eventType} |
||||
|
on:change={eventTypeChanged}> |
||||
|
<option /> |
||||
|
{#each events as ev} |
||||
|
<option value={ev.name}>{ev.name}</option> |
||||
|
{/each} |
||||
|
</select> |
||||
|
|
||||
|
<IconButton icon="trash" size="12" on:click={onRemoved} /> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
{#if parameters} |
||||
|
{#each parameters as p, index} |
||||
|
<div>{p.name}</div> |
||||
|
<StateBindingControl |
||||
|
onChanged={onParameterChanged(index)} |
||||
|
value={p.value} /> |
||||
|
{/each} |
||||
|
{/if} |
||||
@ -0,0 +1,149 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
keys, |
||||
|
map, |
||||
|
some, |
||||
|
includes, |
||||
|
cloneDeep, |
||||
|
isEqual, |
||||
|
sortBy, |
||||
|
filter, |
||||
|
difference |
||||
|
} from "lodash/fp"; |
||||
|
import { pipe } from "../../common/core"; |
||||
|
import Checkbox from "../../common/Checkbox.svelte"; |
||||
|
import Textbox from "../../common/Textbox.svelte"; |
||||
|
import Dropdown from "../../common/Dropdown.svelte"; |
||||
|
import IconButton from "../../common/IconButton.svelte"; |
||||
|
import Modal from "../../common/Modal.svelte"; |
||||
|
import EventSelector from "./EventSelector.svelte"; |
||||
|
|
||||
|
import { PencilIcon } from "../../common/Icons"; |
||||
|
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers"; |
||||
|
|
||||
|
export const EVENT_TYPE = "event"; |
||||
|
|
||||
|
export let componentInfo; |
||||
|
export let onPropChanged = () => {}; |
||||
|
export let components; |
||||
|
|
||||
|
let modalOpen = false; |
||||
|
let events = []; |
||||
|
let selectedEvent = null; |
||||
|
let newEventType = "onClick"; |
||||
|
|
||||
|
// TODO: only show events that have handlers |
||||
|
$: events = |
||||
|
componentInfo && |
||||
|
Object.entries(componentInfo).filter( |
||||
|
([name]) => findType(name) == EVENT_TYPE |
||||
|
); |
||||
|
|
||||
|
$: action = selectedEvent ? "Edit" : "Create"; |
||||
|
|
||||
|
function findType(propName) { |
||||
|
if (!componentInfo._component) return; |
||||
|
return components.find(({ name }) => name === componentInfo._component) |
||||
|
.props[propName]; |
||||
|
} |
||||
|
|
||||
|
console.log(componentInfo, events, components); |
||||
|
|
||||
|
const openModal = event => { |
||||
|
selectedEvent = event; |
||||
|
modalOpen = true; |
||||
|
}; |
||||
|
|
||||
|
const closeModal = () => { |
||||
|
selectedEvent = null; |
||||
|
modalOpen = false; |
||||
|
}; |
||||
|
|
||||
|
const addEventHandler = event => { |
||||
|
const newEventHandler = { |
||||
|
parameters: {}, |
||||
|
[EVENT_TYPE_MEMBER_NAME]: "" |
||||
|
}; |
||||
|
events = [...events, newEventHandler]; |
||||
|
onPropChanged(newEventType, events); |
||||
|
}; |
||||
|
|
||||
|
const changeEventHandler = newEvent => { |
||||
|
console.log({ events, newEventType, newEvent }); |
||||
|
onPropChanged(newEventType, events); |
||||
|
}; |
||||
|
|
||||
|
const removeEventHandler = index => () => { |
||||
|
events = filter(e => e !== events[index])(events); |
||||
|
onPropChanged(newEventType, []); |
||||
|
}; |
||||
|
|
||||
|
console.log(events); |
||||
|
</script> |
||||
|
|
||||
|
<style> |
||||
|
h3 { |
||||
|
text-transform: uppercase; |
||||
|
font-size: 12px; |
||||
|
font-weight: 700; |
||||
|
color: #8997ab; |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.root { |
||||
|
font-size: 10pt; |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.form-root { |
||||
|
display: flex; |
||||
|
flex-wrap: wrap; |
||||
|
} |
||||
|
|
||||
|
.prop-container { |
||||
|
flex: 1 1 auto; |
||||
|
min-width: 250px; |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
|
<h3>Events</h3> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<form class="uk-form-stacked form-root"> |
||||
|
{#each events as event, index} |
||||
|
<div class="prop-container"> |
||||
|
{event[0]} |
||||
|
<PencilIcon on:click={() => openModal({ ...event, index })} /> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</form> |
||||
|
<button on:click={() => openModal()}>Create Event</button> |
||||
|
</div> |
||||
|
<Modal bind:isOpen={modalOpen} onClosed={closeModal}> |
||||
|
<h2>{action} Event</h2> |
||||
|
{#if selectedEvent} |
||||
|
{JSON.stringify(selectedEvent)} |
||||
|
<!-- <EventSelector |
||||
|
onChanged={onEventHandlerChanged(selectedEvent)} |
||||
|
onRemoved={removeHandler(selectedEvent && selectedEvent.index)} |
||||
|
event={selectedEvent} /> |
||||
|
<div class="addelement-container" on:click={addHandler}> |
||||
|
<IconButton icon="plus" size="12" /> |
||||
|
</div> --> |
||||
|
{:else} |
||||
|
<select bind:value={newEventType}> |
||||
|
{#each events as [name]} |
||||
|
<option value={name}>{name}</option> |
||||
|
{/each} |
||||
|
</select> |
||||
|
<EventSelector |
||||
|
onChanged={changeEventHandler} |
||||
|
onRemoved={removeEventHandler} |
||||
|
event={selectedEvent} /> |
||||
|
<div class="addelement-container" on:click={addEventHandler}> |
||||
|
<IconButton icon="plus" size="12" /> |
||||
|
</div> |
||||
|
<button>Save</button> |
||||
|
{/if} |
||||
|
</Modal> |
||||
@ -0,0 +1,177 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
isString |
||||
|
} from "lodash/fp"; |
||||
|
import IconButton from "../../common/IconButton.svelte"; |
||||
|
import { |
||||
|
isBinding, getBinding, setBinding |
||||
|
} from "../../common/binding"; |
||||
|
|
||||
|
export let value=""; |
||||
|
export let onChanged= () => {}; |
||||
|
export let type=""; |
||||
|
export let options=[]; |
||||
|
|
||||
|
let isBound=false; |
||||
|
let bindingPath=""; |
||||
|
let bindingFallbackValue=""; |
||||
|
let bindingSource="store"; |
||||
|
let isExpanded = false; |
||||
|
let forceIsBound = false; |
||||
|
let canOnlyBind = false; |
||||
|
|
||||
|
$: { |
||||
|
canOnlyBind = type === "state"; |
||||
|
if(!forceIsBound && canOnlyBind) |
||||
|
forceIsBound = true; |
||||
|
|
||||
|
isBound= forceIsBound || isBinding(value); |
||||
|
|
||||
|
if(isBound) { |
||||
|
const binding = getBinding(value); |
||||
|
bindingPath= binding.path; |
||||
|
bindingFallbackValue= binding.fallback; |
||||
|
bindingSource = binding.source || "store"; |
||||
|
} else { |
||||
|
bindingPath=""; |
||||
|
bindingFallbackValue=""; |
||||
|
bindingSource="store"; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const clearBinding = () => { |
||||
|
forceIsBound = false; |
||||
|
onChanged(""); |
||||
|
} |
||||
|
|
||||
|
const bind = (path, fallback, source) => { |
||||
|
if(!path) { |
||||
|
clearBinding(""); |
||||
|
return; |
||||
|
} |
||||
|
const binding = setBinding({path, fallback, source}); |
||||
|
onChanged(binding); |
||||
|
} |
||||
|
|
||||
|
const setBindingPath = ev => { |
||||
|
forceIsBound = canOnlyBind; |
||||
|
bind(ev.target.value, bindingFallbackValue, bindingSource) |
||||
|
} |
||||
|
|
||||
|
const setBindingFallback = ev => { |
||||
|
bind(bindingPath, ev.target.value, bindingSource); |
||||
|
} |
||||
|
|
||||
|
const setBindingSource = ev => { |
||||
|
bind(bindingPath, bindingFallbackValue, ev.target.value); |
||||
|
} |
||||
|
|
||||
|
// const makeBinding = () => { |
||||
|
// forceIsBound=true; |
||||
|
// isExpanded=true; |
||||
|
// } |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
{#if isBound} |
||||
|
<div> |
||||
|
<div class="bound-header"> |
||||
|
<div>{isExpanded ? "" : bindingPath}</div> |
||||
|
<IconButton icon={isExpanded ? "chevron-up" : "chevron-down"} |
||||
|
size="12" |
||||
|
on:click={() => isExpanded=!isExpanded}/> |
||||
|
{#if !canOnlyBind} |
||||
|
<IconButton icon="trash" |
||||
|
size="12" |
||||
|
on:click={clearBinding}/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
{#if isExpanded} |
||||
|
<div> |
||||
|
<div class="binding-prop-label">Binding Path</div> |
||||
|
<input class="uk-input uk-form-small" |
||||
|
value={bindingPath} |
||||
|
on:change={setBindingPath} > |
||||
|
<div class="binding-prop-label">Fallback Value</div> |
||||
|
<input class="uk-input uk-form-small" |
||||
|
value={bindingFallbackValue} |
||||
|
on:change={setBindingFallback} > |
||||
|
<div class="binding-prop-label">Binding Source</div> |
||||
|
<select class="uk-select uk-form-small" |
||||
|
value={bindingSource} |
||||
|
on:change={setBindingSource}> |
||||
|
|
||||
|
<option>store</option> |
||||
|
<option>context</option> |
||||
|
|
||||
|
</select> |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
{:else} |
||||
|
<div class="unbound-container"> |
||||
|
|
||||
|
{#if type === "bool"} |
||||
|
|
||||
|
<div> |
||||
|
<IconButton icon={value == true ? "check-square" : "square"} |
||||
|
size="19" |
||||
|
on:click={() => onChanged(!value)} /> |
||||
|
</div> |
||||
|
|
||||
|
{:else if type === "options"} |
||||
|
|
||||
|
<select class="uk-select uk-form-small" |
||||
|
value={value} |
||||
|
on:change={ev => onChanged(ev.target.value)}> |
||||
|
{#each options as option} |
||||
|
<option value={option}>{option}</option> |
||||
|
{/each} |
||||
|
</select> |
||||
|
|
||||
|
{:else} |
||||
|
|
||||
|
<input on:change={ev => onChanged(ev.target.value)} |
||||
|
bind:value={value} |
||||
|
style="flex: 1 0 auto;" /> |
||||
|
|
||||
|
|
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
|
||||
|
<style> |
||||
|
.unbound-container { |
||||
|
display:flex; |
||||
|
} |
||||
|
|
||||
|
.bound-header { |
||||
|
display: flex; |
||||
|
} |
||||
|
|
||||
|
.bound-header > div:nth-child(1) { |
||||
|
flex: 1 0 auto; |
||||
|
width: 30px; |
||||
|
color: var(--secondary50); |
||||
|
padding-left: 5px; |
||||
|
} |
||||
|
|
||||
|
.binding-prop-label { |
||||
|
color: var(--secondary50); |
||||
|
} |
||||
|
|
||||
|
input { |
||||
|
font-size: 12px; |
||||
|
font-weight: 700; |
||||
|
color: #163057; |
||||
|
opacity: 0.7; |
||||
|
padding: 5px 10px; |
||||
|
box-sizing: border-box; |
||||
|
border: 1px solid #DBDBDB; |
||||
|
border-radius: 2px; |
||||
|
outline: none; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1 @@ |
|||||
|
export { default } from "./EventsEditor.svelte"; |
||||
Loading…
Reference in new issue