forked from tsai/budibase
committed by
GitHub
43 changed files with 484 additions and 2899 deletions
@ -1,59 +0,0 @@ |
|||
<script> |
|||
export let title |
|||
export let icon |
|||
|
|||
export let primary |
|||
export let secondary |
|||
export let tertiary |
|||
</script> |
|||
|
|||
<div on:click class:primary class:secondary class:tertiary> |
|||
<i class={icon} /> |
|||
<span>{title}</span> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
height: 80px; |
|||
border-radius: 5px; |
|||
color: var(--ink); |
|||
font-weight: 400; |
|||
padding: 15px; |
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
transition: 0.3s transform; |
|||
background: var(--grey-1); |
|||
} |
|||
|
|||
i { |
|||
font-size: 24px; |
|||
color: var(--grey-7); |
|||
} |
|||
|
|||
span { |
|||
font-size: 14px; |
|||
text-align: center; |
|||
margin-top: 8px; |
|||
line-height: 1.25; |
|||
} |
|||
|
|||
div:hover { |
|||
cursor: pointer; |
|||
background: var(--grey-2); |
|||
} |
|||
|
|||
.primary { |
|||
background: var(--ink); |
|||
color: var(--white); |
|||
} |
|||
|
|||
.secondary { |
|||
background: var(--blue-light); |
|||
} |
|||
|
|||
.tertiary { |
|||
background: var(--white); |
|||
} |
|||
</style> |
|||
@ -1,20 +0,0 @@ |
|||
<script> |
|||
import { JavaScriptIcon } from "../common/Icons" |
|||
// todo: use https://ace.c9.io |
|||
export let text = "" |
|||
</script> |
|||
|
|||
<textarea class="uk-textarea" bind:value={text} /> |
|||
|
|||
<style> |
|||
textarea { |
|||
padding: 10px; |
|||
margin-top: 5px; |
|||
margin-bottom: 10px; |
|||
background: var(--grey-7); |
|||
color: var(--white); |
|||
font-family: "Courier New", Courier, monospace; |
|||
height: 200px; |
|||
border-radius: 5px; |
|||
} |
|||
</style> |
|||
@ -1,13 +0,0 @@ |
|||
<script> |
|||
export let name = "" |
|||
</script> |
|||
|
|||
<div> |
|||
<h4>Coming Sometime: {name}</h4> |
|||
</div> |
|||
|
|||
<style> |
|||
h4 { |
|||
margin-top: 20px; |
|||
} |
|||
</style> |
|||
@ -1,45 +0,0 @@ |
|||
<script> |
|||
import { createEventDispatcher } from "svelte" |
|||
import Select from "../common/Select.svelte" |
|||
|
|||
export let selected |
|||
export let label |
|||
export let options |
|||
export let valueMember |
|||
export let textMember |
|||
export let multiple = false |
|||
export let width = "medium" |
|||
export let size = "small" |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
</script> |
|||
|
|||
<div class="uk-margin"> |
|||
<label class="uk-form-label">{label}</label> |
|||
<div class="uk-form-controls"> |
|||
{#if multiple} |
|||
<Select |
|||
class="uk-select uk-form-width-{width} uk-form-{size}" |
|||
multiple |
|||
bind:value={selected} |
|||
on:change> |
|||
{#each options as option} |
|||
<option value={!valueMember ? option : valueMember(option)}> |
|||
{!textMember ? option : textMember(option)} |
|||
</option> |
|||
{/each} |
|||
</Select> |
|||
{:else} |
|||
<Select |
|||
class="uk-select uk-form-width-{width} uk-form-{size}" |
|||
bind:value={selected} |
|||
on:change> |
|||
{#each options as option} |
|||
<option value={!valueMember ? option : valueMember(option)}> |
|||
{!textMember ? option : textMember(option)} |
|||
</option> |
|||
{/each} |
|||
</Select> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
@ -1,23 +0,0 @@ |
|||
<button on:click> |
|||
<slot>+</slot> |
|||
</button> |
|||
|
|||
<style> |
|||
button { |
|||
cursor: pointer; |
|||
outline: none; |
|||
border: none; |
|||
border-radius: 5px; |
|||
min-width: 1.8rem; |
|||
min-height: 1.8rem; |
|||
padding-bottom: 10px; |
|||
|
|||
display: flex; |
|||
justify-content: center; |
|||
align-items: center; |
|||
|
|||
font-size: 1.2rem; |
|||
font-weight: 600; |
|||
color: var(--ink); |
|||
} |
|||
</style> |
|||
@ -1,33 +0,0 @@ |
|||
<script> |
|||
export let text = "" |
|||
export let label = "" |
|||
export let width = "medium" |
|||
export let size = "small" |
|||
export let margin = true |
|||
export let infoText = "" |
|||
export let hasError = false |
|||
export let disabled = false |
|||
</script> |
|||
|
|||
<div class:uk-margin={margin}> |
|||
<label class="uk-form-label">{label}</label> |
|||
<div class="uk-form-controls"> |
|||
<input |
|||
data-cy={label} |
|||
class="budibase__input" |
|||
class:uk-form-danger={hasError} |
|||
on:change |
|||
bind:value={text} |
|||
{disabled} /> |
|||
</div> |
|||
{#if infoText} |
|||
<div class="info-text">{infoText}</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.info-text { |
|||
font-size: 0.7rem; |
|||
color: var(--secondary50); |
|||
} |
|||
</style> |
|||
@ -1,16 +0,0 @@ |
|||
<script> |
|||
import RecordCard from "./RecordCard.svelte" |
|||
|
|||
export let record = {} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<div class="title">{record.name}</div> |
|||
<div class="inner"> |
|||
<div class="node-path">{record.nodeKey()}</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
@ -1,127 +0,0 @@ |
|||
<script> |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import CodeArea from "components/common/CodeArea.svelte" |
|||
import Button from "components/common/Button.svelte" |
|||
import Dropdown from "components/common/Dropdown.svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { filter, some, map, compose } from "lodash/fp" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import api from "builderStore/api" |
|||
|
|||
const SNIPPET_EDITORS = { |
|||
MAP: "Map", |
|||
FILTER: "Filter", |
|||
REDUCE: "Reduce", |
|||
} |
|||
|
|||
const COUCHDB_FUNCTION = `function(doc) { |
|||
|
|||
}` |
|||
|
|||
export let onClosed |
|||
export let view = {} |
|||
|
|||
let currentSnippetEditor = SNIPPET_EDITORS.MAP |
|||
|
|||
$: instanceId = $backendUiStore.selectedDatabase._id |
|||
|
|||
function deleteView() {} |
|||
|
|||
async function saveView() { |
|||
const SAVE_VIEW_URL = `/api/views` |
|||
const response = await api.post(SAVE_VIEW_URL, view) |
|||
backendUiStore.update(state => { |
|||
state.views = [...state.views, response.view] |
|||
return state |
|||
}) |
|||
onClosed() |
|||
} |
|||
</script> |
|||
|
|||
<div class="header"> |
|||
<i class="ri-eye-line button--toggled" /> |
|||
<h3 class="budibase__title--3">Create / Edit View</h3> |
|||
</div> |
|||
<form on:submit|preventDefault class="uk-form-stacked root"> |
|||
{#if $store.errors && $store.errors.length > 0} |
|||
<ErrorsBox errors={$store.errors} /> |
|||
{/if} |
|||
<div class="main"> |
|||
<div class="uk-grid-small" uk-grid> |
|||
<div class="uk-width-1-2@s"> |
|||
<Textbox bind:text={view.name} label="Name" /> |
|||
</div> |
|||
</div> |
|||
<div class="code-snippets"> |
|||
{#each Object.values(SNIPPET_EDITORS) as snippetType} |
|||
<span |
|||
class="snippet-selector__heading hoverable" |
|||
class:highlighted={currentSnippetEditor === snippetType} |
|||
on:click={() => (currentSnippetEditor = snippetType)}> |
|||
{snippetType} |
|||
</span> |
|||
{/each} |
|||
{#if currentSnippetEditor === SNIPPET_EDITORS.MAP} |
|||
<CodeArea bind:text={view.map} label="Map" /> |
|||
{:else if currentSnippetEditor === SNIPPET_EDITORS.FILTER} |
|||
<CodeArea bind:text={view.filter} label="Filter" /> |
|||
{:else if currentSnippetEditor === SNIPPET_EDITORS.REDUCE} |
|||
<CodeArea bind:text={view.reduce} label="Reduce" /> |
|||
{/if} |
|||
</div> |
|||
</div> |
|||
<div class="buttons"> |
|||
<div class="button"> |
|||
<ActionButton secondary on:click={deleteView}>Delete</ActionButton> |
|||
</div> |
|||
<ActionButton color="secondary" on:click={saveView}>Save</ActionButton> |
|||
</div> |
|||
</form> |
|||
|
|||
<style> |
|||
.root { |
|||
height: 100%; |
|||
} |
|||
|
|||
.highlighted { |
|||
opacity: 1; |
|||
} |
|||
|
|||
h3 { |
|||
margin: 0 0 0 10px; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.snippet-selector__heading { |
|||
margin-right: 20px; |
|||
font-size: 14px; |
|||
color: var(--grey-5); |
|||
} |
|||
|
|||
.header { |
|||
padding: 20px 40px 0 40px; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
.main { |
|||
margin: 20px 40px 0px 40px; |
|||
} |
|||
|
|||
.code-snippets { |
|||
margin: 20px 0px 20px 0px; |
|||
} |
|||
|
|||
.buttons { |
|||
display: flex; |
|||
justify-content: flex-end; |
|||
background-color: var(--grey-1); |
|||
margin: 0 40px; |
|||
padding: 20px 0; |
|||
} |
|||
|
|||
.button { |
|||
margin-right: 20px; |
|||
} |
|||
</style> |
|||
@ -1,4 +1,3 @@ |
|||
export { default as DeleteRecordModal } from "./DeleteRecord.svelte" |
|||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte" |
|||
export { default as CreateEditViewModal } from "./CreateEditView.svelte" |
|||
export { default as CreateUserModal } from "./CreateUser.svelte" |
|||
|
|||
@ -1,95 +0,0 @@ |
|||
<script> |
|||
import { tick, onMount } from "svelte" |
|||
import { goto } from "@sveltech/routify" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import { CheckIcon } from "../common/Icons" |
|||
|
|||
$: instances = $store.appInstances |
|||
|
|||
async function selectDatabase(database) { |
|||
backendUiStore.actions.database.select(database) |
|||
} |
|||
|
|||
async function deleteDatabase(database) { |
|||
const DELETE_DATABASE_URL = `/api/instances/${database.name}` |
|||
const response = await api.delete(DELETE_DATABASE_URL) |
|||
store.update(state => { |
|||
state.appInstances = state.appInstances.filter( |
|||
db => db._id !== database._id |
|||
) |
|||
return state |
|||
}) |
|||
} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<ul> |
|||
{#each $store.appInstances as database} |
|||
<li> |
|||
<span class="icon"> |
|||
{#if database._id === $backendUiStore.selectedDatabase._id} |
|||
<CheckIcon /> |
|||
{/if} |
|||
</span> |
|||
<button |
|||
class:active={database._id === $backendUiStore.selectedDatabase._id} |
|||
on:click={() => { |
|||
$goto(`./database/${database._id}`), selectDatabase(database) |
|||
}}> |
|||
{database.name} |
|||
</button> |
|||
<i |
|||
class="ri-delete-bin-7-line hoverable alignment" |
|||
on:click={() => deleteDatabase(database)} /> |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
font-size: 13px; |
|||
color: var(--ink); |
|||
position: relative; |
|||
padding-left: 20px; |
|||
} |
|||
|
|||
ul { |
|||
margin: 0; |
|||
padding: 0; |
|||
list-style: none; |
|||
} |
|||
|
|||
.alignment { |
|||
margin-left: auto; |
|||
padding-right: 20px; |
|||
} |
|||
|
|||
li { |
|||
margin: 0px 0px 10px 0px; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
|
|||
button { |
|||
margin: 0 0 0 6px; |
|||
padding: 0; |
|||
border: none; |
|||
font-size: 13px; |
|||
outline: none; |
|||
cursor: pointer; |
|||
background: rgba(0, 0, 0, 0); |
|||
text-rendering: optimizeLegibility; |
|||
} |
|||
|
|||
.active { |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.icon { |
|||
display: inline-block; |
|||
width: 14px; |
|||
color: #333; |
|||
} |
|||
</style> |
|||
@ -1,45 +0,0 @@ |
|||
<script> |
|||
import { getContext } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import { cloneDeep } from "lodash/fp" |
|||
import getIcon from "../common/icon" |
|||
import { CreateEditViewModal } from "components/database/ModelDataTable/modals" |
|||
import api from "builderStore/api" |
|||
|
|||
const { open, close } = getContext("simple-modal") |
|||
|
|||
export let node |
|||
export let type |
|||
export let onSelect |
|||
|
|||
let navActive = "" |
|||
|
|||
const ICON_MAP = { |
|||
index: "ri-eye-line", |
|||
model: "ri-list-settings-line", |
|||
} |
|||
</script> |
|||
|
|||
<div> |
|||
<div |
|||
on:click={() => onSelect(node)} |
|||
class="budibase__nav-item hierarchy-item" |
|||
class:capitalized={type === 'model'} |
|||
class:selected={$backendUiStore.selectedView === `all_${node._id}`}> |
|||
<i class={ICON_MAP[type]} /> |
|||
<span style="margin-left: 1rem">{node.name}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.hierarchy-item { |
|||
font-size: 13px; |
|||
font-weight: 400; |
|||
margin-bottom: 10px; |
|||
padding-left: 20px; |
|||
} |
|||
|
|||
.capitalized { |
|||
text-transform: capitalize; |
|||
} |
|||
</style> |
|||
@ -1,120 +0,0 @@ |
|||
<script> |
|||
import { backendUiStore } from "builderStore" |
|||
import { Button } from "@budibase/bbui" |
|||
import Dropdown from "components/common/Dropdown.svelte" |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import ButtonGroup from "components/common/ButtonGroup.svelte" |
|||
import NumberBox from "components/common/NumberBox.svelte" |
|||
import ValuesList from "components/common/ValuesList.svelte" |
|||
import ErrorsBox from "components/common/ErrorsBox.svelte" |
|||
import Checkbox from "components/common/Checkbox.svelte" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import DatePicker from "components/common/DatePicker.svelte" |
|||
import { keys, cloneDeep } from "lodash/fp" |
|||
|
|||
const FIELD_TYPES = ["string", "number", "boolean", "link"] |
|||
|
|||
let field = {} |
|||
|
|||
$: field = |
|||
$backendUiStore.draftModel.schema[$backendUiStore.selectedField] || {} |
|||
$: required = |
|||
field.constraints && |
|||
field.constraints.presence && |
|||
!field.constraints.presence.allowEmpty |
|||
</script> |
|||
|
|||
<div class="info"> |
|||
<div class="field-box"> |
|||
<header>Name</header> |
|||
<input class="budibase__input" type="text" bind:value={field.name} /> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="info"> |
|||
<div class="field-box"> |
|||
<header>Type</header> |
|||
<span>{field.type}</span> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="info"> |
|||
<div class="field"> |
|||
<label>Required</label> |
|||
<input |
|||
type="checkbox" |
|||
bind:checked={required} |
|||
on:change={() => (field.constraints.presence.allowEmpty = required)} /> |
|||
</div> |
|||
|
|||
{#if field.type === 'string'} |
|||
<NumberBox |
|||
label="Max Length" |
|||
bind:value={field.constraints.length.maximum} /> |
|||
<ValuesList label="Categories" bind:values={field.constraints.inclusion} /> |
|||
{:else if field.type === 'datetime'} |
|||
<DatePicker |
|||
label="Min Value" |
|||
bind:value={field.constraints.datetime.earliest} /> |
|||
<DatePicker |
|||
label="Max Value" |
|||
bind:value={field.constraints.datetime.latest} /> |
|||
{:else if field.type === 'number'} |
|||
<NumberBox |
|||
label="Min Value" |
|||
bind:value={field.constraints.numericality.greaterThanOrEqualTo} /> |
|||
<NumberBox |
|||
label="Max Value" |
|||
bind:value={field.constraints.numericality.lessThanOrEqualTo} /> |
|||
{:else if field.type === 'link'} |
|||
<div class="field"> |
|||
<label>Link</label> |
|||
<select class="budibase__input" bind:value={field.modelId}> |
|||
<option value={''} /> |
|||
{#each $backendUiStore.models as model} |
|||
{#if model._id !== $backendUiStore.draftModel._id} |
|||
<option value={model._id}>{model.name}</option> |
|||
{/if} |
|||
{/each} |
|||
</select> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.info { |
|||
margin-bottom: 16px; |
|||
border-radius: 5px; |
|||
} |
|||
|
|||
label { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.field { |
|||
display: grid; |
|||
align-items: center; |
|||
margin-bottom: 16px; |
|||
} |
|||
|
|||
.field-box header { |
|||
font-size: 14px; |
|||
font-weight: 500; |
|||
margin-bottom: 8px; |
|||
} |
|||
|
|||
.field-box span { |
|||
background: var(--grey-2); |
|||
color: var(--grey-6); |
|||
font-weight: 400; |
|||
height: 36px; |
|||
display: grid; |
|||
align-items: center; |
|||
padding-left: 12px; |
|||
text-transform: capitalize; |
|||
border-radius: 5px; |
|||
cursor: not-allowed; |
|||
} |
|||
</style> |
|||
@ -1,156 +0,0 @@ |
|||
<script> |
|||
import { getContext, onMount } from "svelte" |
|||
import { Button, Switcher } from "@budibase/bbui" |
|||
import { notifier } from "builderStore/store/notifications" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import ModelFieldEditor from "./ModelFieldEditor.svelte" |
|||
|
|||
const { open, close } = getContext("simple-modal") |
|||
|
|||
const ITEMS = [ |
|||
{ |
|||
title: "Setup", |
|||
key: "SETUP", |
|||
}, |
|||
{ |
|||
title: "Delete", |
|||
key: "DELETE", |
|||
}, |
|||
] |
|||
|
|||
let edited = false |
|||
|
|||
$: selectedTab = $backendUiStore.tabs.SETUP_PANEL |
|||
|
|||
$: edited = |
|||
$backendUiStore.selectedField || |
|||
($backendUiStore.draftModel && |
|||
$backendUiStore.draftModel.name !== $backendUiStore.selectedModel.name) |
|||
|
|||
async function deleteModel() { |
|||
const model = $backendUiStore.selectedModel |
|||
const field = $backendUiStore.selectedField |
|||
|
|||
if (field) { |
|||
const name = model.schema[field].name |
|||
delete model.schema[field] |
|||
backendUiStore.actions.models.save(model) |
|||
notifier.danger(`Field ${name} deleted.`) |
|||
return |
|||
} |
|||
|
|||
const DELETE_MODEL_URL = `/api/models/${model._id}/${model._rev}` |
|||
const response = await api.delete(DELETE_MODEL_URL) |
|||
backendUiStore.update(state => { |
|||
state.selectedView = null |
|||
state.selectedModel = {} |
|||
state.draftModel = {} |
|||
state.models = state.models.filter(({ _id }) => _id !== model._id) |
|||
notifier.danger(`${model.name} deleted successfully.`) |
|||
return state |
|||
}) |
|||
} |
|||
|
|||
function validate() { |
|||
let errors = [] |
|||
for (let field of Object.values($backendUiStore.draftModel.schema)) { |
|||
const restrictedFieldNames = ["type", "modelId"] |
|||
if (field.name.startsWith("_")) { |
|||
errors.push(`field '${field.name}' - name cannot begin with '_''`) |
|||
} else if (restrictedFieldNames.includes(field.name)) { |
|||
errors.push( |
|||
`field '${field.name}' - is a restricted name, please rename` |
|||
) |
|||
} else if (!field.name || !field.name.trim()) { |
|||
errors.push("field name cannot be blank") |
|||
} |
|||
} |
|||
|
|||
if (!$backendUiStore.draftModel.name) { |
|||
errors.push("Table name cannot be blank") |
|||
} |
|||
|
|||
return errors |
|||
} |
|||
|
|||
async function saveModel() { |
|||
const errors = validate() |
|||
if (errors.length > 0) { |
|||
notifier.danger(errors.join("/n")) |
|||
return |
|||
} |
|||
|
|||
await backendUiStore.actions.models.save($backendUiStore.draftModel) |
|||
notifier.success( |
|||
"Success! Your changes have been saved. Please continue on with your greatness." |
|||
) |
|||
} |
|||
</script> |
|||
|
|||
<div class="items-root"> |
|||
<Switcher headings={ITEMS} bind:value={$backendUiStore.tabs.SETUP_PANEL}> |
|||
{#if selectedTab === 'SETUP'} |
|||
{#if $backendUiStore.selectedField} |
|||
<ModelFieldEditor /> |
|||
{:else if $backendUiStore.draftModel.schema} |
|||
<div class="titled-input"> |
|||
<header>Name</header> |
|||
<input |
|||
data-cy="table-name-input" |
|||
type="text" |
|||
class="budibase__input" |
|||
bind:value={$backendUiStore.draftModel.name} /> |
|||
</div> |
|||
<!-- dont have this capability yet.. |
|||
<div class="titled-input"> |
|||
<header>Import Data</header> |
|||
<Button wide secondary>Import CSV</Button> |
|||
</div> |
|||
--> |
|||
{/if} |
|||
<footer> |
|||
<Button disabled={!edited} green={edited} wide on:click={saveModel}> |
|||
Save |
|||
</Button> |
|||
</footer> |
|||
{:else if selectedTab === 'DELETE'} |
|||
<div class="titled-input"> |
|||
<header>Danger Zone</header> |
|||
<Button red wide on:click={deleteModel}>Delete</Button> |
|||
</div> |
|||
{/if} |
|||
</Switcher> |
|||
</div> |
|||
|
|||
<style> |
|||
header { |
|||
font-weight: 500; |
|||
} |
|||
|
|||
footer { |
|||
width: 260px; |
|||
position: fixed; |
|||
bottom: 20px; |
|||
} |
|||
|
|||
.items-root { |
|||
padding: 20px; |
|||
display: flex; |
|||
flex-direction: column; |
|||
max-height: 100%; |
|||
height: 100%; |
|||
background-color: var(--white); |
|||
} |
|||
|
|||
.titled-input { |
|||
margin-bottom: 16px; |
|||
display: grid; |
|||
} |
|||
|
|||
.titled-input header { |
|||
display: block; |
|||
font-size: 14px; |
|||
margin-bottom: 8px; |
|||
} |
|||
</style> |
|||
@ -1 +0,0 @@ |
|||
export { default as ModelSetupNav } from "./ModelSetupNav.svelte" |
|||
@ -1,82 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { store, backendUiStore } from "builderStore" |
|||
import api from "builderStore/api" |
|||
import getIcon from "../common/icon" |
|||
import { CheckIcon } from "../common/Icons" |
|||
|
|||
const getPage = (s, name) => { |
|||
const props = s.pages[name] |
|||
return { name, props } |
|||
} |
|||
|
|||
$: currentAppInfo = { |
|||
name: $store.name, |
|||
} |
|||
|
|||
async function fetchUsers() { |
|||
const FETCH_USERS_URL = `/api/users` |
|||
const response = await api.get(FETCH_USERS_URL) |
|||
const users = await response.json() |
|||
backendUiStore.update(state => { |
|||
state.users = users |
|||
return state |
|||
}) |
|||
} |
|||
|
|||
onMount(fetchUsers) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<ul> |
|||
{#each $backendUiStore.users as user} |
|||
<li> |
|||
<i class="ri-user-4-line" /> |
|||
<button class:active={user.username === $store.currentUserName}> |
|||
{user.name} |
|||
</button> |
|||
</li> |
|||
{/each} |
|||
</ul> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
padding-bottom: 10px; |
|||
font-size: 0.9rem; |
|||
color: var(--secondary50); |
|||
font-weight: bold; |
|||
position: relative; |
|||
padding-left: 1.8rem; |
|||
} |
|||
|
|||
ul { |
|||
margin: 0; |
|||
padding: 0; |
|||
list-style: none; |
|||
} |
|||
|
|||
li { |
|||
margin: 0.5rem 0; |
|||
} |
|||
|
|||
button { |
|||
margin: 0 0 0 6px; |
|||
padding: 0; |
|||
border: none; |
|||
font-size: 13px; |
|||
outline: none; |
|||
cursor: pointer; |
|||
background: rgba(0, 0, 0, 0); |
|||
} |
|||
|
|||
.active { |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.icon { |
|||
display: inline-block; |
|||
width: 14px; |
|||
color: #333; |
|||
} |
|||
</style> |
|||
@ -1,111 +0,0 @@ |
|||
<script> |
|||
import { store } from "builderStore" |
|||
import UIkit from "uikit" |
|||
import ActionButton from "components/common/ActionButton.svelte" |
|||
import ButtonGroup from "components/common/ButtonGroup.svelte" |
|||
import CodeMirror from "codemirror" |
|||
import "codemirror/mode/javascript/javascript.js" |
|||
|
|||
export let onCodeChanged |
|||
export let code |
|||
|
|||
export const show = () => { |
|||
UIkit.modal(codeModal).show() |
|||
} |
|||
|
|||
let codeModal |
|||
let editor |
|||
let cmInstance |
|||
|
|||
$: currentCode = code |
|||
$: originalCode = code |
|||
$: { |
|||
if (editor) { |
|||
if (!cmInstance) { |
|||
cmInstance = CodeMirror.fromTextArea(editor, { |
|||
mode: "javascript", |
|||
lineNumbers: false, |
|||
lineWrapping: true, |
|||
smartIndent: true, |
|||
matchBrackets: true, |
|||
readOnly: false, |
|||
}) |
|||
cmInstance.on("change", () => (currentCode = cmInstance.getValue())) |
|||
} |
|||
cmInstance.focus() |
|||
cmInstance.setValue(code || "") |
|||
} |
|||
} |
|||
|
|||
const cancel = () => { |
|||
UIkit.modal(codeModal).hide() |
|||
currentCode = originalCode |
|||
} |
|||
|
|||
const save = () => { |
|||
originalCode = currentCode |
|||
onCodeChanged(currentCode) |
|||
UIkit.modal(codeModal).hide() |
|||
} |
|||
</script> |
|||
|
|||
<div bind:this={codeModal} uk-modal> |
|||
<div class="uk-modal-dialog" uk-overflow-auto> |
|||
|
|||
<div class="uk-modal-header"> |
|||
<h3>Code</h3> |
|||
</div> |
|||
|
|||
<div class="uk-modal-body uk-form-horizontal"> |
|||
|
|||
<p> |
|||
Use the code box below to control how this component is displayed, with |
|||
javascript. |
|||
</p> |
|||
|
|||
<div> |
|||
<div class="editor-code-surround"> |
|||
function(render, context, state, route) {'{'} |
|||
</div> |
|||
<div class="editor"> |
|||
<textarea bind:this={editor} /> |
|||
</div> |
|||
<div class="editor-code-surround">{'}'}</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<div class="uk-modal-footer"> |
|||
<ButtonGroup> |
|||
<ActionButton primary on:click={save}>Save</ActionButton> |
|||
<ActionButton alert on:click={cancel}>Close</ActionButton> |
|||
</ButtonGroup> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
h3 { |
|||
text-transform: uppercase; |
|||
font-size: 13px; |
|||
font-weight: 700; |
|||
color: #8997ab; |
|||
margin-bottom: 10px; |
|||
} |
|||
|
|||
p { |
|||
font-size: 13px; |
|||
color: #333; |
|||
margin-top: 0; |
|||
} |
|||
|
|||
.editor { |
|||
border-style: dotted; |
|||
border-width: 1px; |
|||
border-color: gainsboro; |
|||
padding: 10px 30px; |
|||
} |
|||
|
|||
.editor-code-surround { |
|||
font-family: "Courier New", Courier, monospace; |
|||
} |
|||
</style> |
|||
@ -1,320 +0,0 @@ |
|||
<script> |
|||
import { onMount, createEventDispatcher } from "svelte" |
|||
import { fade } from "svelte/transition" |
|||
import Swatch from "./Swatch.svelte" |
|||
import CheckedBackground from "./CheckedBackground.svelte" |
|||
import { buildStyle } from "./helpers.js" |
|||
import { |
|||
getColorFormat, |
|||
convertToHSVA, |
|||
convertHsvaToFormat, |
|||
} from "./utils.js" |
|||
import Slider from "./Slider.svelte" |
|||
import Palette from "./Palette.svelte" |
|||
import ButtonGroup from "./ButtonGroup.svelte" |
|||
import Input from "./Input.svelte" |
|||
import Portal from "./Portal.svelte" |
|||
|
|||
export let value = "#3ec1d3ff" |
|||
export let open = false |
|||
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console |
|||
export let disableSwatches = false |
|||
export let format = "hexa" |
|||
export let style = "" |
|||
export let pickerHeight = 0 |
|||
export let pickerWidth = 0 |
|||
|
|||
let colorPicker = null |
|||
let adder = null |
|||
|
|||
let h = null |
|||
let s = null |
|||
let v = null |
|||
let a = null |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
onMount(() => { |
|||
if (!swatches.length > 0) { |
|||
//Don't use locally stored recent colors if swatches have been passed as props |
|||
getRecentColors() |
|||
} |
|||
|
|||
if (colorPicker) { |
|||
colorPicker.focus() |
|||
} |
|||
|
|||
if (format) { |
|||
convertAndSetHSVA() |
|||
} |
|||
}) |
|||
|
|||
function getRecentColors() { |
|||
let colorStore = localStorage.getItem("cp:recent-colors") |
|||
if (colorStore) { |
|||
swatches = JSON.parse(colorStore) |
|||
} |
|||
} |
|||
|
|||
function handleEscape(e) { |
|||
if (open && e.key === "Escape") { |
|||
open = false |
|||
} |
|||
} |
|||
|
|||
function setRecentColor(color) { |
|||
if (swatches.length === 12) { |
|||
swatches.splice(0, 1) |
|||
} |
|||
if (!swatches.includes(color)) { |
|||
swatches = [...swatches, color] |
|||
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches)) |
|||
} |
|||
} |
|||
|
|||
function convertAndSetHSVA() { |
|||
let hsva = convertToHSVA(value, format) |
|||
setHSVA(hsva) |
|||
} |
|||
|
|||
function setHSVA([hue, sat, val, alpha]) { |
|||
h = hue |
|||
s = sat |
|||
v = val |
|||
a = alpha |
|||
} |
|||
|
|||
//fired by choosing a color from the palette |
|||
function setSaturationAndValue({ detail }) { |
|||
s = detail.s |
|||
v = detail.v |
|||
value = convertHsvaToFormat([h, s, v, a], format) |
|||
dispatchValue() |
|||
} |
|||
|
|||
function setHue({ color, isDrag }) { |
|||
h = color |
|||
value = convertHsvaToFormat([h, s, v, a], format) |
|||
if (!isDrag) { |
|||
dispatchValue() |
|||
} |
|||
} |
|||
|
|||
function setAlpha({ color, isDrag }) { |
|||
a = color === "1.00" ? "1" : color |
|||
value = convertHsvaToFormat([h, s, v, a], format) |
|||
if (!isDrag) { |
|||
dispatchValue() |
|||
} |
|||
} |
|||
|
|||
function dispatchValue() { |
|||
dispatch("change", value) |
|||
} |
|||
|
|||
function changeFormatAndConvert(f) { |
|||
format = f |
|||
value = convertHsvaToFormat([h, s, v, a], format) |
|||
} |
|||
|
|||
function handleColorInput(text) { |
|||
let format = getColorFormat(text) |
|||
if (format) { |
|||
value = text |
|||
convertAndSetHSVA() |
|||
} |
|||
} |
|||
|
|||
function dispatchInputChange() { |
|||
if (format) { |
|||
dispatchValue() |
|||
} |
|||
} |
|||
|
|||
function addSwatch() { |
|||
if (format) { |
|||
dispatch("addswatch", value) |
|||
setRecentColor(value) |
|||
} |
|||
} |
|||
|
|||
function removeSwatch(idx) { |
|||
let removedSwatch = swatches.splice(idx, 1) |
|||
swatches = swatches |
|||
dispatch("removeswatch", removedSwatch) |
|||
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches)) |
|||
} |
|||
|
|||
function applySwatch(color) { |
|||
if (value !== color) { |
|||
format = getColorFormat(color) |
|||
if (format) { |
|||
value = color |
|||
convertAndSetHSVA() |
|||
dispatchValue() |
|||
} |
|||
} |
|||
} |
|||
|
|||
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : "" |
|||
$: selectedColorStyle = buildStyle({ background: value, border }) |
|||
$: shrink = swatches.length > 0 |
|||
</script> |
|||
|
|||
<Portal> |
|||
<div |
|||
class="colorpicker-container" |
|||
transition:fade |
|||
bind:this={colorPicker} |
|||
{style} |
|||
tabindex="0" |
|||
on:keydown={handleEscape} |
|||
bind:clientHeight={pickerHeight} |
|||
bind:clientWidth={pickerWidth}> |
|||
|
|||
<div class="palette-panel"> |
|||
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} /> |
|||
</div> |
|||
|
|||
<div class="control-panel"> |
|||
<div class="alpha-hue-panel"> |
|||
<div> |
|||
<CheckedBackground borderRadius="50%" backgroundSize="8px"> |
|||
<div class="selected-color" style={selectedColorStyle} /> |
|||
</CheckedBackground> |
|||
</div> |
|||
<div> |
|||
<Slider |
|||
type="hue" |
|||
value={h} |
|||
on:change={hue => setHue(hue.detail)} |
|||
on:dragend={dispatchValue} /> |
|||
|
|||
<CheckedBackground borderRadius="10px" backgroundSize="7px"> |
|||
<Slider |
|||
type="alpha" |
|||
value={a} |
|||
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)} |
|||
on:dragend={dispatchValue} /> |
|||
</CheckedBackground> |
|||
|
|||
</div> |
|||
</div> |
|||
|
|||
{#if !disableSwatches} |
|||
<div transition:fade class="swatch-panel"> |
|||
{#if swatches.length > 0} |
|||
{#each swatches as color, idx} |
|||
<Swatch |
|||
{color} |
|||
on:click={() => applySwatch(color)} |
|||
on:removeswatch={() => removeSwatch(idx)} /> |
|||
{/each} |
|||
{/if} |
|||
{#if swatches.length !== 12} |
|||
<div |
|||
bind:this={adder} |
|||
transition:fade |
|||
class="adder" |
|||
on:click={addSwatch} |
|||
class:shrink> |
|||
<span>+</span> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
{/if} |
|||
|
|||
<div class="format-input-panel"> |
|||
<ButtonGroup {format} onclick={changeFormatAndConvert} /> |
|||
<Input |
|||
{value} |
|||
on:input={event => handleColorInput(event.target.value)} |
|||
on:change={dispatchInputChange} /> |
|||
</div> |
|||
</div> |
|||
|
|||
</div> |
|||
</Portal> |
|||
|
|||
<style> |
|||
.colorpicker-container { |
|||
position: absolute; |
|||
outline: none; |
|||
z-index: 3; |
|||
display: flex; |
|||
font-size: 11px; |
|||
font-weight: 400; |
|||
flex-direction: column; |
|||
margin: 5px 0px; |
|||
height: auto; |
|||
width: 220px; |
|||
background: #ffffff; |
|||
border-radius: 2px; |
|||
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1), |
|||
0 0 1em 0 rgba(0, 0, 0, 0.03); |
|||
} |
|||
|
|||
.palette-panel { |
|||
flex: 1; |
|||
} |
|||
|
|||
.control-panel { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
padding: 8px; |
|||
background: white; |
|||
border: 1px solid #d2d2d2; |
|||
color: #777373; |
|||
} |
|||
|
|||
.alpha-hue-panel { |
|||
display: grid; |
|||
grid-template-columns: 25px 1fr; |
|||
grid-gap: 15px; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.selected-color { |
|||
width: 30px; |
|||
height: 30px; |
|||
border-radius: 50%; |
|||
} |
|||
|
|||
.swatch-panel { |
|||
flex: 0 0 15px; |
|||
display: flex; |
|||
flex-flow: row wrap; |
|||
justify-content: flex-start; |
|||
padding: 0 5px; |
|||
max-height: 56px; |
|||
} |
|||
|
|||
.adder { |
|||
flex: 1; |
|||
height: 20px; |
|||
display: flex; |
|||
transition: flex 0.5s; |
|||
justify-content: center; |
|||
align-items: center; |
|||
background: #f1f3f4; |
|||
cursor: pointer; |
|||
border: 1px solid #d4d4d4; |
|||
border-radius: 8px; |
|||
margin-left: 5px; |
|||
margin-top: 3px; |
|||
font-weight: 500; |
|||
} |
|||
|
|||
.shrink { |
|||
flex: 0 0 20px; |
|||
} |
|||
|
|||
.format-input-panel { |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
padding-top: 3px; |
|||
} |
|||
</style> |
|||
@ -1,157 +0,0 @@ |
|||
<script> |
|||
import Colorpicker from "./Colorpicker.svelte" |
|||
import CheckedBackground from "./CheckedBackground.svelte" |
|||
import { createEventDispatcher, beforeUpdate } from "svelte" |
|||
|
|||
import { buildStyle } from "./helpers.js" |
|||
import { fade } from "svelte/transition" |
|||
import { getColorFormat } from "./utils.js" |
|||
|
|||
export let value = "#3ec1d3ff" |
|||
export let swatches = [] |
|||
export let disableSwatches = false |
|||
export let open = false |
|||
export let width = "25px" |
|||
export let height = "25px" |
|||
|
|||
let format = "hexa" |
|||
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 } |
|||
let positionSide = "top" |
|||
let colorPreview = null |
|||
|
|||
let previewHeight = null |
|||
let previewWidth = null |
|||
let pickerWidth = 0 |
|||
let pickerHeight = 0 |
|||
|
|||
let errorMsg = null |
|||
|
|||
const dispatch = createEventDispatcher() |
|||
|
|||
beforeUpdate(() => { |
|||
format = getColorFormat(value) |
|||
if (!format) { |
|||
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value` |
|||
console.error(errorMsg) |
|||
} else { |
|||
errorMsg = null |
|||
} |
|||
}) |
|||
|
|||
function openColorpicker(event) { |
|||
if (colorPreview) { |
|||
open = true |
|||
} |
|||
} |
|||
|
|||
function onColorChange(color) { |
|||
value = color.detail |
|||
dispatch("change", color.detail) |
|||
} |
|||
|
|||
$: if (open && colorPreview) { |
|||
const { |
|||
top: spaceAbove, |
|||
width, |
|||
bottom, |
|||
right, |
|||
left, |
|||
} = colorPreview.getBoundingClientRect() |
|||
|
|||
const spaceBelow = window.innerHeight - bottom |
|||
const previewCenter = previewWidth / 2 |
|||
|
|||
let y, x |
|||
|
|||
if (spaceAbove > spaceBelow) { |
|||
positionSide = "bottom" |
|||
y = window.innerHeight - spaceAbove |
|||
} else { |
|||
positionSide = "top" |
|||
y = bottom |
|||
} |
|||
|
|||
x = left + previewCenter - pickerWidth / 2 |
|||
|
|||
dimensions = { [positionSide]: y.toFixed(1), left: x.toFixed(1) } |
|||
} |
|||
|
|||
$: previewStyle = buildStyle({ width, height, background: value }) |
|||
$: errorPreviewStyle = buildStyle({ width, height }) |
|||
$: pickerStyle = buildStyle({ |
|||
[positionSide]: `${dimensions[positionSide]}px`, |
|||
left: `${dimensions.left}px`, |
|||
}) |
|||
</script> |
|||
|
|||
<div class="color-preview-container"> |
|||
{#if !errorMsg} |
|||
<CheckedBackground borderRadius="3px" backgroundSize="8px"> |
|||
<div |
|||
bind:this={colorPreview} |
|||
bind:clientHeight={previewHeight} |
|||
bind:clientWidth={previewWidth} |
|||
class="color-preview" |
|||
style={previewStyle} |
|||
on:click={openColorpicker} /> |
|||
</CheckedBackground> |
|||
|
|||
{#if open} |
|||
<Colorpicker |
|||
style={pickerStyle} |
|||
on:change={onColorChange} |
|||
on:addswatch |
|||
on:removeswatch |
|||
bind:format |
|||
bind:value |
|||
bind:pickerHeight |
|||
bind:pickerWidth |
|||
bind:open |
|||
{swatches} |
|||
{disableSwatches} /> |
|||
<div on:click|self={() => (open = false)} class="overlay" /> |
|||
{/if} |
|||
{:else} |
|||
<div class="color-preview preview-error" style={errorPreviewStyle}> |
|||
<span>×</span> |
|||
</div> |
|||
{/if} |
|||
</div> |
|||
|
|||
<style> |
|||
.color-preview-container { |
|||
display: flex; |
|||
flex-flow: row nowrap; |
|||
height: fit-content; |
|||
} |
|||
|
|||
.color-preview { |
|||
cursor: pointer; |
|||
border-radius: 3px; |
|||
border: 1px solid #dedada; |
|||
} |
|||
|
|||
.preview-error { |
|||
background: #cccccc; |
|||
color: #808080; |
|||
text-align: center; |
|||
font-size: 18px; |
|||
cursor: not-allowed; |
|||
} |
|||
|
|||
/* .picker-container { |
|||
position: absolute; |
|||
z-index: 3; |
|||
width: fit-content; |
|||
height: fit-content; |
|||
} */ |
|||
|
|||
.overlay { |
|||
position: fixed; |
|||
top: 0; |
|||
bottom: 0; |
|||
left: 0; |
|||
right: 0; |
|||
z-index: 2; |
|||
} |
|||
</style> |
|||
@ -1,37 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
|
|||
export let target = document.body |
|||
|
|||
let targetEl |
|||
let portal |
|||
let componentInstance |
|||
|
|||
onMount(() => { |
|||
if (typeof target === "string") { |
|||
targetEl = document.querySelector(target) |
|||
// Force exit |
|||
if (targetEl === null) { |
|||
return () => {} |
|||
} |
|||
} else if (target instanceof HTMLElement) { |
|||
targetEl = target |
|||
} else { |
|||
throw new TypeError( |
|||
`Unknown target type: ${typeof target}. Allowed types: String (CSS selector), HTMLElement.` |
|||
) |
|||
} |
|||
|
|||
portal = document.createElement("div") |
|||
targetEl.appendChild(portal) |
|||
portal.appendChild(componentInstance) |
|||
|
|||
return () => { |
|||
targetEl.removeChild(portal) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<div bind:this={componentInstance}> |
|||
<slot /> |
|||
</div> |
|||
@ -1,59 +0,0 @@ |
|||
<script> |
|||
import { searchAllComponents } from "./pagesParsing/searchComponents" |
|||
import { store } from "../builderStore" |
|||
|
|||
export let onComponentChosen = () => {} |
|||
|
|||
let phrase = "" |
|||
|
|||
components = $store.components |
|||
|
|||
$: filteredComponents = !phrase ? [] : searchAllComponents(components, phrase) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
|
|||
<form on:submit|preventDefault class="uk-search uk-search-large"> |
|||
<span uk-search-icon /> |
|||
<input |
|||
class="uk-search-input" |
|||
type="search" |
|||
placeholder="Based on component..." |
|||
bind:value={phrase} /> |
|||
</form> |
|||
|
|||
<div> |
|||
{#each filteredComponents as component} |
|||
<div class="component" on:click={() => onComponentChosen(component)}> |
|||
<div class="title">{component.name}</div> |
|||
<div class="description">{component.description}</div> |
|||
</div> |
|||
{/each} |
|||
</div> |
|||
|
|||
</div> |
|||
|
|||
<style> |
|||
.component { |
|||
padding: 5px; |
|||
border-style: solid; |
|||
border-width: 0 0 1px 0; |
|||
border-color: var(--grey-1); |
|||
cursor: pointer; |
|||
} |
|||
|
|||
.component:hover { |
|||
background-color: var(--primary10); |
|||
} |
|||
|
|||
.component > .title { |
|||
font-size: 13pt; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.component > .description { |
|||
font-size: 10pt; |
|||
color: var(--blue); |
|||
font-style: italic; |
|||
} |
|||
</style> |
|||
@ -1,90 +0,0 @@ |
|||
<script> |
|||
import PropsView from "./PropsView.svelte" |
|||
import { store } from "../builderStore" |
|||
import Textbox from "../common/Textbox.svelte" |
|||
import Button from "../common/Button.svelte" |
|||
import { LayoutIcon, PaintIcon, TerminalIcon } from "../common/Icons/" |
|||
|
|||
import { cloneDeep, join, split, last } from "lodash/fp" |
|||
import { assign } from "lodash" |
|||
|
|||
$: component = $store.currentPreviewItem |
|||
$: componentInfo = $store.currentComponentInfo |
|||
$: components = $store.components |
|||
|
|||
const updateComponent = doChange => doChange(cloneDeep(component)) |
|||
|
|||
const onPropsChanged = newProps => { |
|||
updateComponent(newComponent => assign(newComponent.props, newProps)) |
|||
} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
|
|||
<ul> |
|||
<li> |
|||
<button> |
|||
<PaintIcon /> |
|||
</button> |
|||
</li> |
|||
<li> |
|||
<button> |
|||
<LayoutIcon /> |
|||
</button> |
|||
</li> |
|||
<li> |
|||
<button> |
|||
<TerminalIcon /> |
|||
</button> |
|||
</li> |
|||
</ul> |
|||
|
|||
<div class="component-props-container"> |
|||
<PropsView {componentInfo} {onPropsChanged} /> |
|||
</div> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.title > div:nth-child(1) { |
|||
grid-column-start: name; |
|||
color: var(--ink); |
|||
} |
|||
|
|||
.title > div:nth-child(2) { |
|||
grid-column-start: actions; |
|||
} |
|||
|
|||
.component-props-container { |
|||
flex: 1 1 auto; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
ul { |
|||
list-style: none; |
|||
display: flex; |
|||
padding: 0; |
|||
} |
|||
|
|||
li { |
|||
margin-right: 20px; |
|||
background: none; |
|||
border-radius: 5px; |
|||
width: 45px; |
|||
height: 45px; |
|||
} |
|||
|
|||
li button { |
|||
width: 100%; |
|||
height: 100%; |
|||
background: none; |
|||
border: none; |
|||
border-radius: 5px; |
|||
padding: 13px; |
|||
} |
|||
</style> |
|||
@ -1,33 +0,0 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
export let value = "" |
|||
export let onChange = value => {} |
|||
export let options = [] |
|||
export let initialValue = "" |
|||
export let styleBindingProperty = "" |
|||
|
|||
const handleStyleBind = value => |
|||
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {} |
|||
|
|||
$: isOptionsObject = options.every(o => typeof o === "object") |
|||
|
|||
onMount(() => { |
|||
if (!value && !!initialValue) { |
|||
value = initialValue |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<select {value} on:change={ev => onChange(ev.target.value)}> |
|||
{#if isOptionsObject} |
|||
{#each options as { value, label }} |
|||
<option {...handleStyleBind(value || label)} value={value || label}> |
|||
{label} |
|||
</option> |
|||
{/each} |
|||
{:else} |
|||
{#each options as value} |
|||
<option {...handleStyleBind(value)} {value}>{value}</option> |
|||
{/each} |
|||
{/if} |
|||
</select> |
|||
@ -1,65 +0,0 @@ |
|||
<script> |
|||
import Textbox from "components/common/Textbox.svelte" |
|||
import Dropdown from "components/common/Dropdown.svelte" |
|||
import Button from "components/common/Button.svelte" |
|||
import { store } from "builderStore" |
|||
import { isRootComponent } from "./pagesParsing/searchComponents" |
|||
import { pipe } from "components/common/core" |
|||
import { filter, find, concat } from "lodash/fp" |
|||
|
|||
const notSelectedComponent = { name: "(none selected)" } |
|||
|
|||
$: page = $store.pages[$store.currentPageName] |
|||
$: title = page.index.title |
|||
$: components = pipe($store.components, [ |
|||
filter(store => !isRootComponent($store)), |
|||
concat([notSelectedComponent]), |
|||
]) |
|||
$: entryComponent = components[page.appBody] || notSelectedComponent |
|||
|
|||
const save = () => { |
|||
if (!title || !entryComponent || entryComponent === notSeletedComponent) |
|||
return |
|||
const page = { |
|||
index: { |
|||
title, |
|||
}, |
|||
appBody: entryComponent.name, |
|||
} |
|||
store.savePage(page) |
|||
} |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
|
|||
<h3>{$store.currentPageName}</h3> |
|||
|
|||
<form on:submit|preventDefault class="uk-form-horizontal"> |
|||
<Textbox bind:text={title} label="Title" hasError={!title} /> |
|||
<div class="help-text"> |
|||
The title of your page, displayed in the bowser tab |
|||
</div> |
|||
<Dropdown |
|||
label="App Entry Component" |
|||
options={components} |
|||
bind:selected={entryComponent} |
|||
textMember={v => v.name} /> |
|||
|
|||
<div class="help-text"> |
|||
The component that will be loaded into the body of the page |
|||
</div> |
|||
<div style="margin-top: 20px" /> |
|||
<Button on:click={save}>Save</Button> |
|||
</form> |
|||
|
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
padding: 15px; |
|||
} |
|||
.help-text { |
|||
color: var(--grey-2); |
|||
font-size: 10pt; |
|||
} |
|||
</style> |
|||
File diff suppressed because it is too large
Loading…
Reference in new issue