forked from tsai/budibase
11 changed files with 560 additions and 173 deletions
@ -0,0 +1,136 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
last |
||||
|
} from "lodash/fp"; |
||||
|
import IconButton from "../common/IconButton.svelte"; |
||||
|
import ComponentSearch from "./ComponentSearch.svelte"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
import UIkit from "uikit"; |
||||
|
import { |
||||
|
getComponentInfo |
||||
|
} from "./pagesParsing/createProps"; |
||||
|
import { store } from "../builderStore"; |
||||
|
|
||||
|
const emptyProps = () => ({_component:""}) |
||||
|
|
||||
|
export let props = emptyProps(); |
||||
|
export let onValueChanged = () => {}; |
||||
|
export let label = "" |
||||
|
export let disabled = false; |
||||
|
|
||||
|
const CHOOSE_COMPONENT = "choose_component"; |
||||
|
const CLEAR_COMPONENT = "clear_component"; |
||||
|
|
||||
|
let allComponents; |
||||
|
let modalElement; |
||||
|
let modalAction; |
||||
|
|
||||
|
store.subscribe(s => { |
||||
|
allComponents = s.allComponents; |
||||
|
}); |
||||
|
|
||||
|
$: componentSelected = props._component.length > 0; |
||||
|
$: shortName = last(props._component.split("/")); |
||||
|
|
||||
|
const chooseComponent = () => { |
||||
|
modalAction = CHOOSE_COMPONENT; |
||||
|
showDialog(); |
||||
|
} |
||||
|
|
||||
|
const clearComponent = () => { |
||||
|
modalAction = CLEAR_COMPONENT; |
||||
|
showDialog(); |
||||
|
} |
||||
|
|
||||
|
const onComponentChosen = (component) => { |
||||
|
const componentInfo = getComponentInfo(allComponents, component.name); |
||||
|
props = componentInfo.fullProps; |
||||
|
onValueChanged(props); |
||||
|
hideDialog(); |
||||
|
} |
||||
|
|
||||
|
const hideDialog = () => { |
||||
|
UIkit.modal(modalElement).hide(); |
||||
|
} |
||||
|
|
||||
|
const showDialog = () => { |
||||
|
UIkit.modal(modalElement).show(); |
||||
|
} |
||||
|
|
||||
|
const confirmClearComponent = () => { |
||||
|
props = emptyProps(); |
||||
|
onValueChanged(emptyProps()); |
||||
|
hideDialog(); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<label class="uk-form-label">{label}</label> |
||||
|
<div class="root uk-form-controls"> |
||||
|
<div class:selectedname={componentSelected}> |
||||
|
{componentSelected ? shortName : "(none)"} |
||||
|
</div> |
||||
|
<div> |
||||
|
{#if !disabled && componentSelected} |
||||
|
<IconButton icon="edit" /> |
||||
|
|
||||
|
<IconButton icon="trash" |
||||
|
on:click={clearComponent} /> |
||||
|
{:else if !disabled && !componentSelected} |
||||
|
<IconButton icon="plus" |
||||
|
on:click={chooseComponent} /> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div bind:this={modalElement} uk-modal> |
||||
|
<div class="uk-modal-dialog"> |
||||
|
|
||||
|
{#if modalAction === CHOOSE_COMPONENT} |
||||
|
<div class="uk-modal-body"> |
||||
|
<ComponentSearch {onComponentChosen} /> |
||||
|
</div> |
||||
|
{:else if modalAction === CLEAR_COMPONENT} |
||||
|
<div class="uk-modal-body"> |
||||
|
Clear this component ? |
||||
|
</div> |
||||
|
<div class="uk-modal-footer"> |
||||
|
<ButtonGroup> |
||||
|
<Button grouped |
||||
|
on:click={hideDialog} |
||||
|
color="secondary" >Cancel</Button> |
||||
|
<Button grouped |
||||
|
on:click={confirmClearComponent}>OK</Button> |
||||
|
</ButtonGroup> |
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
display: grid; |
||||
|
grid-template-columns: [name] 1fr [actions] auto; |
||||
|
} |
||||
|
|
||||
|
.root > div:nth-child(1) { |
||||
|
grid-column-start: name; |
||||
|
color: var(--secondary50); |
||||
|
} |
||||
|
|
||||
|
.root > div:nth-child(2) { |
||||
|
grid-column-start: actions; |
||||
|
} |
||||
|
|
||||
|
.selectedname { |
||||
|
font-weight: bold; |
||||
|
color: var(--secondary); |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,223 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import PropsView from "./PropsView.svelte"; |
||||
|
import { store } from "../builderStore"; |
||||
|
import { isRootComponent } from "./pagesParsing/searchComponents"; |
||||
|
import IconButton from "../common/IconButton.svelte"; |
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import UIkit from "uikit"; |
||||
|
import { pipe } from "../common/core"; |
||||
|
import { |
||||
|
getComponentInfo |
||||
|
} from "./pagesParsing/createProps"; |
||||
|
import Button from "../common/Button.svelte"; |
||||
|
import ButtonGroup from "../common/ButtonGroup.svelte"; |
||||
|
|
||||
|
import { |
||||
|
cloneDeep, |
||||
|
join, |
||||
|
split, |
||||
|
map, |
||||
|
keys, |
||||
|
isUndefined, |
||||
|
last |
||||
|
} from "lodash/fp"; |
||||
|
import { assign } from "lodash"; |
||||
|
|
||||
|
let component; |
||||
|
let name = ""; |
||||
|
let description = ""; |
||||
|
let tagsString = ""; |
||||
|
let nameInvalid = ""; |
||||
|
let componentDetailsExpanded = false; |
||||
|
let componentInfo; |
||||
|
let modalElement |
||||
|
let propsValidationErrors = []; |
||||
|
$: shortName = last(name.split("/")); |
||||
|
|
||||
|
store.subscribe(s => { |
||||
|
component = s.currentFrontEndItem; |
||||
|
if(!component) return; |
||||
|
name = component.name; |
||||
|
description = component.description; |
||||
|
tagsString = join(", ")(component.tags); |
||||
|
componentInfo = s.currentComponentInfo; |
||||
|
componentDetailsExpanded = s.currentComponentIsNew; |
||||
|
}); |
||||
|
|
||||
|
const save = () => { |
||||
|
|
||||
|
if(!validate()) return; |
||||
|
|
||||
|
component.name = name; |
||||
|
component.description = description; |
||||
|
component.tags = pipe(tagsString, [ |
||||
|
split(","), |
||||
|
map(s => s.trim()) |
||||
|
]); |
||||
|
|
||||
|
store.saveDerivedComponent(component); |
||||
|
} |
||||
|
|
||||
|
const deleteComponent = () => { |
||||
|
showDialog(); |
||||
|
} |
||||
|
|
||||
|
const confirmDeleteComponent = () => { |
||||
|
store.deleteDerivedComponent(component.name); |
||||
|
hideDialog(); |
||||
|
} |
||||
|
|
||||
|
const onPropsValidate = result => { |
||||
|
propsValidationErrors = result; |
||||
|
} |
||||
|
|
||||
|
const onPropsChanged = props => { |
||||
|
assign(component.props, props); |
||||
|
} |
||||
|
|
||||
|
const validate = () => { |
||||
|
const fieldInvalid = (field, err) => |
||||
|
errors[field] = err; |
||||
|
const fieldValid = field => |
||||
|
errors[field] && delete errors[field]; |
||||
|
|
||||
|
if(!name) nameInvalid = "component name i not supplied"; |
||||
|
else nameInvalid = ""; |
||||
|
|
||||
|
return (!nameInvalid && propsValidationErrors.length === 0); |
||||
|
} |
||||
|
|
||||
|
const hideDialog = () => { |
||||
|
UIkit.modal(modalElement).hide(); |
||||
|
} |
||||
|
|
||||
|
const showDialog = () => { |
||||
|
UIkit.modal(modalElement).show(); |
||||
|
} |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
<div class="title"> |
||||
|
<div>{shortName}</div> |
||||
|
<div> |
||||
|
<IconButton icon="save" |
||||
|
on:click={save} |
||||
|
color="var(--primary100)" |
||||
|
hoverColor="red"/> |
||||
|
<IconButton icon="trash" |
||||
|
on:click={deleteComponent} |
||||
|
color="var(--primary100)" |
||||
|
hoverColor="red"/> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="body"> |
||||
|
|
||||
|
<div class="section-header" on:click={() => componentDetailsExpanded = !componentDetailsExpanded}> |
||||
|
<span style="margin-right: 7px">Component Details</span> |
||||
|
<IconButton icon={componentDetailsExpanded ? "chevron-down" : "chevron-right"}/> |
||||
|
</div> |
||||
|
|
||||
|
{#if componentDetailsExpanded} |
||||
|
<div> |
||||
|
<Textbox label="Name" |
||||
|
infoText="use forward slash to store in subfolders" |
||||
|
bind:text={name} |
||||
|
hasError={!!nameInvalid}/> |
||||
|
<div class="info-text"></div> |
||||
|
<Textbox label="Description" |
||||
|
bind:text={description}/> |
||||
|
<Textbox label="Tags" |
||||
|
infoText="comma separated" |
||||
|
bind:text={tagsString}/> |
||||
|
|
||||
|
</div> |
||||
|
{/if} |
||||
|
|
||||
|
<p class="section-header"><span>Properties</span></p> |
||||
|
|
||||
|
|
||||
|
<PropsView onValidate={onPropsValidate} |
||||
|
{componentInfo} |
||||
|
{onPropsChanged}/> |
||||
|
|
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
|
||||
|
<div bind:this={modalElement} uk-modal> |
||||
|
<div class="uk-modal-dialog"> |
||||
|
|
||||
|
<div class="uk-modal-header"> |
||||
|
Delete {component.name} ? |
||||
|
</div> |
||||
|
|
||||
|
<div class="uk-modal-body"> |
||||
|
Are you sure you want to delete this component ? |
||||
|
</div> |
||||
|
|
||||
|
<div class="uk-modal-footer"> |
||||
|
<ButtonGroup> |
||||
|
<Button grouped |
||||
|
on:click={confirmDeleteComponent}> |
||||
|
OK |
||||
|
</Button> |
||||
|
<Button grouped |
||||
|
on:click={hideDialog} |
||||
|
color="secondary" > |
||||
|
Cancel |
||||
|
</Button> |
||||
|
</ButtonGroup> |
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
height: 100%; |
||||
|
border-style: solid; |
||||
|
border-color: var(--lightslate); |
||||
|
border-width: 0px 0px 0px 1px; |
||||
|
} |
||||
|
|
||||
|
.body { |
||||
|
padding: 10px; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
background-color: white; |
||||
|
padding: 3px; |
||||
|
display: grid; |
||||
|
grid-template-columns: [name] 1fr [actions] auto; |
||||
|
} |
||||
|
|
||||
|
.title > div:nth-child(1) { |
||||
|
grid-column-start: name; |
||||
|
color: var(--secondary100); |
||||
|
} |
||||
|
|
||||
|
.title > div:nth-child(2) { |
||||
|
grid-column-start: actions; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
font-style: italic; |
||||
|
color: var(--slate); |
||||
|
border-style: solid; |
||||
|
border-color: var(--lightslate); |
||||
|
border-width: 0px 0px 1px 0px; |
||||
|
} |
||||
|
|
||||
|
.section-header { |
||||
|
vertical-align: middle; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
@ -0,0 +1,54 @@ |
|||||
|
<script> |
||||
|
|
||||
|
import Checkbox from "../common/Checkbox.svelte"; |
||||
|
import Textbox from "../common/Textbox.svelte"; |
||||
|
import Dropdown from "../common/Dropdown.svelte"; |
||||
|
import ComponentPropSelector from "./ComponentPropSelector.svelte"; |
||||
|
|
||||
|
export let errors = []; |
||||
|
export let setProp = () => {}; |
||||
|
export let fieldHasError =() => {}; |
||||
|
export let propDef = {}; |
||||
|
export let props = {}; |
||||
|
export let disabled; |
||||
|
|
||||
|
</script> |
||||
|
|
||||
|
|
||||
|
<div class="root"> |
||||
|
|
||||
|
|
||||
|
{#if propDef.type === "bool"} |
||||
|
<Checkbox label={propDef.____name} |
||||
|
checked={props[propDef.____name]} |
||||
|
on:change={ev => setProp(propDef.____name, ev.target.checked)} |
||||
|
hasError={fieldHasError(propDef.____name)} /> |
||||
|
{:else if propDef.type === "options"} |
||||
|
<Dropdown label={propDef.____name} |
||||
|
selected={props[propDef.____name]} |
||||
|
options={propDef.options} |
||||
|
on:change={ev => setProp(propDef.____name, ev.target.value)} |
||||
|
hasError={fieldHasError(propDef.____name)}/> |
||||
|
{:else if propDef.type === "component"} |
||||
|
<ComponentPropSelector label={propDef.____name} |
||||
|
props={props[propDef.____name]} |
||||
|
{disabled} |
||||
|
onValueChanged={props => setProp(propDef.____name, props)}/> |
||||
|
{:else} |
||||
|
<Textbox label={propDef.____name} |
||||
|
text={props[propDef.____name]} |
||||
|
on:change={ev => setProp(propDef.____name, ev.target.value)} |
||||
|
margin={false} |
||||
|
hasError={fieldHasError(propDef.____name)} |
||||
|
{disabled}/> |
||||
|
{/if} |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
|
||||
|
.root { |
||||
|
margin-top: 7px; |
||||
|
} |
||||
|
|
||||
|
</style> |
||||
Loading…
Reference in new issue