forked from tsai/budibase
committed by
GitHub
58 changed files with 1631 additions and 1245 deletions
@ -0,0 +1,23 @@ |
|||||
|
import { writable } from "svelte/store" |
||||
|
import { generate } from "shortid" |
||||
|
|
||||
|
export const notificationStore = writable({ |
||||
|
notifications: [], |
||||
|
}) |
||||
|
|
||||
|
export function send(message, type = "default") { |
||||
|
notificationStore.update(state => { |
||||
|
state.notifications = [ |
||||
|
...state.notifications, |
||||
|
{ id: generate(), type, message }, |
||||
|
] |
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
export const notifier = { |
||||
|
danger: msg => send(msg, "danger"), |
||||
|
warning: msg => send(msg, "warning"), |
||||
|
info: msg => send(msg, "info"), |
||||
|
success: msg => send(msg, "success"), |
||||
|
} |
||||
@ -1,76 +0,0 @@ |
|||||
<script context="module"> |
|
||||
import UIKit from "uikit" |
|
||||
|
|
||||
export function showAppNotification({ message, status }) { |
|
||||
UIKit.notification({ |
|
||||
message: ` |
|
||||
<div class="message-container"> |
|
||||
<div class="information-icon">🤯</div> |
|
||||
<span class="notification-message"> |
|
||||
${message} |
|
||||
</span> |
|
||||
<button class="hoverable refresh-page-button" onclick="window.location.reload()">Refresh Page</button> |
|
||||
</div> |
|
||||
`, |
|
||||
status, |
|
||||
timeout: 100000, |
|
||||
}) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
:global(.information-icon) { |
|
||||
font-size: 24px; |
|
||||
margin-right: 8px; |
|
||||
} |
|
||||
|
|
||||
:global(.uk-nofi) { |
|
||||
display: grid; |
|
||||
grid-template-columns: 40px 1fr auto; |
|
||||
grid-gap: 5px; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
:global(.message-container) { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
|
|
||||
:global(.uk-notification) { |
|
||||
width: 50% !important; |
|
||||
left: 0 !important; |
|
||||
right: 0 !important; |
|
||||
margin-right: auto !important; |
|
||||
margin-left: auto !important; |
|
||||
border-radius: 10px; |
|
||||
} |
|
||||
|
|
||||
:global(.uk-notification-message) { |
|
||||
border-radius: 5px; |
|
||||
} |
|
||||
|
|
||||
:global(.uk-notification-message:hover .uk-notification-close) { |
|
||||
visibility: hidden; |
|
||||
} |
|
||||
|
|
||||
:global(.uk-notification-message-danger) { |
|
||||
background: var(--grey-9) !important; |
|
||||
color: #fff !important; |
|
||||
font-size: 16px !important; |
|
||||
} |
|
||||
|
|
||||
:global(.refresh-page-button) { |
|
||||
font-size: 14px; |
|
||||
border-radius: 3px; |
|
||||
border: none; |
|
||||
padding: 8px 16px; |
|
||||
color: var(--ink); |
|
||||
background: #ffffff; |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
|
|
||||
:global(.refresh-page-button):hover { |
|
||||
background: var(--grey-1); |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,56 @@ |
|||||
|
<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: 3px; |
||||
|
color: var(--ink); |
||||
|
font-weight: 500; |
||||
|
padding: 15px; |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
align-items: center; |
||||
|
flex-direction: column; |
||||
|
transition: 0.3s transform; |
||||
|
background: var(--light-grey); |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
font-size: 30px; |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
font-size: 14px; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
div:hover { |
||||
|
cursor: pointer; |
||||
|
transform: scale(1.1); |
||||
|
} |
||||
|
|
||||
|
.primary { |
||||
|
background: var(--ink); |
||||
|
color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.secondary { |
||||
|
background: var(--blue-light); |
||||
|
} |
||||
|
|
||||
|
.tertiary { |
||||
|
background: var(--white); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,101 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import api from "builderStore/api" |
||||
|
|
||||
|
export let modelId |
||||
|
export let linkName |
||||
|
export let linked = [] |
||||
|
|
||||
|
let records = [] |
||||
|
let model = {} |
||||
|
|
||||
|
let linkedRecords = new Set(linked) |
||||
|
|
||||
|
$: linked = [...linkedRecords] |
||||
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id] |
||||
|
$: schema = $backendUiStore.selectedModel.schema |
||||
|
|
||||
|
async function fetchRecords() { |
||||
|
const FETCH_RECORDS_URL = `/api/${modelId}/records` |
||||
|
const response = await api.get(FETCH_RECORDS_URL) |
||||
|
const modelResponse = await api.get(`/api/models/${modelId}`) |
||||
|
|
||||
|
model = await modelResponse.json() |
||||
|
records = await response.json() |
||||
|
} |
||||
|
|
||||
|
function linkRecord(id) { |
||||
|
if (linkedRecords.has(id)) { |
||||
|
linkedRecords.delete(id) |
||||
|
} else { |
||||
|
linkedRecords.add(id) |
||||
|
} |
||||
|
|
||||
|
linkedRecords = linkedRecords |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
fetchRecords() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<header> |
||||
|
<h3>{linkName}</h3> |
||||
|
</header> |
||||
|
{#each records as record} |
||||
|
<div class="linked-record" on:click={() => linkRecord(record._id)}> |
||||
|
<div class="fields" class:selected={linkedRecords.has(record._id)}> |
||||
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key} |
||||
|
<div class="field"> |
||||
|
<span>{model.schema[key].name}</span> |
||||
|
<p>{record[key]}</p> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
.fields.selected { |
||||
|
background: var(--light-grey); |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-size: 18px; |
||||
|
font-weight: 600; |
||||
|
margin-bottom: 12px; |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
.fields { |
||||
|
padding: 15px; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr; |
||||
|
grid-gap: 20px; |
||||
|
background: var(--white); |
||||
|
border: 1px solid var(--grey); |
||||
|
border-radius: 5px; |
||||
|
transition: 0.5s all; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.fields:hover { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.field span { |
||||
|
color: var(--ink-lighter); |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.field p { |
||||
|
color: var(--ink); |
||||
|
font-size: 14px; |
||||
|
word-break: break-word; |
||||
|
font-weight: 500; |
||||
|
margin-top: 4px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,65 @@ |
|||||
|
<script> |
||||
|
import { notificationStore } from "builderStore/store/notifications" |
||||
|
import { onMount, onDestroy } from "svelte" |
||||
|
import { fade } from "svelte/transition" |
||||
|
|
||||
|
export let themes = { |
||||
|
danger: "#E26D69", |
||||
|
success: "#84C991", |
||||
|
warning: "#f0ad4e", |
||||
|
info: "#5bc0de", |
||||
|
default: "#aaaaaa", |
||||
|
} |
||||
|
|
||||
|
export let timeout = 3000 |
||||
|
|
||||
|
$: if ($notificationStore.notifications.length) { |
||||
|
setTimeout(() => { |
||||
|
notificationStore.update(state => { |
||||
|
state.notifications.shift() |
||||
|
state.notifications = state.notifications |
||||
|
return state |
||||
|
}) |
||||
|
}, timeout) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ul class="notifications"> |
||||
|
{#each $notificationStore.notifications as notification (notification.id)} |
||||
|
<li |
||||
|
class="toast" |
||||
|
style="background: {themes[notification.type]};" |
||||
|
transition:fade> |
||||
|
<div class="content">{notification.message}</div> |
||||
|
{#if notification.icon} |
||||
|
<i class={notification.icon} /> |
||||
|
{/if} |
||||
|
</li> |
||||
|
{/each} |
||||
|
</ul> |
||||
|
|
||||
|
<style> |
||||
|
.notifications { |
||||
|
width: 40vw; |
||||
|
list-style: none; |
||||
|
position: fixed; |
||||
|
top: 0; |
||||
|
left: 0; |
||||
|
right: 0; |
||||
|
margin-left: auto; |
||||
|
margin-right: auto; |
||||
|
padding: 0; |
||||
|
z-index: 9999; |
||||
|
} |
||||
|
|
||||
|
.toast { |
||||
|
margin-bottom: 10px; |
||||
|
} |
||||
|
|
||||
|
.content { |
||||
|
padding: 10px; |
||||
|
display: block; |
||||
|
color: var(--white); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,124 @@ |
|||||
|
<script> |
||||
|
import { onMount } from "svelte" |
||||
|
import { fade } from "svelte/transition" |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import api from "builderStore/api" |
||||
|
|
||||
|
export let ids = [] |
||||
|
export let field |
||||
|
|
||||
|
let records = [] |
||||
|
let open = false |
||||
|
let model |
||||
|
|
||||
|
$: FIELDS_TO_HIDE = [$backendUiStore.selectedModel._id, field.modelId] |
||||
|
|
||||
|
async function fetchRecords() { |
||||
|
const response = await api.post("/api/records/search", { |
||||
|
keys: ids, |
||||
|
}) |
||||
|
const modelResponse = await api.get(`/api/models/${field.modelId}`) |
||||
|
records = await response.json() |
||||
|
model = await modelResponse.json() |
||||
|
} |
||||
|
|
||||
|
$: ids && fetchRecords() |
||||
|
|
||||
|
function toggleOpen() { |
||||
|
open = !open |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
fetchRecords() |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<a on:click={toggleOpen}>{records.length}</a> |
||||
|
{#if open} |
||||
|
<div class="popover" transition:fade> |
||||
|
<header> |
||||
|
<h3>{field.name}</h3> |
||||
|
<i class="ri-close-circle-fill" on:click={toggleOpen} /> |
||||
|
</header> |
||||
|
{#each records as record} |
||||
|
<div class="linked-record"> |
||||
|
<div class="fields"> |
||||
|
{#each Object.keys(model.schema).filter(key => !FIELDS_TO_HIDE.includes(key)) as key} |
||||
|
<div class="field"> |
||||
|
<span>{model.schema[key].name}</span> |
||||
|
<p>{record[key]}</p> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
section { |
||||
|
display: relative; |
||||
|
} |
||||
|
|
||||
|
header { |
||||
|
width: 100%; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 12px; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
font-size: 24px; |
||||
|
color: var(--ink-lighter); |
||||
|
} |
||||
|
|
||||
|
i:hover { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
a { |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.popover { |
||||
|
width: 500px; |
||||
|
position: absolute; |
||||
|
right: 15%; |
||||
|
padding: 20px; |
||||
|
background: var(--light-grey); |
||||
|
border: 1px solid var(--grey); |
||||
|
} |
||||
|
|
||||
|
h3 { |
||||
|
font-size: 20px; |
||||
|
font-weight: bold; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.fields { |
||||
|
padding: 15px; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr 1fr; |
||||
|
grid-gap: 20px; |
||||
|
background: var(--white); |
||||
|
border: 1px solid var(--grey); |
||||
|
border-radius: 5px; |
||||
|
margin-bottom: 8px; |
||||
|
} |
||||
|
|
||||
|
.field span { |
||||
|
color: var(--ink-lighter); |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.field p { |
||||
|
color: var(--ink); |
||||
|
font-size: 14px; |
||||
|
word-break: break-word; |
||||
|
font-weight: 500; |
||||
|
margin-top: 4px; |
||||
|
} |
||||
|
</style> |
||||
@ -1,106 +0,0 @@ |
|||||
<script> |
|
||||
import Dropdown from "components/common/Dropdown.svelte" |
|
||||
import Textbox from "components/common/Textbox.svelte" |
|
||||
import Button from "components/common/Button.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"] |
|
||||
|
|
||||
export let field = { |
|
||||
type: "string", |
|
||||
constraints: { type: "string", presence: false }, |
|
||||
} |
|
||||
export let schema |
|
||||
export let goBack |
|
||||
|
|
||||
let errors = [] |
|
||||
let draftField = cloneDeep(field) |
|
||||
|
|
||||
let type = field.type |
|
||||
let constraints = field.constraints |
|
||||
let required = |
|
||||
field.constraints.presence && !field.constraints.presence.allowEmpty |
|
||||
|
|
||||
const save = () => { |
|
||||
constraints.presence = required ? { allowEmpty: false } : false |
|
||||
draftField.constraints = constraints |
|
||||
draftField.type = type |
|
||||
schema[field.name] = draftField |
|
||||
goBack() |
|
||||
} |
|
||||
|
|
||||
$: constraints = |
|
||||
type === "string" |
|
||||
? { type: "string", length: {}, presence: false } |
|
||||
: type === "number" |
|
||||
? { type: "number", presence: false, numericality: {} } |
|
||||
: type === "boolean" |
|
||||
? { type: "boolean", presence: false } |
|
||||
: type === "datetime" |
|
||||
? { type: "date", datetime: {}, presence: false } |
|
||||
: type.startsWith("array") |
|
||||
? { type: "array", presence: false } |
|
||||
: { type: "string", presence: false } |
|
||||
</script> |
|
||||
|
|
||||
<div class="root"> |
|
||||
|
|
||||
<ErrorsBox {errors} /> |
|
||||
|
|
||||
<form on:submit|preventDefault class="uk-form-stacked"> |
|
||||
<Textbox label="Name" bind:text={field.name} /> |
|
||||
<Dropdown label="Type" bind:selected={type} options={FIELD_TYPES} /> |
|
||||
|
|
||||
<Checkbox label="Required" bind:checked={required} /> |
|
||||
|
|
||||
{#if type === 'string'} |
|
||||
<NumberBox label="Max Length" bind:value={constraints.length.maximum} /> |
|
||||
<ValuesList label="Categories" bind:values={constraints.inclusion} /> |
|
||||
{:else if type === 'datetime'} |
|
||||
<DatePicker |
|
||||
label="Min Value" |
|
||||
bind:value={constraints.datetime.earliest} /> |
|
||||
<DatePicker label="Max Value" bind:value={constraints.datetime.latest} /> |
|
||||
{:else if type === 'number'} |
|
||||
<NumberBox |
|
||||
label="Min Value" |
|
||||
bind:value={constraints.numericality.greaterThanOrEqualTo} /> |
|
||||
<NumberBox |
|
||||
label="Max Value" |
|
||||
bind:value={constraints.numericality.lessThanOrEqualTo} /> |
|
||||
{/if} |
|
||||
</form> |
|
||||
</div> |
|
||||
<footer> |
|
||||
<div class="button"> |
|
||||
<ActionButton secondary on:click={goBack}>Cancel</ActionButton> |
|
||||
</div> |
|
||||
<ActionButton primary on:click={save}>Save</ActionButton> |
|
||||
</footer> |
|
||||
|
|
||||
<style> |
|
||||
.root { |
|
||||
margin: 40px; |
|
||||
} |
|
||||
footer { |
|
||||
padding: 20px 40px; |
|
||||
border-radius: 0 0 5px 5px; |
|
||||
bottom: 0; |
|
||||
left: 0; |
|
||||
background: var(--grey-1); |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
justify-content: flex-end; |
|
||||
} |
|
||||
|
|
||||
.button { |
|
||||
margin-right: 20px; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,6 +1,5 @@ |
|||||
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 CreateEditModelModal } from "./CreateEditModel/CreateEditModel.svelte" |
|
||||
export { default as CreateEditViewModal } from "./CreateEditView.svelte" |
export { default as CreateEditViewModal } from "./CreateEditView.svelte" |
||||
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte" |
export { default as CreateDatabaseModal } from "./CreateDatabase.svelte" |
||||
export { default as CreateUserModal } from "./CreateUser.svelte" |
export { default as CreateUserModal } from "./CreateUser.svelte" |
||||
|
|||||
@ -1,87 +0,0 @@ |
|||||
<script> |
|
||||
import { getContext } from "svelte" |
|
||||
import { store, backendUiStore } from "builderStore" |
|
||||
import HierarchyRow from "./HierarchyRow.svelte" |
|
||||
import DatabasesList from "./DatabasesList.svelte" |
|
||||
import UsersList from "./UsersList.svelte" |
|
||||
import NavItem from "./NavItem.svelte" |
|
||||
import getIcon from "components/common/icon" |
|
||||
import { |
|
||||
CreateDatabaseModal, |
|
||||
CreateUserModal, |
|
||||
} from "components/database/ModelDataTable/modals" |
|
||||
const { open, close } = getContext("simple-modal") |
|
||||
|
|
||||
const openDatabaseCreator = () => { |
|
||||
open( |
|
||||
CreateDatabaseModal, |
|
||||
{ |
|
||||
onClosed: close, |
|
||||
}, |
|
||||
{ styleContent: { padding: "0" } } |
|
||||
) |
|
||||
} |
|
||||
const openUserCreator = () => { |
|
||||
open( |
|
||||
CreateUserModal, |
|
||||
{ |
|
||||
onClosed: close, |
|
||||
}, |
|
||||
{ styleContent: { padding: "0" } } |
|
||||
) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="items-root"> |
|
||||
<div class="hierarchy" /> |
|
||||
{#if $backendUiStore.selectedDatabase._id} |
|
||||
<div class="hierarchy"> |
|
||||
<div class="components-list-container"> |
|
||||
<div class="nav-group-header"> |
|
||||
<div class="hierarchy-title">Users</div> |
|
||||
<i class="ri-add-line hoverable" on:click={openUserCreator} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="hierarchy-items-container"> |
|
||||
<UsersList /> |
|
||||
</div> |
|
||||
|
|
||||
</div> |
|
||||
{/if} |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.items-root { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
max-height: 100%; |
|
||||
height: 100%; |
|
||||
background: var(--white); |
|
||||
} |
|
||||
|
|
||||
.nav-group-header { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
padding: 20px 20px 10px 20px; |
|
||||
} |
|
||||
|
|
||||
.hierarchy-title { |
|
||||
align-items: center; |
|
||||
font-size: 18px; |
|
||||
font-weight: 600; |
|
||||
text-rendering: optimizeLegibility; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
|
|
||||
.hierarchy { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
.hierarchy-items-container { |
|
||||
flex: 1 1 auto; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,81 @@ |
|||||
|
<script> |
||||
|
import * as blockDefinitions from "constants/backend" |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import Block from "components/common/Block.svelte" |
||||
|
|
||||
|
const HEADINGS = [ |
||||
|
{ |
||||
|
title: "Fields", |
||||
|
key: "FIELDS", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Blocks", |
||||
|
key: "BLOCKS", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
let selectedTab = "FIELDS" |
||||
|
|
||||
|
function addField(blockDefinition) { |
||||
|
backendUiStore.actions.models.addField(blockDefinition) |
||||
|
backendUiStore.actions.models.fetch() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<section> |
||||
|
<header> |
||||
|
{#each HEADINGS as tab} |
||||
|
<span |
||||
|
class:selected={selectedTab === tab.key} |
||||
|
on:click={() => (selectedTab = tab.key)}> |
||||
|
{tab.title} |
||||
|
</span> |
||||
|
{/each} |
||||
|
</header> |
||||
|
|
||||
|
<div class="block-grid"> |
||||
|
{#each Object.values(blockDefinitions[selectedTab]) as blockDefinition} |
||||
|
<Block |
||||
|
on:click={() => addField(blockDefinition)} |
||||
|
title={blockDefinition.name} |
||||
|
icon={blockDefinition.icon} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
header { |
||||
|
margin-top: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
display: grid; |
||||
|
grid-gap: 20px; |
||||
|
grid-template-columns: repeat(3, 1fr); |
||||
|
} |
||||
|
|
||||
|
span { |
||||
|
text-align: center; |
||||
|
padding: 10px; |
||||
|
font-weight: 500; |
||||
|
border-radius: 3px; |
||||
|
color: var(--ink-lighter); |
||||
|
font-size: 14px; |
||||
|
background: var(--light-grey); |
||||
|
} |
||||
|
|
||||
|
span:hover { |
||||
|
background: var(--blue-light); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
background: var(--blue-light); |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
.block-grid { |
||||
|
margin-top: 20px; |
||||
|
display: grid; |
||||
|
grid-template-columns: 1fr 1fr; |
||||
|
grid-gap: 20px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,112 @@ |
|||||
|
<script> |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import { uuid } from "builderStore/uuid" |
||||
|
import { fade } from "svelte/transition" |
||||
|
import { FIELDS, BLOCKS, MODELS } from "constants/backend" |
||||
|
import Block from "components/common/Block.svelte" |
||||
|
|
||||
|
function addNewField(field) { |
||||
|
backendUiStore.actions.models.addField(field) |
||||
|
} |
||||
|
|
||||
|
function createModel(model) { |
||||
|
const { schema, ...rest } = $backendUiStore.selectedModel |
||||
|
|
||||
|
const newModel = { ...model, schema: {} } |
||||
|
|
||||
|
// TODO: could be better |
||||
|
for (let key in model.schema) { |
||||
|
newModel.schema[uuid()] = model.schema[key] |
||||
|
} |
||||
|
|
||||
|
backendUiStore.actions.models.save({ |
||||
|
model: { |
||||
|
...newModel, |
||||
|
...rest, |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<section transition:fade> |
||||
|
<header> |
||||
|
<h2>Create New Model</h2> |
||||
|
<p>Before you can view your model, you need to set it up.</p> |
||||
|
</header> |
||||
|
|
||||
|
<div class="block-row"> |
||||
|
<span class="block-row-title">Fields</span> |
||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p> |
||||
|
<div class="blocks"> |
||||
|
{#each Object.values(FIELDS) as field} |
||||
|
<Block |
||||
|
primary |
||||
|
title={field.name} |
||||
|
icon={field.icon} |
||||
|
on:click={() => addNewField(field)} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="block-row"> |
||||
|
<span class="block-row-title">Blocks</span> |
||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p> |
||||
|
<div class="blocks"> |
||||
|
{#each Object.values(BLOCKS) as field} |
||||
|
<Block |
||||
|
secondary |
||||
|
title={field.name} |
||||
|
icon={field.icon} |
||||
|
on:click={() => addNewField(field)} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<div class="block-row"> |
||||
|
<span class="block-row-title">Models</span> |
||||
|
<p>Blocks are pre-made fields and help you build your model quicker.</p> |
||||
|
<div class="blocks"> |
||||
|
{#each Object.values(MODELS) as model} |
||||
|
<Block |
||||
|
tertiary |
||||
|
title={model.name} |
||||
|
icon={model.icon} |
||||
|
on:click={() => createModel(model)} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<style> |
||||
|
section { |
||||
|
height: 100vh; |
||||
|
} |
||||
|
|
||||
|
h2 { |
||||
|
font-size: 20px; |
||||
|
font-weight: bold; |
||||
|
margin: 0; |
||||
|
} |
||||
|
|
||||
|
.block-row-title { |
||||
|
font-weight: 500; |
||||
|
font-size: 16px; |
||||
|
} |
||||
|
|
||||
|
p { |
||||
|
margin-top: 8px; |
||||
|
margin-bottom: 20px; |
||||
|
font-size: 14px; |
||||
|
} |
||||
|
|
||||
|
.block-row { |
||||
|
margin-top: 40px; |
||||
|
} |
||||
|
|
||||
|
.block-row .blocks { |
||||
|
display: grid; |
||||
|
grid-auto-flow: column; |
||||
|
grid-auto-columns: 110px; |
||||
|
grid-gap: 20px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,48 @@ |
|||||
|
<script> |
||||
|
export let icon |
||||
|
export let className |
||||
|
export let title |
||||
|
export let selected |
||||
|
export let indented |
||||
|
</script> |
||||
|
|
||||
|
<div class:selected on:click class={className}> |
||||
|
<i class:indented class={icon} /> |
||||
|
<span>{title}</span> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.indented { |
||||
|
margin-left: 10px; |
||||
|
} |
||||
|
|
||||
|
div { |
||||
|
padding: 0 10px 0 10px; |
||||
|
width: 90%; |
||||
|
height: 40px; |
||||
|
border-radius: 3px; |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
transition: 0.3s background-color; |
||||
|
color: var(--ink); |
||||
|
font-weight: 500; |
||||
|
font-size: 16px; |
||||
|
margin-top: 4px; |
||||
|
margin-bottom: 4px; |
||||
|
} |
||||
|
|
||||
|
.selected { |
||||
|
background-color: var(--blue-light); |
||||
|
} |
||||
|
|
||||
|
div:hover { |
||||
|
background-color: var(--blue-light); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
color: var(--dark-grey); |
||||
|
font-size: 20px; |
||||
|
margin-right: 8px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,107 @@ |
|||||
|
<script> |
||||
|
import { getContext } from "svelte" |
||||
|
import { slide } from "svelte/transition" |
||||
|
import { Switcher } from "@budibase/bbui" |
||||
|
import { goto } from "@sveltech/routify" |
||||
|
import { store, backendUiStore } from "builderStore" |
||||
|
import BlockNavigator from "./BlockNavigator.svelte" |
||||
|
import ListItem from "./ListItem.svelte" |
||||
|
import { Button } from "@budibase/bbui" |
||||
|
|
||||
|
const { open, close } = getContext("simple-modal") |
||||
|
|
||||
|
let HEADINGS = [ |
||||
|
{ |
||||
|
title: "Navigate", |
||||
|
key: "NAVIGATE", |
||||
|
}, |
||||
|
{ |
||||
|
title: "Add", |
||||
|
key: "ADD", |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
$: selectedTab = $backendUiStore.tabs.NAVIGATION_PANEL |
||||
|
|
||||
|
function selectModel(model, fieldId) { |
||||
|
backendUiStore.actions.models.select(model) |
||||
|
|
||||
|
if (fieldId) { |
||||
|
backendUiStore.update(state => { |
||||
|
state.selectedField = fieldId |
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function setupForNewModel() { |
||||
|
backendUiStore.update(state => { |
||||
|
state.selectedModel = {} |
||||
|
state.draftModel = { schema: {} } |
||||
|
state.tabs.SETUP_PANEL = "SETUP" |
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div class="items-root"> |
||||
|
{#if $backendUiStore.selectedDatabase && $backendUiStore.selectedDatabase._id} |
||||
|
<div class="hierarchy"> |
||||
|
<div class="components-list-container"> |
||||
|
<Switcher |
||||
|
headings={HEADINGS} |
||||
|
bind:value={$backendUiStore.tabs.NAVIGATION_PANEL}> |
||||
|
{#if selectedTab === 'NAVIGATE'} |
||||
|
<Button secondary wide on:click={setupForNewModel}> |
||||
|
Create New Model |
||||
|
</Button> |
||||
|
<div class="hierarchy-items-container"> |
||||
|
{#each $backendUiStore.models as model} |
||||
|
<ListItem |
||||
|
selected={!$backendUiStore.selectedField && model._id === $backendUiStore.selectedModel._id} |
||||
|
title={model.name} |
||||
|
icon="ri-table-fill" |
||||
|
on:click={() => selectModel(model)} /> |
||||
|
{#if model._id === $backendUiStore.selectedModel._id} |
||||
|
<div in:slide> |
||||
|
{#each Object.keys(model.schema) as fieldId} |
||||
|
<ListItem |
||||
|
selected={model._id === $backendUiStore.selectedModel._id && fieldId === $backendUiStore.selectedField} |
||||
|
indented |
||||
|
icon="ri-layout-column-fill" |
||||
|
title={model.schema[fieldId].name} |
||||
|
on:click={() => selectModel(model, fieldId)} /> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
{/each} |
||||
|
</div> |
||||
|
{:else if selectedTab === 'ADD'} |
||||
|
<BlockNavigator /> |
||||
|
{/if} |
||||
|
</Switcher> |
||||
|
</div> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.items-root { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
max-height: 100%; |
||||
|
height: 100%; |
||||
|
background: var(--white); |
||||
|
padding: 20px; |
||||
|
} |
||||
|
|
||||
|
.hierarchy { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
} |
||||
|
|
||||
|
.hierarchy-items-container { |
||||
|
margin-top: 20px; |
||||
|
flex: 1 1 auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,124 @@ |
|||||
|
<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 && |
||||
|
!constraints.presence.allowEmpty |
||||
|
|
||||
|
function attachModelIdToSchema(evt) { |
||||
|
const { draftModel } = $backendUiStore |
||||
|
if ($backendUiStore.selectedField !== evt.target.value) { |
||||
|
delete draftModel.schema[$backendUiStore.selectedField] |
||||
|
draftModel.schema[evt.target.value] = field |
||||
|
backendUiStore.update(state => { |
||||
|
state.selectedField = evt.target.value |
||||
|
return state |
||||
|
}) |
||||
|
} |
||||
|
} |
||||
|
</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" /> |
||||
|
</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} |
||||
|
on:change={attachModelIdToSchema}> |
||||
|
<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 { |
||||
|
background: var(--light-grey); |
||||
|
padding: 12px; |
||||
|
margin-bottom: 5px; |
||||
|
border-radius: 3px; |
||||
|
} |
||||
|
|
||||
|
label { |
||||
|
font-size: 12px; |
||||
|
} |
||||
|
|
||||
|
.field { |
||||
|
display: grid; |
||||
|
align-items: center; |
||||
|
grid-template-columns: 40% 1fr; |
||||
|
margin-top: 8px; |
||||
|
} |
||||
|
|
||||
|
.field-box header { |
||||
|
font-size: 14px; |
||||
|
font-weight: 500; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
|
||||
|
.field-box span { |
||||
|
font-weight: bold; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,128 @@ |
|||||
|
<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 |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
async function saveModel() { |
||||
|
await backendUiStore.actions.models.save({ |
||||
|
model: $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 |
||||
|
type="text" |
||||
|
class="budibase__input" |
||||
|
bind:value={$backendUiStore.draftModel.name} /> |
||||
|
</div> |
||||
|
<div class="titled-input"> |
||||
|
<header>Import Data</header> |
||||
|
<Button wide secondary>Import CSV</Button> |
||||
|
</div> |
||||
|
{/if} |
||||
|
<footer> |
||||
|
<Button disabled={!edited} attention={edited} wide on:click={saveModel}> |
||||
|
Save |
||||
|
</Button> |
||||
|
</footer> |
||||
|
{:else if selectedTab === 'DELETE'} |
||||
|
<div class="titled-input"> |
||||
|
<header>Danger Zone</header> |
||||
|
<Button error wide on:click={deleteModel}>Delete</Button> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</Switcher> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
header { |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
footer { |
||||
|
width: 100%; |
||||
|
position: fixed; |
||||
|
bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.items-root { |
||||
|
padding: 20px; |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
max-height: 100%; |
||||
|
height: 100%; |
||||
|
background-color: var(--white); |
||||
|
} |
||||
|
|
||||
|
.titled-input { |
||||
|
padding: 12px; |
||||
|
background: var(--light-grey); |
||||
|
margin-bottom: 5px; |
||||
|
} |
||||
|
|
||||
|
.titled-input header { |
||||
|
display: block; |
||||
|
font-size: 14px; |
||||
|
margin-bottom: 16px; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1 @@ |
|||||
|
export { default as ModelSetupNav } from "./ModelSetupNav.svelte" |
||||
@ -1,143 +0,0 @@ |
|||||
<script> |
|
||||
import { getContext, onMount } from "svelte" |
|
||||
import { store, backendUiStore } from "builderStore" |
|
||||
import HierarchyRow from "./HierarchyRow.svelte" |
|
||||
import NavItem from "./NavItem.svelte" |
|
||||
import getIcon from "components/common/icon" |
|
||||
import api from "builderStore/api" |
|
||||
import { |
|
||||
CreateEditModelModal, |
|
||||
CreateEditViewModal, |
|
||||
} from "components/database/ModelDataTable/modals" |
|
||||
|
|
||||
const { open, close } = getContext("simple-modal") |
|
||||
|
|
||||
function editModel() { |
|
||||
open( |
|
||||
CreateEditModelModal, |
|
||||
{ |
|
||||
model: node, |
|
||||
onClosed: close, |
|
||||
}, |
|
||||
{ styleContent: { padding: "0" } } |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
function newModel() { |
|
||||
open( |
|
||||
CreateEditModelModal, |
|
||||
{ |
|
||||
onClosed: close, |
|
||||
}, |
|
||||
{ styleContent: { padding: "0" } } |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
function newView() { |
|
||||
open( |
|
||||
CreateEditViewModal, |
|
||||
{ |
|
||||
onClosed: close, |
|
||||
}, |
|
||||
{ styleContent: { padding: "0" } } |
|
||||
) |
|
||||
} |
|
||||
|
|
||||
function selectModel(model) { |
|
||||
backendUiStore.update(state => { |
|
||||
state.selectedModel = model |
|
||||
state.selectedView = `all_${model._id}` |
|
||||
return state |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
async function deleteModel(modelToDelete) { |
|
||||
const DELETE_MODEL_URL = `/api/models/${node._id}/${node._rev}` |
|
||||
const response = await api.delete(DELETE_MODEL_URL) |
|
||||
backendUiStore.update(state => { |
|
||||
state.models = state.models.filter( |
|
||||
model => model._id !== modelToDelete._id |
|
||||
) |
|
||||
state.selectedView = {} |
|
||||
return state |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
function selectView(view) { |
|
||||
backendUiStore.update(state => { |
|
||||
state.selectedView = view.name |
|
||||
return state |
|
||||
}) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="items-root"> |
|
||||
<div class="hierarchy"> |
|
||||
<div class="components-list-container"> |
|
||||
<div class="nav-group-header"> |
|
||||
<div class="hierarchy-title">Models</div> |
|
||||
<div class="uk-inline"> |
|
||||
<i class="ri-add-line hoverable" on:click={newModel} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="hierarchy-items-container"> |
|
||||
{#each $backendUiStore.models as model} |
|
||||
<HierarchyRow onSelect={selectModel} node={model} type="model" /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="hierarchy"> |
|
||||
<div class="components-list-container"> |
|
||||
<div class="nav-group-header"> |
|
||||
<div class="hierarchy-title">Views</div> |
|
||||
<div class="uk-inline"> |
|
||||
<i class="ri-add-line hoverable" on:click={newView} /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<div class="hierarchy-items-container"> |
|
||||
{#each $backendUiStore.views as view} |
|
||||
<HierarchyRow onSelect={selectView} node={view} type="view" /> |
|
||||
{/each} |
|
||||
</div> |
|
||||
</div> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.items-root { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
max-height: 100%; |
|
||||
height: 100%; |
|
||||
background-color: var(--white); |
|
||||
} |
|
||||
|
|
||||
.nav-group-header { |
|
||||
display: flex; |
|
||||
justify-content: space-between; |
|
||||
align-items: center; |
|
||||
padding: 20px 20px 10px 20px; |
|
||||
} |
|
||||
|
|
||||
.hierarchy-title { |
|
||||
align-items: center; |
|
||||
font-size: 18px; |
|
||||
font-weight: 600; |
|
||||
text-rendering: optimizeLegibility; |
|
||||
color: var(--ink); |
|
||||
} |
|
||||
|
|
||||
.hierarchy { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
} |
|
||||
|
|
||||
.hierarchy-items-container { |
|
||||
flex: 1 1 auto; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,180 @@ |
|||||
|
export const FIELDS = { |
||||
|
PLAIN_TEXT: { |
||||
|
name: "Plain Text", |
||||
|
icon: "ri-text", |
||||
|
type: "string", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
length: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
NUMBER: { |
||||
|
name: "Number", |
||||
|
icon: "ri-number-1", |
||||
|
type: "number", |
||||
|
constraints: { |
||||
|
type: "number", |
||||
|
presence: false, |
||||
|
numericality: {}, |
||||
|
}, |
||||
|
}, |
||||
|
BOOLEAN: { |
||||
|
name: "True/False", |
||||
|
icon: "ri-toggle-line", |
||||
|
type: "boolean", |
||||
|
constraints: { |
||||
|
type: "boolean", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
// OPTIONS: {
|
||||
|
// name: "Options",
|
||||
|
// icon: "ri-list-check-2",
|
||||
|
// type: "options",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// },
|
||||
|
// },
|
||||
|
DATETIME: { |
||||
|
name: "Date/Time", |
||||
|
icon: "ri-calendar-event-fill", |
||||
|
type: "datetime", |
||||
|
constraints: { |
||||
|
type: "date", |
||||
|
datetime: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
// IMAGE: {
|
||||
|
// name: "File",
|
||||
|
// icon: "ri-image-line",
|
||||
|
// type: "file",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// },
|
||||
|
// },
|
||||
|
// FILE: {
|
||||
|
// name: "Image",
|
||||
|
// icon: "ri-file-line",
|
||||
|
// type: "file",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// },
|
||||
|
// },
|
||||
|
DATA_LINK: { |
||||
|
name: "Data Links", |
||||
|
icon: "ri-link", |
||||
|
type: "link", |
||||
|
modelId: null, |
||||
|
constraints: { |
||||
|
type: "array", |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
|
||||
|
export const BLOCKS = { |
||||
|
NAME: { |
||||
|
name: "Name", |
||||
|
icon: "ri-text", |
||||
|
type: "string", |
||||
|
constraints: { |
||||
|
type: "string", |
||||
|
length: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
PHONE_NUMBER: { |
||||
|
name: "Phone Number", |
||||
|
icon: "ri-number-1", |
||||
|
type: "number", |
||||
|
constraints: { |
||||
|
type: "number", |
||||
|
presence: false, |
||||
|
numericality: {}, |
||||
|
}, |
||||
|
}, |
||||
|
ACTIVE: { |
||||
|
name: "Active", |
||||
|
icon: "ri-toggle-line", |
||||
|
type: "boolean", |
||||
|
constraints: { |
||||
|
type: "boolean", |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
// PRIORITY: {
|
||||
|
// name: "Options",
|
||||
|
// icon: "ri-list-check-2",
|
||||
|
// type: "options",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// inclusion: ["low", "medium", "high"],
|
||||
|
// },
|
||||
|
// },
|
||||
|
END_DATE: { |
||||
|
name: "End Date", |
||||
|
icon: "ri-calendar-event-fill", |
||||
|
type: "datetime", |
||||
|
constraints: { |
||||
|
type: "date", |
||||
|
datetime: {}, |
||||
|
presence: false, |
||||
|
}, |
||||
|
}, |
||||
|
// AVATAR: {
|
||||
|
// name: "Avatar",
|
||||
|
// icon: "ri-image-line",
|
||||
|
// type: "image",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// },
|
||||
|
// },
|
||||
|
// PDF: {
|
||||
|
// name: "PDF",
|
||||
|
// icon: "ri-file-line",
|
||||
|
// type: "file",
|
||||
|
// constraints: {
|
||||
|
// type: "string",
|
||||
|
// presence: false,
|
||||
|
// },
|
||||
|
// },
|
||||
|
} |
||||
|
|
||||
|
export const MODELS = { |
||||
|
CONTACTS: { |
||||
|
icon: "ri-contacts-book-line", |
||||
|
name: "Contacts", |
||||
|
schema: { |
||||
|
Name: BLOCKS.NAME, |
||||
|
"Phone Number": BLOCKS.PHONE_NUMBER, |
||||
|
}, |
||||
|
}, |
||||
|
RECIPES: { |
||||
|
icon: "ri-link", |
||||
|
name: "Recipes", |
||||
|
schema: { |
||||
|
Name: BLOCKS.NAME, |
||||
|
Cuisine: { |
||||
|
...FIELDS.PLAIN_TEXT, |
||||
|
name: "Cuisine", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
SPORTS_TEAM: { |
||||
|
icon: "ri-basketball-line", |
||||
|
name: "Sports Team", |
||||
|
schema: { |
||||
|
Name: BLOCKS.NAME, |
||||
|
Championships: { |
||||
|
...FIELDS.NUMBER, |
||||
|
name: "Championships", |
||||
|
}, |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
@ -1,647 +0,0 @@ |
|||||
body, html { |
|
||||
margin: 0px; |
|
||||
padding: 0px; |
|
||||
overflow: hidden; |
|
||||
background-repeat: repeat; |
|
||||
background-size: 30px 30px; |
|
||||
background-color: var(--grey-1); |
|
||||
height: 100%; |
|
||||
} |
|
||||
#navigation { |
|
||||
height: 71px; |
|
||||
background-color: #FFF; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
width: 100%; |
|
||||
display: table; |
|
||||
box-sizing: border-box; |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
z-index: 9 |
|
||||
} |
|
||||
#back { |
|
||||
width: 40px; |
|
||||
height: 40px; |
|
||||
border-radius: 100px; |
|
||||
background-color: #F1F4FC; |
|
||||
text-align: center; |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
margin-top: 12px; |
|
||||
margin-right: 10px |
|
||||
} |
|
||||
#back img { |
|
||||
margin-top: 13px; |
|
||||
} |
|
||||
#names { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
} |
|
||||
#title { |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 16px; |
|
||||
color: #393C44; |
|
||||
margin-bottom: 0px; |
|
||||
} |
|
||||
#subtitle { |
|
||||
font-family: Roboto; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
margin-top: 5px; |
|
||||
} |
|
||||
#leftside { |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
#centerswitch { |
|
||||
position: absolute; |
|
||||
width: 222px; |
|
||||
left: 50%; |
|
||||
margin-left: -111px; |
|
||||
top: 15px; |
|
||||
} |
|
||||
#leftswitch { |
|
||||
border: 1px solid #E8E8EF; |
|
||||
background-color: var(--grey-1); |
|
||||
width: 111px; |
|
||||
height: 39px; |
|
||||
line-height: 39px; |
|
||||
border-radius: 5px 0px 0px 5px; |
|
||||
font-family: Roboto; |
|
||||
color: #393C44; |
|
||||
display: inline-block; |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
} |
|
||||
#rightswitch { |
|
||||
font-family: Roboto; |
|
||||
color: #808292; |
|
||||
border-radius: 0px 5px 5px 0px; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
height: 39px; |
|
||||
width: 102px; |
|
||||
display: inline-block; |
|
||||
font-size: 14px; |
|
||||
line-height: 39px; |
|
||||
text-align: center; |
|
||||
margin-left: -5px; |
|
||||
} |
|
||||
#discard { |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 14px; |
|
||||
color: #A6A6B3; |
|
||||
width: 95px; |
|
||||
height: 38px; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
border-radius: 5px; |
|
||||
text-align: center; |
|
||||
line-height: 38px; |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
transition: all .2s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
#discard:hover { |
|
||||
cursor: pointer; |
|
||||
opacity: .7; |
|
||||
} |
|
||||
#publish { |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 14px; |
|
||||
color: #FFF; |
|
||||
background-color: #217CE8; |
|
||||
border-radius: 5px; |
|
||||
width: 143px; |
|
||||
height: 38px; |
|
||||
margin-left: 10px; |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
text-align: center; |
|
||||
line-height: 38px; |
|
||||
margin-right: 20px; |
|
||||
transition: all .2s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
#publish:hover { |
|
||||
cursor: pointer; |
|
||||
opacity: .7; |
|
||||
} |
|
||||
#buttonsright { |
|
||||
float: right; |
|
||||
margin-top: 15px; |
|
||||
} |
|
||||
#leftcard { |
|
||||
width: 363px; |
|
||||
background-color: #FFF; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
box-sizing: border-box; |
|
||||
padding-top: 85px; |
|
||||
padding-left: 20px; |
|
||||
height: 100%; |
|
||||
position: absolute; |
|
||||
z-index: 2; |
|
||||
} |
|
||||
#search input { |
|
||||
width: 318px; |
|
||||
height: 40px; |
|
||||
background-color: #FFF; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
box-sizing: border-box; |
|
||||
box-shadow: 0px 2px 8px rgba(34,34,87,0.05); |
|
||||
border-radius: 5px; |
|
||||
text-indent: 35px; |
|
||||
font-family: Roboto; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
::-webkit-input-placeholder { /* Edge */ |
|
||||
color: #C9C9D5; |
|
||||
} |
|
||||
|
|
||||
:-ms-input-placeholder { /* Internet Explorer 10-11 */ |
|
||||
color: #C9C9D5 |
|
||||
} |
|
||||
|
|
||||
::placeholder { |
|
||||
color: #C9C9D5; |
|
||||
} |
|
||||
#search img { |
|
||||
position: absolute; |
|
||||
margin-top: 10px; |
|
||||
width: 18px; |
|
||||
margin-left: 12px; |
|
||||
} |
|
||||
#header { |
|
||||
font-size: 20px; |
|
||||
font-family: Roboto; |
|
||||
font-weight: bold; |
|
||||
color: #393C44; |
|
||||
} |
|
||||
#subnav { |
|
||||
border-bottom: 1px solid #E8E8EF; |
|
||||
width: calc(100% + 20px); |
|
||||
margin-left: -20px; |
|
||||
margin-top: 10px; |
|
||||
} |
|
||||
.navdisabled { |
|
||||
transition: all .3s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
.navdisabled:hover { |
|
||||
cursor: pointer; |
|
||||
opacity: .5; |
|
||||
} |
|
||||
.navactive { |
|
||||
color: #393C44!important; |
|
||||
} |
|
||||
#triggers { |
|
||||
margin-left: 20px; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
color: #808292; |
|
||||
width: calc(88% / 3); |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
display: inline-block; |
|
||||
float: left; |
|
||||
} |
|
||||
.navactive:after { |
|
||||
display: block; |
|
||||
content: ""; |
|
||||
width: 100%; |
|
||||
height: 4px; |
|
||||
background-color: #217CE8; |
|
||||
margin-top: -4px; |
|
||||
} |
|
||||
#actions { |
|
||||
display: inline-block; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
width: calc(88% / 3); |
|
||||
text-align: center; |
|
||||
float: left; |
|
||||
} |
|
||||
#loggers { |
|
||||
width: calc(88% / 3); |
|
||||
display: inline-block; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
text-align: center; |
|
||||
} |
|
||||
#footer { |
|
||||
position: absolute; |
|
||||
left: 0; |
|
||||
padding-left: 20px; |
|
||||
line-height: 40px; |
|
||||
bottom: 0; |
|
||||
width: 362px; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
height: 67px; |
|
||||
box-sizing: border-box; |
|
||||
background-color: #FFF; |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
} |
|
||||
#footer a { |
|
||||
text-decoration: none; |
|
||||
color: #393C44; |
|
||||
transition: all .2s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
#footer a:hover { |
|
||||
opacity: .5; |
|
||||
} |
|
||||
#footer span { |
|
||||
color: #808292; |
|
||||
} |
|
||||
#footer p { |
|
||||
display: inline-block; |
|
||||
color: #808292; |
|
||||
} |
|
||||
#footer img { |
|
||||
margin-left: 5px; |
|
||||
margin-right: 5px; |
|
||||
} |
|
||||
.blockelem:first-child { |
|
||||
margin-top: 20px |
|
||||
} |
|
||||
.blockelem { |
|
||||
padding-top: 10px; |
|
||||
width: 318px; |
|
||||
border: 1px solid transparent; |
|
||||
transition-property: box-shadow, height; |
|
||||
transition-duration: .2s; |
|
||||
transition-timing-function: cubic-bezier(.05,.03,.35,1); |
|
||||
border-radius: 5px; |
|
||||
box-shadow: 0px 0px 30px rgba(22, 33, 74, 0); |
|
||||
box-sizing: border-box; |
|
||||
} |
|
||||
.blockelem:hover { |
|
||||
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08); |
|
||||
border-radius: 5px; |
|
||||
background-color: #FFF; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
.grabme, .blockico { |
|
||||
display: inline-block; |
|
||||
} |
|
||||
|
|
||||
.grabme { |
|
||||
margin-top: 10px; |
|
||||
margin-left: 10px; |
|
||||
margin-bottom: -14px; |
|
||||
width: 15px; |
|
||||
} |
|
||||
#blocklist { |
|
||||
height: calc(100% - 220px); |
|
||||
overflow: auto; |
|
||||
} |
|
||||
#proplist { |
|
||||
height: calc(100% - 305px); |
|
||||
overflow: auto; |
|
||||
margin-top: -30px; |
|
||||
padding-top: 30px; |
|
||||
} |
|
||||
.blockin { |
|
||||
display: inline-block; |
|
||||
vertical-align: top; |
|
||||
margin-left: 12px; |
|
||||
} |
|
||||
.blockico { |
|
||||
width: 36px; |
|
||||
height: 36px; |
|
||||
background-color: #F1F4FC; |
|
||||
border-radius: 5px; |
|
||||
text-align: center; |
|
||||
white-space: nowrap; |
|
||||
} |
|
||||
|
|
||||
.blockico i { |
|
||||
font-size: 24px; |
|
||||
color: var(--grey-4); |
|
||||
} |
|
||||
|
|
||||
.blockico span { |
|
||||
height: 100%; |
|
||||
width: 0px; |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
.blockico img { |
|
||||
vertical-align: middle; |
|
||||
margin-left: auto; |
|
||||
margin-right: auto; |
|
||||
display: inline-block; |
|
||||
} |
|
||||
.blocktext { |
|
||||
display: inline-block; |
|
||||
width: 220px; |
|
||||
vertical-align: top; |
|
||||
margin-left: 12px |
|
||||
} |
|
||||
.blocktitle { |
|
||||
margin: 0px!important; |
|
||||
padding: 0px!important; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 16px; |
|
||||
color: #393C44; |
|
||||
} |
|
||||
.blockdesc { |
|
||||
margin-top: 5px; |
|
||||
font-family: Roboto; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
line-height: 21px; |
|
||||
} |
|
||||
.blockdisabled { |
|
||||
background-color: #F0F2F9; |
|
||||
opacity: .5; |
|
||||
} |
|
||||
#closecard { |
|
||||
position: absolute; |
|
||||
margin-left: 340px; |
|
||||
background-color: #FFF; |
|
||||
border-radius: 0px 5px 5px 0px; |
|
||||
border-bottom: 1px solid #E8E8EF; |
|
||||
border-right: 1px solid #E8E8EF; |
|
||||
border-top: 1px solid #E8E8EF; |
|
||||
width: 53px; |
|
||||
height: 53px; |
|
||||
text-align: center; |
|
||||
z-index: 10; |
|
||||
} |
|
||||
#closecard img { |
|
||||
margin-top: 15px |
|
||||
} |
|
||||
#canvas { |
|
||||
border: 1px solid green; |
|
||||
position: absolute; |
|
||||
width: calc(100% - 361px); |
|
||||
height: calc(100% - 71px); |
|
||||
top: 71px; |
|
||||
left: 361px; |
|
||||
z-index: 0; |
|
||||
overflow: auto; |
|
||||
} |
|
||||
#propwrap { |
|
||||
position: absolute; |
|
||||
right: 0; |
|
||||
top: 0; |
|
||||
width: 311px; |
|
||||
height: 100%; |
|
||||
padding-left: 20px; |
|
||||
overflow: hidden; |
|
||||
z-index: -2; |
|
||||
} |
|
||||
#properties { |
|
||||
position: absolute; |
|
||||
height: 100%; |
|
||||
width: 311px; |
|
||||
background-color: #FFF; |
|
||||
right: -150px; |
|
||||
opacity: 0; |
|
||||
z-index: 2; |
|
||||
top: 0px; |
|
||||
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0); |
|
||||
padding-left: 20px; |
|
||||
transition: all .25s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
.itson { |
|
||||
z-index: 2!important; |
|
||||
} |
|
||||
.expanded { |
|
||||
right: 0!important; |
|
||||
opacity: 1!important; |
|
||||
box-shadow: -4px 0px 40px rgba(26, 26, 73, 0.05); |
|
||||
z-index: 2; |
|
||||
} |
|
||||
#header2 { |
|
||||
font-size: 20px; |
|
||||
font-family: Roboto; |
|
||||
font-weight: bold; |
|
||||
color: #393C44; |
|
||||
margin-top: 101px; |
|
||||
} |
|
||||
#close { |
|
||||
margin-top: 100px; |
|
||||
position: absolute; |
|
||||
right: 20px; |
|
||||
z-index: 9999; |
|
||||
transition: all .25s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
#close:hover { |
|
||||
cursor: pointer; |
|
||||
opacity: .7; |
|
||||
} |
|
||||
#propswitch { |
|
||||
border-bottom: 1px solid #E8E8EF; |
|
||||
width: 331px; |
|
||||
margin-top: 10px; |
|
||||
margin-left: -20px; |
|
||||
margin-bottom: 30px; |
|
||||
} |
|
||||
#dataprop { |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
color: #393C44; |
|
||||
width: calc(88% / 3); |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
display: inline-block; |
|
||||
float: left; |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
#dataprop:after { |
|
||||
display: block; |
|
||||
content: ""; |
|
||||
width: 100%; |
|
||||
height: 4px; |
|
||||
background-color: #217CE8; |
|
||||
margin-top: -4px; |
|
||||
} |
|
||||
#alertprop { |
|
||||
display: inline-block; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
width: calc(88% / 3); |
|
||||
text-align: center; |
|
||||
float: left; |
|
||||
} |
|
||||
#logsprop { |
|
||||
width: calc(88% / 3); |
|
||||
display: inline-block; |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
color: #808292; |
|
||||
font-size: 14px; |
|
||||
height: 48px; |
|
||||
line-height: 48px; |
|
||||
text-align: center; |
|
||||
} |
|
||||
.inputlabel { |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
color: #253134; |
|
||||
} |
|
||||
.dropme { |
|
||||
background-color: #FFF; |
|
||||
border-radius: 5px; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
box-shadow: 0px 2px 8px rgba(34, 34, 87, 0.05); |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
color: #253134; |
|
||||
text-indent: 20px; |
|
||||
height: 40px; |
|
||||
line-height: 40px; |
|
||||
width: 287px; |
|
||||
margin-bottom: 25px; |
|
||||
} |
|
||||
.dropme img { |
|
||||
margin-top: 17px; |
|
||||
float: right; |
|
||||
margin-right: 15px; |
|
||||
} |
|
||||
.checkus { |
|
||||
margin-bottom: 10px; |
|
||||
} |
|
||||
.checkus img { |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
.checkus p { |
|
||||
display: inline-block; |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
vertical-align: middle; |
|
||||
margin-left: 10px; |
|
||||
} |
|
||||
#divisionthing { |
|
||||
height: 1px; |
|
||||
width: 100%; |
|
||||
background-color: #E8E8EF; |
|
||||
position: absolute; |
|
||||
right: 0px; |
|
||||
bottom: 80; |
|
||||
} |
|
||||
#removeblock { |
|
||||
border-radius: 5px; |
|
||||
position: absolute; |
|
||||
bottom: 20px; |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
text-align: center; |
|
||||
width: 287px; |
|
||||
height: 38px; |
|
||||
line-height: 38px; |
|
||||
color: #253134; |
|
||||
border: 1px solid #E8E8EF; |
|
||||
transition: all .3s cubic-bezier(.05,.03,.35,1); |
|
||||
} |
|
||||
#removeblock:hover { |
|
||||
cursor: pointer; |
|
||||
opacity: .5; |
|
||||
} |
|
||||
.noselect { |
|
||||
-webkit-touch-callout: none; /* iOS Safari */ |
|
||||
-webkit-user-select: none; /* Safari */ |
|
||||
-khtml-user-select: none; /* Konqueror HTML */ |
|
||||
-moz-user-select: none; /* Old versions of Firefox */ |
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */ |
|
||||
user-select: none; /* Non-prefixed version, currently |
|
||||
supported by Chrome, Opera and Firefox */ |
|
||||
} |
|
||||
.blockyname { |
|
||||
font-family: Roboto; |
|
||||
font-weight: 500; |
|
||||
color: #253134; |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
margin-left: 8px; |
|
||||
font-size: 16px; |
|
||||
} |
|
||||
.blockyleft img { |
|
||||
display: inline-block; |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
.blockyright { |
|
||||
display: inline-block; |
|
||||
float: right; |
|
||||
vertical-align: middle; |
|
||||
margin-right: 20px; |
|
||||
margin-top: 10px; |
|
||||
width: 28px; |
|
||||
height: 28px; |
|
||||
border-radius: 5px; |
|
||||
text-align: center; |
|
||||
background-color: #FFF; |
|
||||
transition: all .3s cubic-bezier(.05,.03,.35,1); |
|
||||
z-index: 10; |
|
||||
} |
|
||||
.blockyright:hover { |
|
||||
background-color: #F1F4FC; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
.blockyright img { |
|
||||
margin-top: 12px; |
|
||||
} |
|
||||
.blockyleft { |
|
||||
display: inline-block; |
|
||||
margin-left: 20px; |
|
||||
} |
|
||||
.blockydiv { |
|
||||
width: 100%; |
|
||||
height: 1px; |
|
||||
background-color: #E9E9EF; |
|
||||
} |
|
||||
.blockyinfo { |
|
||||
font-family: Roboto; |
|
||||
font-size: 14px; |
|
||||
color: #808292; |
|
||||
margin-top: 15px; |
|
||||
text-indent: 20px; |
|
||||
margin-bottom: 20px; |
|
||||
} |
|
||||
.blockyinfo span { |
|
||||
color: #253134; |
|
||||
font-weight: 500; |
|
||||
display: inline-block; |
|
||||
border-bottom: 1px solid #D3DCEA; |
|
||||
line-height: 20px; |
|
||||
text-indent: 0px; |
|
||||
} |
|
||||
.block { |
|
||||
background-color: #FFF; |
|
||||
margin-top: 0px!important; |
|
||||
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.05); |
|
||||
} |
|
||||
.selectedblock { |
|
||||
border: 2px solid #217CE8; |
|
||||
box-shadow: 0px 4px 30px rgba(22, 33, 74, 0.08); |
|
||||
} |
|
||||
|
|
||||
@media only screen and (max-width: 832px) { |
|
||||
#centerswitch { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
@media only screen and (max-width: 560px) { |
|
||||
#names { |
|
||||
display: none; |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1 @@ |
|||||
|
<slot /> |
||||
Loading…
Reference in new issue