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 DeleteRecordModal } from "./DeleteRecord.svelte" |
||||
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte" |
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte" |
||||
export { default as CreateEditViewModal } from "./CreateEditView.svelte" |
|
||||
export { default as CreateUserModal } from "./CreateUser.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