mirror of https://github.com/Budibase/budibase.git
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