mirror of https://github.com/Budibase/budibase.git
44 changed files with 999 additions and 6786 deletions
@ -0,0 +1,42 @@ |
|||
export const CAPTURE_VAR_INSIDE_MUSTACHE = /{{([^}]+)}}/g |
|||
|
|||
export function readableToRuntimeBinding(bindableProperties, textWithBindings) { |
|||
// Find all instances of mustasche
|
|||
const boundValues = textWithBindings.match(CAPTURE_VAR_INSIDE_MUSTACHE) |
|||
|
|||
let result = textWithBindings |
|||
// Replace readableBindings with runtimeBindings
|
|||
boundValues && |
|||
boundValues.forEach(boundValue => { |
|||
const binding = bindableProperties.find(({ readableBinding }) => { |
|||
return boundValue === `{{ ${readableBinding} }}` |
|||
}) |
|||
if (binding) { |
|||
result = textWithBindings.replace( |
|||
boundValue, |
|||
`{{ ${binding.runtimeBinding} }}` |
|||
) |
|||
} |
|||
}) |
|||
return result |
|||
} |
|||
|
|||
export function runtimeToReadableBinding(bindableProperties, textWithBindings) { |
|||
let temp = textWithBindings |
|||
const boundValues = |
|||
(typeof textWithBindings === "string" && |
|||
textWithBindings.match(CAPTURE_VAR_INSIDE_MUSTACHE)) || |
|||
[] |
|||
|
|||
// Replace runtimeBindings with readableBindings:
|
|||
boundValues.forEach(v => { |
|||
const binding = bindableProperties.find(({ runtimeBinding }) => { |
|||
return v === `{{ ${runtimeBinding} }}` |
|||
}) |
|||
if (binding) { |
|||
temp = temp.replace(v, `{{ ${binding.readableBinding} }}`) |
|||
} |
|||
}) |
|||
|
|||
return temp |
|||
} |
|||
|
After Width: | Height: | Size: 412 B |
@ -1,4 +0,0 @@ |
|||
import feather from "feather-icons" |
|||
const getIcon = (icon, size) => |
|||
feather.icons[icon].toSvg({ height: size || "16", width: size || "16" }) |
|||
export default getIcon |
|||
@ -1,168 +1,220 @@ |
|||
<script> |
|||
import { store } from "builderStore" |
|||
import { Button, Select } from "@budibase/bbui" |
|||
import HandlerSelector from "./HandlerSelector.svelte" |
|||
import ActionButton from "../../common/ActionButton.svelte" |
|||
import getIcon from "../../common/icon" |
|||
import { CloseIcon } from "components/common/Icons/" |
|||
|
|||
import { TextButton, Button, Heading, DropdownMenu } from "@budibase/bbui" |
|||
import { AddIcon, ArrowDownIcon } from "components/common/Icons/" |
|||
import { EVENT_TYPE_MEMBER_NAME } from "../../common/eventHandlers" |
|||
import actionTypes from "./actions" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let event |
|||
export let eventOptions = [] |
|||
export let onClose |
|||
|
|||
let eventType = "" |
|||
let addActionButton |
|||
let addActionDropdown |
|||
let selectedAction |
|||
|
|||
let draftEventHandler = { parameters: [] } |
|||
|
|||
$: eventData = event || { handlers: [] } |
|||
$: if (!eventOptions.includes(eventType) && eventOptions.length > 0) |
|||
eventType = eventOptions[0].name |
|||
$: actions = event || [] |
|||
$: selectedActionComponent = |
|||
selectedAction && |
|||
actionTypes.find(t => t.name === selectedAction[EVENT_TYPE_MEMBER_NAME]) |
|||
.component |
|||
|
|||
const closeModal = () => { |
|||
onClose() |
|||
dispatch("close") |
|||
draftEventHandler = { parameters: [] } |
|||
eventData = { handlers: [] } |
|||
actions = [] |
|||
} |
|||
|
|||
const updateEventHandler = (updatedHandler, index) => { |
|||
eventData.handlers[index] = updatedHandler |
|||
} |
|||
|
|||
const updateDraftEventHandler = updatedHandler => { |
|||
draftEventHandler = updatedHandler |
|||
actions[index] = updatedHandler |
|||
} |
|||
|
|||
const deleteEventHandler = index => { |
|||
eventData.handlers.splice(index, 1) |
|||
eventData = eventData |
|||
const deleteAction = index => { |
|||
actions.splice(index, 1) |
|||
actions = actions |
|||
} |
|||
|
|||
const createNewEventHandler = handler => { |
|||
const newHandler = handler || { |
|||
const addAction = actionType => () => { |
|||
const newAction = { |
|||
parameters: {}, |
|||
[EVENT_TYPE_MEMBER_NAME]: "", |
|||
[EVENT_TYPE_MEMBER_NAME]: actionType.name, |
|||
} |
|||
eventData.handlers.push(newHandler) |
|||
eventData = eventData |
|||
actions.push(newAction) |
|||
selectedAction = newAction |
|||
actions = actions |
|||
addActionDropdown.hide() |
|||
} |
|||
|
|||
const deleteEvent = () => { |
|||
store.setComponentProp(eventType, []) |
|||
closeModal() |
|||
const selectAction = action => () => { |
|||
selectedAction = action |
|||
} |
|||
|
|||
const saveEventData = () => { |
|||
store.setComponentProp(eventType, eventData.handlers) |
|||
dispatch("change", actions) |
|||
closeModal() |
|||
} |
|||
</script> |
|||
|
|||
<div class="container"> |
|||
<div class="body"> |
|||
<div class="heading"> |
|||
<h3> |
|||
{eventData.name ? `${eventData.name} Event` : 'Create a New Component Event'} |
|||
</h3> |
|||
<div class="root"> |
|||
|
|||
<div class="header"> |
|||
<Heading small dark>Actions</Heading> |
|||
<div bind:this={addActionButton}> |
|||
<TextButton text small blue on:click={addActionDropdown.show}> |
|||
Add Action |
|||
<div style="height: 20px; width: 20px;"> |
|||
<AddIcon /> |
|||
</div> |
|||
</TextButton> |
|||
</div> |
|||
<div class="event-options"> |
|||
<div class="section"> |
|||
<h4>Event Type</h4> |
|||
<Select bind:value={eventType}> |
|||
{#each eventOptions as option} |
|||
<option value={option.name}>{option.name}</option> |
|||
{/each} |
|||
</Select> |
|||
<DropdownMenu |
|||
bind:this={addActionDropdown} |
|||
anchor={addActionButton} |
|||
align="right"> |
|||
<div class="available-actions-container"> |
|||
{#each actionTypes as actionType} |
|||
<div class="available-action" on:click={addAction(actionType)}> |
|||
<span>{actionType.name}</span> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
</div> |
|||
</DropdownMenu> |
|||
</div> |
|||
|
|||
<div class="section"> |
|||
<h4>Event Action(s)</h4> |
|||
<HandlerSelector |
|||
newHandler |
|||
onChanged={updateDraftEventHandler} |
|||
onCreate={() => { |
|||
createNewEventHandler(draftEventHandler) |
|||
draftEventHandler = { parameters: [] } |
|||
}} |
|||
handler={draftEventHandler} /> |
|||
</div> |
|||
{#if eventData} |
|||
{#each eventData.handlers as handler, index} |
|||
<HandlerSelector |
|||
{index} |
|||
onChanged={updateEventHandler} |
|||
onRemoved={() => deleteEventHandler(index)} |
|||
{handler} /> |
|||
<div class="actions-container"> |
|||
{#if actions && actions.length > 0} |
|||
{#each actions as action, index} |
|||
<div class="action-container"> |
|||
<div class="action-header" on:click={selectAction(action)}> |
|||
<p |
|||
class="bb-body bb-body--small bb-body--color-dark" |
|||
style="margin: var(--spacing-s) 0;"> |
|||
{index + 1}. {action[EVENT_TYPE_MEMBER_NAME]} |
|||
</p> |
|||
<div class="row-expander" class:rotate={action !== selectedAction}> |
|||
<ArrowDownIcon /> |
|||
</div> |
|||
</div> |
|||
{#if action === selectedAction} |
|||
<div class="selected-action-container"> |
|||
<svelte:component |
|||
this={selectedActionComponent} |
|||
parameters={selectedAction.parameters} /> |
|||
<div class="delete-action-button"> |
|||
<TextButton text medium on:click={() => deleteAction(index)}> |
|||
Delete |
|||
</TextButton> |
|||
</div> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
{/each} |
|||
{/if} |
|||
|
|||
</div> |
|||
|
|||
<div class="footer"> |
|||
{#if eventData.name} |
|||
<Button |
|||
outline |
|||
on:click={deleteEvent} |
|||
disabled={eventData.handlers.length === 0}> |
|||
Delete |
|||
</Button> |
|||
{/if} |
|||
<div class="save"> |
|||
<Button |
|||
primary |
|||
on:click={saveEventData} |
|||
disabled={eventData.handlers.length === 0}> |
|||
Save |
|||
</Button> |
|||
</div> |
|||
</div> |
|||
<div class="close-button" on:click={closeModal}> |
|||
<CloseIcon /> |
|||
<a href="https://docs.budibase.com">Learn more about Actions</a> |
|||
<Button secondary on:click={closeModal}>Cancel</Button> |
|||
<Button primary on:click={saveEventData}>Save</Button> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.container { |
|||
position: relative; |
|||
.root { |
|||
max-height: 50vh; |
|||
width: 700px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
.heading { |
|||
margin-bottom: 20px; |
|||
|
|||
.header { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: var(--spacing-xl); |
|||
padding-bottom: 0; |
|||
} |
|||
|
|||
.close-button { |
|||
.action-header { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: center; |
|||
} |
|||
|
|||
.action-header > p { |
|||
flex: 1; |
|||
} |
|||
|
|||
.row-expander { |
|||
height: 30px; |
|||
width: 30px; |
|||
} |
|||
|
|||
.available-action { |
|||
padding: var(--spacing-s); |
|||
font-size: var(--font-size-m); |
|||
cursor: pointer; |
|||
position: absolute; |
|||
top: 20px; |
|||
right: 20px; |
|||
} |
|||
.close-button :global(svg) { |
|||
width: 24px; |
|||
height: 24px; |
|||
|
|||
.available-action:hover { |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
h4 { |
|||
margin-bottom: 10px; |
|||
.actions-container { |
|||
flex: 1; |
|||
min-height: 0px; |
|||
padding-bottom: var(--spacing-s); |
|||
padding-top: 0; |
|||
border: var(--border-light); |
|||
border-width: 0 0 1px 0; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
h3 { |
|||
margin: 0; |
|||
font-size: 24px; |
|||
font-weight: bold; |
|||
.action-container { |
|||
border: var(--border-light); |
|||
border-width: 1px 0 0 0; |
|||
padding-left: var(--spacing-xl); |
|||
padding-right: var(--spacing-xl); |
|||
padding-top: 0; |
|||
padding-bottom: 0; |
|||
} |
|||
.body { |
|||
padding: 40px; |
|||
display: grid; |
|||
grid-gap: 20px; |
|||
|
|||
.selected-action-container { |
|||
padding-bottom: var(--spacing-s); |
|||
padding-top: var(--spacing-s); |
|||
} |
|||
.footer { |
|||
|
|||
.delete-action-button { |
|||
padding-top: var(--spacing-l); |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
padding: 30px 40px; |
|||
border-bottom-left-radius: 5px; |
|||
border-bottom-right-radius: 50px; |
|||
background-color: var(--grey-1); |
|||
flex-direction: row; |
|||
} |
|||
.save { |
|||
margin-left: 20px; |
|||
|
|||
.footer { |
|||
display: flex; |
|||
flex-direction: row; |
|||
gap: var(--spacing-s); |
|||
padding: var(--spacing-xl); |
|||
padding-top: var(--spacing-m); |
|||
} |
|||
|
|||
.footer > a { |
|||
flex: 1; |
|||
color: var(--grey-5); |
|||
font-size: var(--font-size-s); |
|||
text-decoration: none; |
|||
} |
|||
|
|||
.footer > a:hover { |
|||
color: var(--blue); |
|||
} |
|||
|
|||
.rotate :global(svg) { |
|||
transform: rotate(90deg); |
|||
} |
|||
</style> |
|||
|
|||
@ -0,0 +1,75 @@ |
|||
<script> |
|||
import { Select, Label } from "@budibase/bbui" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
import SaveFields from "./SaveFields.svelte" |
|||
|
|||
export let parameters |
|||
|
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
models: $backendUiStore.models, |
|||
}) |
|||
|
|||
// just wraps binding in {{ ... }} |
|||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}` |
|||
|
|||
const modelFields = modelId => { |
|||
const model = $backendUiStore.models.find(m => m._id === modelId) |
|||
|
|||
return Object.keys(model.schema).map(k => ({ |
|||
name: k, |
|||
type: model.schema[k].type, |
|||
})) |
|||
} |
|||
|
|||
$: schemaFields = |
|||
parameters && parameters.modelId ? modelFields(parameters.modelId) : [] |
|||
|
|||
const onFieldsChanged = e => { |
|||
parameters.fields = e.detail |
|||
} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<Label size="m" color="dark">Table</Label> |
|||
<Select secondary bind:value={parameters.modelId}> |
|||
<option value="" /> |
|||
{#each $backendUiStore.models as model} |
|||
<option value={model._id}>{model.name}</option> |
|||
{/each} |
|||
</Select> |
|||
|
|||
{#if parameters.modelId} |
|||
<SaveFields |
|||
parameterFields={parameters.fields} |
|||
{schemaFields} |
|||
on:fieldschanged={onFieldsChanged} /> |
|||
{/if} |
|||
|
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: grid; |
|||
column-gap: var(--spacing-s); |
|||
row-gap: var(--spacing-s); |
|||
grid-template-columns: auto 1fr auto 1fr auto; |
|||
align-items: baseline; |
|||
} |
|||
|
|||
.root :global(.relative:nth-child(2)) { |
|||
grid-column-start: 2; |
|||
grid-column-end: 6; |
|||
} |
|||
|
|||
.cannot-use { |
|||
color: var(--red); |
|||
font-size: var(--font-size-s); |
|||
text-align: center; |
|||
width: 70%; |
|||
margin: auto; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,29 @@ |
|||
<script> |
|||
import { Select, Label } from "@budibase/bbui" |
|||
import { store } from "builderStore" |
|||
|
|||
export let parameters |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<Label size="m" color="dark">Screen</Label> |
|||
<Select secondary bind:value={parameters.url}> |
|||
<option value="" /> |
|||
{#each $store.screens as screen} |
|||
<option value={screen.route}>{screen.props._instanceName}</option> |
|||
{/each} |
|||
</Select> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: flex; |
|||
flex-direction: row; |
|||
align-items: baseline; |
|||
} |
|||
|
|||
.root :global(.relative) { |
|||
flex: 1; |
|||
margin-left: var(--spacing-l); |
|||
} |
|||
</style> |
|||
@ -0,0 +1,119 @@ |
|||
<script> |
|||
// accepts an array of field names, and outputs an object of { FieldName: value } |
|||
import { Select, Label, TextButton, Spacer } from "@budibase/bbui" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
import { CloseCircleIcon, AddIcon } from "components/common/Icons" |
|||
import { |
|||
readableToRuntimeBinding, |
|||
runtimeToReadableBinding, |
|||
} from "builderStore/replaceBindings" |
|||
import { createEventDispatcher } from "svelte" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
export let parameterFields |
|||
export let schemaFields |
|||
|
|||
const emptyField = () => ({ name: "", value: "" }) |
|||
|
|||
// this statement initialises fields from parameters.fields |
|||
$: fields = |
|||
fields || |
|||
Object.keys(parameterFields || { "": "" }).map(name => ({ |
|||
name, |
|||
value: |
|||
(parameterFields && |
|||
runtimeToReadableBinding( |
|||
bindableProperties, |
|||
parameterFields[name].value |
|||
)) || |
|||
"", |
|||
})) |
|||
|
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
models: $backendUiStore.models, |
|||
}) |
|||
|
|||
const addField = () => { |
|||
const newFields = fields.filter(f => f.name) |
|||
newFields.push(emptyField()) |
|||
fields = newFields |
|||
rebuildParameters() |
|||
} |
|||
|
|||
const removeField = field => () => { |
|||
fields = fields.filter(f => f !== field) |
|||
rebuildParameters() |
|||
} |
|||
|
|||
const rebuildParameters = () => { |
|||
// rebuilds paramters.fields every time a field name or value is added |
|||
// as UI below is bound to "fields" array, but we need to output a { key: value } |
|||
const newParameterFields = {} |
|||
for (let field of fields) { |
|||
if (field.name) { |
|||
// value and type is needed by the client, so it can parse |
|||
// a string into a correct type |
|||
newParameterFields[field.name] = { |
|||
type: schemaFields.find(f => f.name === field.name).type, |
|||
value: readableToRuntimeBinding(bindableProperties, field.value), |
|||
} |
|||
} |
|||
} |
|||
dispatch("fieldschanged", newParameterFields) |
|||
} |
|||
|
|||
// just wraps binding in {{ ... }} |
|||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}` |
|||
</script> |
|||
|
|||
{#if fields} |
|||
{#each fields as field} |
|||
<Label size="m" color="dark">Field</Label> |
|||
<Select secondary bind:value={field.name} on:blur={rebuildParameters}> |
|||
<option value="" /> |
|||
{#each schemaFields as schemaField} |
|||
<option value={schemaField.name}>{schemaField.name}</option> |
|||
{/each} |
|||
</Select> |
|||
<Label size="m" color="dark">Value</Label> |
|||
<Select |
|||
editable |
|||
secondary |
|||
bind:value={field.value} |
|||
on:blur={rebuildParameters}> |
|||
<option value="" /> |
|||
{#each bindableProperties as bindableProp} |
|||
<option value={toBindingExpression(bindableProp.readableBinding)}> |
|||
{bindableProp.readableBinding} |
|||
</option> |
|||
{/each} |
|||
</Select> |
|||
<div class="remove-field-container"> |
|||
<TextButton text small on:click={removeField(field)}> |
|||
<CloseCircleIcon /> |
|||
</TextButton> |
|||
</div> |
|||
{/each} |
|||
|
|||
<div> |
|||
<Spacer small /> |
|||
|
|||
<TextButton text small blue on:click={addField}> |
|||
Add Field |
|||
<div style="height: 20px; width: 20px;"> |
|||
<AddIcon /> |
|||
</div> |
|||
</TextButton> |
|||
</div> |
|||
{/if} |
|||
|
|||
<style> |
|||
.remove-field-container :global(button) { |
|||
vertical-align: bottom; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,134 @@ |
|||
<script> |
|||
import { Select, Label } from "@budibase/bbui" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import fetchBindableProperties from "builderStore/fetchBindableProperties" |
|||
import SaveFields from "./SaveFields.svelte" |
|||
import { |
|||
readableToRuntimeBinding, |
|||
runtimeToReadableBinding, |
|||
} from "builderStore/replaceBindings" |
|||
|
|||
export let parameters |
|||
|
|||
$: bindableProperties = fetchBindableProperties({ |
|||
componentInstanceId: $store.currentComponentInfo._id, |
|||
components: $store.components, |
|||
screen: $store.currentPreviewItem, |
|||
models: $backendUiStore.models, |
|||
}) |
|||
|
|||
let idFields |
|||
let recordId |
|||
$: { |
|||
idFields = bindableProperties.filter( |
|||
bindable => |
|||
bindable.type === "context" && bindable.runtimeBinding.endsWith("._id") |
|||
) |
|||
// ensure recordId is always defaulted - there is usually only one option |
|||
if (idFields.length > 0 && !parameters._id) { |
|||
recordId = idFields[0].runtimeBinding |
|||
parameters = parameters |
|||
} else if (!recordId && parameters._id) { |
|||
recordId = parameters._id |
|||
.replace("{{", "") |
|||
.replace("}}", "") |
|||
.trim() |
|||
} |
|||
} |
|||
|
|||
$: parameters._id = `{{ ${recordId} }}` |
|||
|
|||
// just wraps binding in {{ ... }} |
|||
const toBindingExpression = bindingPath => `{{ ${bindingPath} }}` |
|||
|
|||
// finds the selected idBinding, then reads the table/view |
|||
// from the component instance that it belongs to. |
|||
// then returns the field names for that schema |
|||
const schemaFromIdBinding = recordId => { |
|||
if (!recordId) return [] |
|||
|
|||
const idBinding = bindableProperties.find( |
|||
prop => prop.runtimeBinding === recordId |
|||
) |
|||
if (!idBinding) return [] |
|||
|
|||
const { instance } = idBinding |
|||
|
|||
const component = $store.components[instance._component] |
|||
|
|||
// component.context is the name of the prop that holds the modelId |
|||
const modelInfo = instance[component.context] |
|||
|
|||
if (!modelInfo) return [] |
|||
|
|||
const model = $backendUiStore.models.find(m => m._id === modelInfo.modelId) |
|||
parameters.modelId = modelInfo.modelId |
|||
return Object.keys(model.schema).map(k => ({ |
|||
name: k, |
|||
type: model.schema[k].type, |
|||
})) |
|||
} |
|||
|
|||
let schemaFields |
|||
$: { |
|||
if (parameters && recordId) { |
|||
schemaFields = schemaFromIdBinding(recordId) |
|||
} else { |
|||
schemaFields = [] |
|||
} |
|||
} |
|||
|
|||
const onFieldsChanged = e => { |
|||
parameters.fields = e.detail |
|||
} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
{#if idFields.length === 0} |
|||
<div class="cannot-use"> |
|||
Update record can only be used within a component that provides data, such |
|||
as a List |
|||
</div> |
|||
{:else} |
|||
<Label size="m" color="dark">Record Id</Label> |
|||
<Select secondary bind:value={recordId}> |
|||
<option value="" /> |
|||
{#each idFields as idField} |
|||
<option value={idField.runtimeBinding}> |
|||
{idField.readableBinding} |
|||
</option> |
|||
{/each} |
|||
</Select> |
|||
{/if} |
|||
|
|||
{#if recordId} |
|||
<SaveFields |
|||
parameterFields={parameters.fields} |
|||
{schemaFields} |
|||
on:fieldschanged={onFieldsChanged} /> |
|||
{/if} |
|||
|
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: grid; |
|||
column-gap: var(--spacing-s); |
|||
row-gap: var(--spacing-s); |
|||
grid-template-columns: auto 1fr auto 1fr auto; |
|||
align-items: baseline; |
|||
} |
|||
|
|||
.root :global(.relative:nth-child(2)) { |
|||
grid-column-start: 2; |
|||
grid-column-end: 6; |
|||
} |
|||
|
|||
.cannot-use { |
|||
color: var(--red); |
|||
font-size: var(--font-size-s); |
|||
text-align: center; |
|||
width: 70%; |
|||
margin: auto; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,23 @@ |
|||
import NavigateTo from "./NavigateTo.svelte" |
|||
import UpdateRecord from "./UpdateRecord.svelte" |
|||
import CreateRecord from "./CreateRecord.svelte" |
|||
|
|||
// defines what actions are available, when adding a new one
|
|||
// the component is the setup panel for the action
|
|||
// NOTE that the "name" is used by the client library,
|
|||
// so if you want to change it, you must change it client lib too
|
|||
|
|||
export default [ |
|||
{ |
|||
name: "Create Record", |
|||
component: CreateRecord, |
|||
}, |
|||
{ |
|||
name: "Navigate To", |
|||
component: NavigateTo, |
|||
}, |
|||
{ |
|||
name: "Update Record", |
|||
component: UpdateRecord, |
|||
}, |
|||
] |
|||
@ -1 +0,0 @@ |
|||
export { default } from "./EventsEditor.svelte" |
|||
File diff suppressed because it is too large
@ -1,17 +1,38 @@ |
|||
import renderTemplateString from "./renderTemplateString" |
|||
|
|||
export const EVENT_TYPE_MEMBER_NAME = "##eventHandlerType" |
|||
|
|||
export const eventHandlers = routeTo => { |
|||
const handler = (parameters, execute) => ({ |
|||
execute, |
|||
parameters, |
|||
}) |
|||
const handlers = { |
|||
"Navigate To": param => routeTo(param && param.url), |
|||
} |
|||
|
|||
return { |
|||
"Navigate To": handler(["url"], param => routeTo(param && param.url)), |
|||
// when an event is called, this is what gets run
|
|||
const runEventActions = async (actions, state) => { |
|||
if (!actions) return |
|||
// calls event handlers sequentially
|
|||
for (let action of actions) { |
|||
const handler = handlers[action[EVENT_TYPE_MEMBER_NAME]] |
|||
const parameters = createParameters(action.parameters, state) |
|||
if (handler) { |
|||
await handler(parameters) |
|||
} |
|||
} |
|||
} |
|||
|
|||
return runEventActions |
|||
} |
|||
|
|||
export const isEventType = prop => |
|||
Array.isArray(prop) && |
|||
prop.length > 0 && |
|||
!prop[0][EVENT_TYPE_MEMBER_NAME] === undefined |
|||
// this will take a parameters obj, iterate all keys, and do a mustache render
|
|||
// for every string. It will work recursively if it encounnters an {}
|
|||
const createParameters = (parameterTemplateObj, state) => { |
|||
const parameters = {} |
|||
for (let key in parameterTemplateObj) { |
|||
if (typeof parameterTemplateObj[key] === "string") { |
|||
parameters[key] = renderTemplateString(parameterTemplateObj[key], state) |
|||
} else if (typeof parameterTemplateObj[key] === "object") { |
|||
parameters[key] = createParameters(parameterTemplateObj[key], state) |
|||
} |
|||
} |
|||
return parameters |
|||
} |
|||
|
|||
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue