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