Merge branch 'master' of github.com:Budibase/budibase into deployment
This commit is contained in:
commit
7f40e89e67
|
@ -55,7 +55,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@budibase/bbui": "^1.13.0",
|
"@budibase/bbui": "^1.15.0",
|
||||||
"@budibase/client": "^0.0.32",
|
"@budibase/client": "^0.0.32",
|
||||||
"@nx-js/compiler-util": "^2.0.0",
|
"@nx-js/compiler-util": "^2.0.0",
|
||||||
"codemirror": "^5.51.0",
|
"codemirror": "^5.51.0",
|
||||||
|
@ -70,6 +70,7 @@
|
||||||
"safe-buffer": "^5.1.2",
|
"safe-buffer": "^5.1.2",
|
||||||
"shortid": "^2.2.8",
|
"shortid": "^2.2.8",
|
||||||
"string_decoder": "^1.2.0",
|
"string_decoder": "^1.2.0",
|
||||||
|
"svelte-portal": "^0.1.0",
|
||||||
"svelte-simple-modal": "^0.4.2",
|
"svelte-simple-modal": "^0.4.2",
|
||||||
"uikit": "^3.1.7"
|
"uikit": "^3.1.7"
|
||||||
},
|
},
|
||||||
|
|
|
@ -185,7 +185,11 @@ export default {
|
||||||
svelte({
|
svelte({
|
||||||
// enable run-time checks when not in production
|
// enable run-time checks when not in production
|
||||||
dev: !production,
|
dev: !production,
|
||||||
include: ["src/**/*.svelte", "node_modules/**/*.svelte"],
|
include: [
|
||||||
|
"src/**/*.svelte",
|
||||||
|
"node_modules/**/*.svelte",
|
||||||
|
"../../../bbui/src/**/*.svelte",
|
||||||
|
],
|
||||||
// we'll extract any component CSS out into
|
// we'll extract any component CSS out into
|
||||||
// a separate file — better for performance
|
// a separate file — better for performance
|
||||||
css: css => {
|
css: css => {
|
||||||
|
|
|
@ -27,9 +27,6 @@ export const getBackendUiStore = () => {
|
||||||
const views = await viewsResponse.json()
|
const views = await viewsResponse.json()
|
||||||
store.update(state => {
|
store.update(state => {
|
||||||
state.selectedDatabase = db
|
state.selectedDatabase = db
|
||||||
if (models && models.length > 0) {
|
|
||||||
store.actions.models.select(models[0])
|
|
||||||
}
|
|
||||||
state.models = models
|
state.models = models
|
||||||
state.views = views
|
state.views = views
|
||||||
return state
|
return state
|
||||||
|
|
|
@ -109,8 +109,8 @@ const setPackage = (store, initial) => async pkg => {
|
||||||
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
initial.builtins = [getBuiltin("##builtin/screenslot")]
|
||||||
initial.appInstances = pkg.application.instances
|
initial.appInstances = pkg.application.instances
|
||||||
initial.appId = pkg.application._id
|
initial.appId = pkg.application._id
|
||||||
|
|
||||||
store.set(initial)
|
store.set(initial)
|
||||||
|
await backendUiStore.actions.database.select(initial.appInstances[0])
|
||||||
return initial
|
return initial
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
class={determineClassName(type)}
|
class={determineClassName(type)}
|
||||||
bind:value
|
bind:value
|
||||||
class:uk-form-danger={errors.length > 0}>
|
class:uk-form-danger={errors.length > 0}>
|
||||||
|
<option></option>
|
||||||
{#each options as opt}
|
{#each options as opt}
|
||||||
<option value={opt}>{opt}</option>
|
<option value={opt}>{opt}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
|
|
||||||
function selectModel(model, fieldId) {
|
function selectModel(model, fieldId) {
|
||||||
backendUiStore.actions.models.select(model)
|
backendUiStore.actions.models.select(model)
|
||||||
|
$goto(`./model/${model._id}`)
|
||||||
if (fieldId) {
|
if (fieldId) {
|
||||||
backendUiStore.update(state => {
|
backendUiStore.update(state => {
|
||||||
state.selectedField = fieldId
|
state.selectedField = fieldId
|
||||||
|
|
|
@ -0,0 +1,56 @@
|
||||||
|
<script>
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
export let remove = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="content">
|
||||||
|
<div class="img">
|
||||||
|
<img src="https://picsum.photos/60/60" alt="zoom" />
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<div class="title">Zoom</div>
|
||||||
|
<div class="description">
|
||||||
|
Lorem, ipsum dolor sit amet consectetur adipisicing elit
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer">
|
||||||
|
<Button wide error={remove} secondary={!remove} on:click>
|
||||||
|
<span>{remove ? 'Remove' : 'Add'}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
padding: 12px;
|
||||||
|
background: var(--light-grey);
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 12px;
|
||||||
|
grid-template-columns: 60px auto;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.description {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
.img {
|
||||||
|
border-radius: 3px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
img {
|
||||||
|
height: 60px;
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,48 @@
|
||||||
|
<script>
|
||||||
|
import Modal from "./Modal.svelte"
|
||||||
|
import { SettingsIcon } from "components/common/Icons/"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { isActive, goto, layout } from "@sveltech/routify"
|
||||||
|
|
||||||
|
// Handle create app modal
|
||||||
|
const { open } = getContext("simple-modal")
|
||||||
|
|
||||||
|
const showSettingsModal = () => {
|
||||||
|
open(
|
||||||
|
Modal,
|
||||||
|
{
|
||||||
|
name: "Placeholder App Name",
|
||||||
|
description: "This is a hardcoded description that needs to change",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
closeButton: false,
|
||||||
|
closeOnEsc: true,
|
||||||
|
styleContent: { padding: 0 },
|
||||||
|
closeOnOuterClick: true,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="topnavitemright" on:click={showSettingsModal}>
|
||||||
|
<SettingsIcon />
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
span:first-letter {
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
.topnavitemright {
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--ink-light);
|
||||||
|
margin: 0px 20px 0px 0px;
|
||||||
|
padding-top: 4px;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1rem;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
align-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,92 @@
|
||||||
|
<script>
|
||||||
|
import { General, Users, DangerZone } from "./tabs"
|
||||||
|
|
||||||
|
import { Input, TextArea, Button, Switcher } from "@budibase/bbui"
|
||||||
|
import { SettingsIcon, CloseIcon } from "components/common/Icons/"
|
||||||
|
import { getContext } from "svelte"
|
||||||
|
import { post } from "builderStore/api"
|
||||||
|
|
||||||
|
const { open, close } = getContext("simple-modal")
|
||||||
|
export let name = ""
|
||||||
|
export let description = ""
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: "General",
|
||||||
|
key: "GENERAL",
|
||||||
|
component: General,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Users",
|
||||||
|
key: "USERS",
|
||||||
|
component: Users,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Danger Zone",
|
||||||
|
key: "DANGERZONE",
|
||||||
|
component: DangerZone,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
let value = "GENERAL"
|
||||||
|
$: selectedTab = tabs.find(tab => tab.key === value).component
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="body">
|
||||||
|
<div class="heading">
|
||||||
|
<span class="icon">
|
||||||
|
<SettingsIcon />
|
||||||
|
</span>
|
||||||
|
<h3>Settings</h3>
|
||||||
|
</div>
|
||||||
|
<Switcher headings={tabs} bind:value>
|
||||||
|
<svelte:component this={selectedTab} />
|
||||||
|
</Switcher>
|
||||||
|
</div>
|
||||||
|
<div class="close-button" on:click={close}>
|
||||||
|
<CloseIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-button {
|
||||||
|
cursor: pointer;
|
||||||
|
position: absolute;
|
||||||
|
top: 20px;
|
||||||
|
right: 20px;
|
||||||
|
}
|
||||||
|
.close-button :global(svg) {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
}
|
||||||
|
.heading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.icon {
|
||||||
|
display: grid;
|
||||||
|
border-radius: 3px;
|
||||||
|
align-content: center;
|
||||||
|
justify-content: center;
|
||||||
|
margin-right: 12px;
|
||||||
|
height: 20px;
|
||||||
|
width: 20px;
|
||||||
|
padding: 10px;
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
}
|
||||||
|
.body {
|
||||||
|
padding: 40px 40px 80px 40px;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
<h3>
|
||||||
|
<slot />
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
h3 {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--ink);
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
import { Input, Select, Button } from "@budibase/bbui"
|
||||||
|
export let user
|
||||||
|
|
||||||
|
let editMode = false
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="inputs">
|
||||||
|
<Input
|
||||||
|
disabled
|
||||||
|
thin
|
||||||
|
bind:value={user.username}
|
||||||
|
name="Name"
|
||||||
|
placeholder="Username" />
|
||||||
|
<Select disabled={!editMode} bind:value={user.accessLevelId} thin>
|
||||||
|
<option value="ADMIN">Admin</option>
|
||||||
|
<option value="POWER_USER">Power User</option>
|
||||||
|
</Select>
|
||||||
|
{#if editMode}
|
||||||
|
<Button
|
||||||
|
blue
|
||||||
|
on:click={() => {
|
||||||
|
dispatch('save', user)
|
||||||
|
editMode = false
|
||||||
|
}}>
|
||||||
|
Save
|
||||||
|
</Button>
|
||||||
|
{:else}
|
||||||
|
<Button secondary on:click={() => (editMode = true)}>Edit</Button>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.inputs {
|
||||||
|
display: grid;
|
||||||
|
justify-items: stretch;
|
||||||
|
grid-gap: 18px;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,46 @@
|
||||||
|
<script>
|
||||||
|
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||||
|
import Title from "../TabTitle.svelte"
|
||||||
|
|
||||||
|
let value = ""
|
||||||
|
let loading = false
|
||||||
|
|
||||||
|
const deleteApp = () => {
|
||||||
|
loading = true
|
||||||
|
// Do stuff here to delete app!
|
||||||
|
// Navigate to start
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Title>Danger Zone</Title>
|
||||||
|
<div class="background">
|
||||||
|
<Input
|
||||||
|
on:change={e => (value = e.target.value)}
|
||||||
|
on:input={e => (value = e.target.value)}
|
||||||
|
thin
|
||||||
|
disabled={loading}
|
||||||
|
placeholder="Enter your name"
|
||||||
|
label="Type DELETE into the textbox, then click the following button to
|
||||||
|
delete your web app:" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
disabled={value !== 'DELETE' || loading}
|
||||||
|
primary
|
||||||
|
wide
|
||||||
|
on:click={deleteApp}>
|
||||||
|
Delete Entire Web App
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.background {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--space);
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
padding: 12px 12px 18px 12px;
|
||||||
|
}
|
||||||
|
.background :global(button) {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,26 @@
|
||||||
|
<script>
|
||||||
|
import { Input, TextArea, Button } from "@budibase/bbui"
|
||||||
|
import Title from "../TabTitle.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Title>General</Title>
|
||||||
|
<div class="container">
|
||||||
|
<div class="background">
|
||||||
|
<Input thin edit placeholder="Enter your name" label="Name" />
|
||||||
|
</div>
|
||||||
|
<div class="background">
|
||||||
|
<TextArea thin edit placeholder="Enter your name" label="Name" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: var(--space);
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
padding: 12px 12px 18px 12px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,45 @@
|
||||||
|
<script>
|
||||||
|
import Integration from "../Integration.svelte"
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="title">Your Integrations</div>
|
||||||
|
<div class="integrations">
|
||||||
|
<Integration remove />
|
||||||
|
<Integration remove />
|
||||||
|
<Integration remove />
|
||||||
|
<Integration remove />
|
||||||
|
</div>
|
||||||
|
<div class="apps">Recommended apps</div>
|
||||||
|
<div class="integrations">
|
||||||
|
<Integration />
|
||||||
|
<Integration />
|
||||||
|
<Integration />
|
||||||
|
<Integration />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 16px;
|
||||||
|
}
|
||||||
|
.integrations {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 20px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.apps {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--light-grey);
|
||||||
|
padding: 12px 12px 18px 12px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1 @@
|
||||||
|
Permissions
|
|
@ -0,0 +1,135 @@
|
||||||
|
<script>
|
||||||
|
import { Input, Select, Button } from "@budibase/bbui"
|
||||||
|
import Title from "../TabTitle.svelte"
|
||||||
|
import UserRow from "../UserRow.svelte"
|
||||||
|
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import api from "builderStore/api"
|
||||||
|
// import * as api from "../api"
|
||||||
|
|
||||||
|
let username = ""
|
||||||
|
let password = ""
|
||||||
|
let accessLevelId
|
||||||
|
|
||||||
|
$: valid = username && password && accessLevelId
|
||||||
|
$: appId = $store.appId
|
||||||
|
|
||||||
|
// Create user!
|
||||||
|
async function createUser() {
|
||||||
|
if (valid) {
|
||||||
|
const user = { name: username, username, password, accessLevelId }
|
||||||
|
const response = await api.post(`/api/users`, user)
|
||||||
|
const json = await response.json()
|
||||||
|
backendUiStore.actions.users.create(json)
|
||||||
|
fetchUsersPromise = fetchUsers()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update user!
|
||||||
|
async function updateUser(event) {
|
||||||
|
let data = event.detail
|
||||||
|
delete data.password
|
||||||
|
const response = await api.put(`/api/users`, data)
|
||||||
|
const users = await response.json()
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.users = users
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
fetchUsersPromise = fetchUsers()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get users
|
||||||
|
async function fetchUsers() {
|
||||||
|
const response = await api.get(`/api/users`)
|
||||||
|
const users = await response.json()
|
||||||
|
backendUiStore.update(state => {
|
||||||
|
state.users = users
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
return users
|
||||||
|
}
|
||||||
|
|
||||||
|
let fetchUsersPromise = fetchUsers()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Title>Users</Title>
|
||||||
|
<div class="container">
|
||||||
|
<div class="background create">
|
||||||
|
<div class="title">Create new user</div>
|
||||||
|
<div class="inputs">
|
||||||
|
<Input thin bind:value={username} name="Name" placeholder="Username" />
|
||||||
|
<Input
|
||||||
|
thin
|
||||||
|
bind:value={password}
|
||||||
|
name="Password"
|
||||||
|
placeholder="Password" />
|
||||||
|
<Select bind:value={accessLevelId} thin>
|
||||||
|
<option value="ADMIN">Admin</option>
|
||||||
|
<option value="POWER_USER">Power User</option>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div class="create-button">
|
||||||
|
<Button on:click={createUser} small blue>Create</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="background">
|
||||||
|
<div class="title">Current Users</div>
|
||||||
|
{#await fetchUsersPromise}
|
||||||
|
Loading state!
|
||||||
|
{:then users}
|
||||||
|
<ul>
|
||||||
|
{#each users as user}
|
||||||
|
<li>
|
||||||
|
<UserRow {user} on:save={updateUser} />
|
||||||
|
</li>
|
||||||
|
{:else}
|
||||||
|
<li>No Users found</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{:catch error}
|
||||||
|
err0r
|
||||||
|
{/await}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.container {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 14px;
|
||||||
|
}
|
||||||
|
.background {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 12px;
|
||||||
|
border-radius: 5px;
|
||||||
|
background-color: var(--grey-2);
|
||||||
|
padding: 12px 12px 18px 12px;
|
||||||
|
}
|
||||||
|
.background.create {
|
||||||
|
background-color: var(--blue-light);
|
||||||
|
}
|
||||||
|
.inputs :global(select) {
|
||||||
|
padding: 12px 9px;
|
||||||
|
height: initial;
|
||||||
|
}
|
||||||
|
.create-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
right: 12px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
.inputs {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 18px;
|
||||||
|
grid-template-columns: 1fr 1fr 1fr;
|
||||||
|
}
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,5 @@
|
||||||
|
export { default as General } from "./General.svelte"
|
||||||
|
export { default as Integrations } from "./Integrations.svelte"
|
||||||
|
export { default as Permissions } from "./Permissions.svelte"
|
||||||
|
export { default as Users } from "./Users.svelte"
|
||||||
|
export { default as DangerZone } from "./DangerZone.svelte"
|
|
@ -25,38 +25,69 @@
|
||||||
name: "Screen Placeholder",
|
name: "Screen Placeholder",
|
||||||
route: "*",
|
route: "*",
|
||||||
props: {
|
props: {
|
||||||
_component: "@budibase/standard-components/container",
|
"_id": "49c3d0a2-7028-46f0-b004-7eddf62ad01c",
|
||||||
type: "div",
|
"_component": "@budibase/standard-components/container",
|
||||||
_children: [
|
"_styles": {
|
||||||
|
"normal": {
|
||||||
|
"padding": "0px",
|
||||||
|
"font-family": "Roboto",
|
||||||
|
"border-width": "0",
|
||||||
|
"border-style": "None",
|
||||||
|
"text-align": "center"
|
||||||
|
},
|
||||||
|
"hover": {},
|
||||||
|
"active": {},
|
||||||
|
"selected": {}
|
||||||
|
},
|
||||||
|
"_code": "",
|
||||||
|
"className": "",
|
||||||
|
"onLoad": [],
|
||||||
|
"type": "div",
|
||||||
|
"_children": [
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/container",
|
"_id": "335428f7-f9ca-4acd-9e76-71bc8ad27324",
|
||||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
"_component": "@budibase/standard-components/container",
|
||||||
_id: "__screenslot__text",
|
"_styles": {
|
||||||
_code: "",
|
"normal": {
|
||||||
className: "",
|
"padding": "16px",
|
||||||
onLoad: [],
|
"border-style": "Dashed",
|
||||||
type: "div",
|
"border-width": "2px",
|
||||||
_children: [
|
"border-color": "#8a8989fa"
|
||||||
|
},
|
||||||
|
"hover": {},
|
||||||
|
"active": {},
|
||||||
|
"selected": {}
|
||||||
|
},
|
||||||
|
"_code": "",
|
||||||
|
"className": "",
|
||||||
|
"onLoad": [],
|
||||||
|
"type": "div",
|
||||||
|
"_instanceId": "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3",
|
||||||
|
"_instanceName": "Container",
|
||||||
|
"_children": [
|
||||||
{
|
{
|
||||||
_component: "@budibase/standard-components/text",
|
"_id": "ddb6a225-33ba-4ba8-91da-bc6a2697ebf9",
|
||||||
_styles: {
|
"_component": "@budibase/standard-components/heading",
|
||||||
normal: {},
|
"_styles": {
|
||||||
hover: {},
|
"normal": {
|
||||||
active: {},
|
"font-family": "Roboto"
|
||||||
selected: {},
|
|
||||||
},
|
},
|
||||||
_id: "__screenslot__text_2",
|
"hover": {},
|
||||||
_code: "",
|
"active": {},
|
||||||
text: "content",
|
"selected": {}
|
||||||
font: "",
|
|
||||||
color: "",
|
|
||||||
textAlign: "inline",
|
|
||||||
verticalAlign: "inline",
|
|
||||||
formattingTag: "none",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
|
"_code": "",
|
||||||
|
"className": "",
|
||||||
|
"text": "Your screens go here",
|
||||||
|
"type": "h1",
|
||||||
|
"_instanceId": "inst_b3b4e95_ab0df02dda3f4d8eb4b35eea2968bad3",
|
||||||
|
"_instanceName": "Heading",
|
||||||
|
"_children": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
"_instanceName": "Content Placeholder"
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
export default `<html>
|
export default `<html>
|
||||||
<head>
|
<head>
|
||||||
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||||
<style>
|
<style>
|
||||||
body, html {
|
body, html {
|
||||||
height: 100%!important;
|
height: 100%!important;
|
||||||
font-family: Roboto !important;
|
font-family: Roboto !important;
|
||||||
}
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
.lay-__screenslot__text {
|
.lay-__screenslot__text {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as drag } from "./drag.js"
|
||||||
|
export { default as keyevents } from "./key-events.js"
|
|
@ -0,0 +1,41 @@
|
||||||
|
//events: Array<{trigger: fn}>
|
||||||
|
export default function(node, events = []) {
|
||||||
|
const ev = Object.entries(events)
|
||||||
|
let fns = []
|
||||||
|
|
||||||
|
for (let [trigger, fn] of ev) {
|
||||||
|
let f = addEvent(trigger, fn)
|
||||||
|
fns = [...fns, f]
|
||||||
|
}
|
||||||
|
|
||||||
|
function _scaffold(trigger, fn) {
|
||||||
|
return () => {
|
||||||
|
let trig = parseInt(trigger)
|
||||||
|
if (trig) {
|
||||||
|
if (event.keyCode === trig) {
|
||||||
|
fn(event)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (event.key === trigger) {
|
||||||
|
fn(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEvent(trigger, fn) {
|
||||||
|
let f = _scaffold(trigger, fn)
|
||||||
|
node.addEventListener("keydown", f)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeEvents() {
|
||||||
|
fns.forEach(f => node.removeEventListener("keypress", f))
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
destroy() {
|
||||||
|
removeEvents()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
<script>
|
<script>
|
||||||
import { buildStyle } from "./helpers.js"
|
import {buildStyle} from "../helpers.js"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
|
|
||||||
export let backgroundSize = "10px"
|
export let backgroundSize = "10px"
|
|
@ -3,32 +3,35 @@
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import Swatch from "./Swatch.svelte"
|
import Swatch from "./Swatch.svelte"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
import { buildStyle } from "./helpers.js"
|
import {buildStyle} from "../helpers.js"
|
||||||
import {
|
import {
|
||||||
getColorFormat,
|
getColorFormat,
|
||||||
convertToHSVA,
|
convertToHSVA,
|
||||||
convertHsvaToFormat,
|
convertHsvaToFormat,
|
||||||
} from "./utils.js"
|
} from "../utils.js"
|
||||||
import Slider from "./Slider.svelte"
|
import Slider from "./Slider.svelte"
|
||||||
import Palette from "./Palette.svelte"
|
import Palette from "./Palette.svelte"
|
||||||
import ButtonGroup from "./ButtonGroup.svelte"
|
import ButtonGroup from "./ButtonGroup.svelte"
|
||||||
import Input from "./Input.svelte"
|
import Input from "./Input.svelte"
|
||||||
|
import Portal from "./Portal.svelte"
|
||||||
|
import {keyevents} from "../actions"
|
||||||
|
|
||||||
export let value = "#3ec1d3ff"
|
export let value = "#3ec1d3ff"
|
||||||
|
export let open = false;
|
||||||
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
|
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
|
||||||
export let disableSwatches = false
|
export let disableSwatches = false
|
||||||
export let format = "hexa"
|
export let format = "hexa"
|
||||||
export let open = false
|
export let style = ""
|
||||||
|
|
||||||
export let pickerHeight = 0
|
export let pickerHeight = 0
|
||||||
export let pickerWidth = 0
|
export let pickerWidth = 0
|
||||||
|
|
||||||
|
let colorPicker = null
|
||||||
let adder = null
|
let adder = null
|
||||||
|
|
||||||
let h = null
|
let h = 0
|
||||||
let s = null
|
let s = 0
|
||||||
let v = null
|
let v = 0
|
||||||
let a = null
|
let a = 0
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
@ -38,6 +41,10 @@
|
||||||
getRecentColors()
|
getRecentColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(colorPicker) {
|
||||||
|
colorPicker.focus()
|
||||||
|
}
|
||||||
|
|
||||||
if (format) {
|
if (format) {
|
||||||
convertAndSetHSVA()
|
convertAndSetHSVA()
|
||||||
}
|
}
|
||||||
|
@ -50,6 +57,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleEscape() {
|
||||||
|
if(open) {
|
||||||
|
open = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function setRecentColor(color) {
|
function setRecentColor(color) {
|
||||||
if (swatches.length === 12) {
|
if (swatches.length === 12) {
|
||||||
swatches.splice(0, 1)
|
swatches.splice(0, 1)
|
||||||
|
@ -145,12 +158,19 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
|
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
|
||||||
$: style = buildStyle({ background: value, border })
|
$: selectedColorStyle = buildStyle({ background: value, border })
|
||||||
$: shrink = swatches.length > 0
|
$: shrink = swatches.length > 0
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<Portal>
|
||||||
|
<div
|
||||||
class="colorpicker-container"
|
class="colorpicker-container"
|
||||||
|
use:keyevents={{"Escape": handleEscape}}
|
||||||
|
transition:fade
|
||||||
|
bind:this={colorPicker}
|
||||||
|
{style}
|
||||||
|
tabindex="0"
|
||||||
bind:clientHeight={pickerHeight}
|
bind:clientHeight={pickerHeight}
|
||||||
bind:clientWidth={pickerWidth}>
|
bind:clientWidth={pickerWidth}>
|
||||||
|
|
||||||
|
@ -162,7 +182,7 @@
|
||||||
<div class="alpha-hue-panel">
|
<div class="alpha-hue-panel">
|
||||||
<div>
|
<div>
|
||||||
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
<CheckedBackground borderRadius="50%" backgroundSize="8px">
|
||||||
<div class="selected-color" {style} />
|
<div class="selected-color" style={selectedColorStyle} />
|
||||||
</CheckedBackground>
|
</CheckedBackground>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -195,11 +215,13 @@
|
||||||
{/if}
|
{/if}
|
||||||
{#if swatches.length !== 12}
|
{#if swatches.length !== 12}
|
||||||
<div
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
use:keyevents={{"Enter": addSwatch}}
|
||||||
bind:this={adder}
|
bind:this={adder}
|
||||||
transition:fade
|
transition:fade
|
||||||
class="adder"
|
class="adder"
|
||||||
on:click={addSwatch}
|
class:shrink
|
||||||
class:shrink>
|
on:click={addSwatch}>
|
||||||
<span>+</span>
|
<span>+</span>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -215,15 +237,20 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</Portal>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.colorpicker-container {
|
.colorpicker-container {
|
||||||
|
position: absolute;
|
||||||
|
outline: none;
|
||||||
|
z-index: 3;
|
||||||
display: flex;
|
display: flex;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
transition: top 0.1s, left 0.1s;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
/* height: 265px; */
|
margin: 5px 0px;
|
||||||
height: auto;
|
height: auto;
|
||||||
width: 220px;
|
width: 220px;
|
||||||
background: #ffffff;
|
background: #ffffff;
|
||||||
|
@ -273,7 +300,7 @@
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
display: flex;
|
display: flex;
|
||||||
transition: flex 0.5s;
|
transition: flex 0.3s;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #f1f3f4;
|
background: #f1f3f4;
|
||||||
|
@ -283,6 +310,8 @@
|
||||||
margin-left: 5px;
|
margin-left: 5px;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
outline-color: #003cb0;
|
||||||
|
outline-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.shrink {
|
.shrink {
|
|
@ -1,11 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import Colorpicker from "./Colorpicker.svelte"
|
import Colorpicker from "./Colorpicker.svelte"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
import { createEventDispatcher, afterUpdate, beforeUpdate } from "svelte"
|
import { createEventDispatcher, beforeUpdate, onMount } from "svelte"
|
||||||
|
|
||||||
import { buildStyle } from "./helpers.js"
|
import {buildStyle, debounce} from "../helpers.js"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { getColorFormat } from "./utils.js"
|
import { getColorFormat } from "../utils.js"
|
||||||
|
|
||||||
export let value = "#3ec1d3ff"
|
export let value = "#3ec1d3ff"
|
||||||
export let swatches = []
|
export let swatches = []
|
||||||
|
@ -15,7 +15,8 @@
|
||||||
export let height = "25px"
|
export let height = "25px"
|
||||||
|
|
||||||
let format = "hexa"
|
let format = "hexa"
|
||||||
let dimensions = { top: 0, left: 0 }
|
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 }
|
||||||
|
let positionSide = "top"
|
||||||
let colorPreview = null
|
let colorPreview = null
|
||||||
|
|
||||||
let previewHeight = null
|
let previewHeight = null
|
||||||
|
@ -23,17 +24,8 @@
|
||||||
let pickerWidth = 0
|
let pickerWidth = 0
|
||||||
let pickerHeight = 0
|
let pickerHeight = 0
|
||||||
|
|
||||||
let anchorEl = null
|
|
||||||
let parentNodes = []
|
|
||||||
let errorMsg = null
|
let errorMsg = null
|
||||||
|
|
||||||
$: previewStyle = buildStyle({ width, height, background: value })
|
|
||||||
$: errorPreviewStyle = buildStyle({ width, height })
|
|
||||||
$: pickerStyle = buildStyle({
|
|
||||||
top: `${dimensions.top}px`,
|
|
||||||
left: `${dimensions.left}px`,
|
|
||||||
})
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
beforeUpdate(() => {
|
beforeUpdate(() => {
|
||||||
|
@ -46,25 +38,6 @@
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
afterUpdate(() => {
|
|
||||||
if (colorPreview && colorPreview.offsetParent && !anchorEl) {
|
|
||||||
//Anchor relative to closest positioned ancestor element. If none, then anchor to body
|
|
||||||
anchorEl = colorPreview.offsetParent
|
|
||||||
let curEl = colorPreview
|
|
||||||
let els = []
|
|
||||||
//Travel up dom tree from preview element to find parent elements that scroll
|
|
||||||
while (!anchorEl.isSameNode(curEl)) {
|
|
||||||
curEl = curEl.parentNode
|
|
||||||
let elOverflow = window
|
|
||||||
.getComputedStyle(curEl)
|
|
||||||
.getPropertyValue("overflow")
|
|
||||||
if (/scroll|auto/.test(elOverflow)) {
|
|
||||||
els.push(curEl)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
parentNodes = els
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
function openColorpicker(event) {
|
function openColorpicker(event) {
|
||||||
if (colorPreview) {
|
if (colorPreview) {
|
||||||
|
@ -72,45 +45,53 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (open && colorPreview) {
|
function onColorChange(color) {
|
||||||
|
value = color.detail
|
||||||
|
dispatch("change", color.detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateDimensions() {
|
||||||
const {
|
const {
|
||||||
top: spaceAbove,
|
top: spaceAbove,
|
||||||
width,
|
width,
|
||||||
bottom,
|
bottom,
|
||||||
right,
|
right,
|
||||||
left: spaceLeft,
|
left,
|
||||||
} = colorPreview.getBoundingClientRect()
|
} = colorPreview.getBoundingClientRect()
|
||||||
const { innerHeight, innerWidth } = window
|
|
||||||
|
|
||||||
const { offsetLeft, offsetTop } = colorPreview
|
const spaceBelow = window.innerHeight - bottom
|
||||||
//get the scrollTop value for all scrollable parent elements
|
const previewCenter = previewWidth / 2
|
||||||
let scrollTop = parentNodes.reduce(
|
|
||||||
(scrollAcc, el) => (scrollAcc += el.scrollTop),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
|
|
||||||
const spaceBelow = innerHeight - spaceAbove - previewHeight
|
let y, x;
|
||||||
const top =
|
|
||||||
spaceAbove > spaceBelow
|
|
||||||
? offsetTop - pickerHeight - scrollTop
|
|
||||||
: offsetTop + previewHeight - scrollTop
|
|
||||||
|
|
||||||
//TOO: Testing and Scroll Awareness for x Scroll
|
if(spaceAbove > spaceBelow) {
|
||||||
const spaceRight = innerWidth - spaceLeft + previewWidth
|
positionSide = "bottom"
|
||||||
const left =
|
y = (window.innerHeight - spaceAbove)
|
||||||
spaceRight > spaceLeft
|
}else{
|
||||||
? offsetLeft + previewWidth
|
positionSide = "top"
|
||||||
: offsetLeft - pickerWidth
|
y = bottom
|
||||||
|
|
||||||
dimensions = { top, left }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onColorChange(color) {
|
x = (left + previewCenter) - (220 / 2)
|
||||||
value = color.detail
|
|
||||||
dispatch("change", color.detail)
|
dimensions = { [positionSide]: y.toFixed(1), left: x.toFixed(1) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if(open && colorPreview) {
|
||||||
|
calculateDimensions()
|
||||||
|
}
|
||||||
|
|
||||||
|
$: previewStyle = buildStyle({ width, height, background: value })
|
||||||
|
$: errorPreviewStyle = buildStyle({ width, height })
|
||||||
|
$: pickerStyle = buildStyle({
|
||||||
|
[positionSide]: `${dimensions[positionSide]}px`,
|
||||||
|
left: `${dimensions.left}px`,
|
||||||
|
})
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<svelte:window on:resize={debounce(calculateDimensions, 200)} />
|
||||||
|
|
||||||
<div class="color-preview-container">
|
<div class="color-preview-container">
|
||||||
{#if !errorMsg}
|
{#if !errorMsg}
|
||||||
<CheckedBackground borderRadius="3px" backgroundSize="8px">
|
<CheckedBackground borderRadius="3px" backgroundSize="8px">
|
||||||
|
@ -124,8 +105,8 @@
|
||||||
</CheckedBackground>
|
</CheckedBackground>
|
||||||
|
|
||||||
{#if open}
|
{#if open}
|
||||||
<div transition:fade class="picker-container" style={pickerStyle}>
|
|
||||||
<Colorpicker
|
<Colorpicker
|
||||||
|
style={pickerStyle}
|
||||||
on:change={onColorChange}
|
on:change={onColorChange}
|
||||||
on:addswatch
|
on:addswatch
|
||||||
on:removeswatch
|
on:removeswatch
|
||||||
|
@ -133,10 +114,10 @@
|
||||||
bind:value
|
bind:value
|
||||||
bind:pickerHeight
|
bind:pickerHeight
|
||||||
bind:pickerWidth
|
bind:pickerWidth
|
||||||
|
bind:open
|
||||||
{swatches}
|
{swatches}
|
||||||
{disableSwatches}
|
{disableSwatches}
|
||||||
{open} />
|
/>
|
||||||
</div>
|
|
||||||
<div on:click|self={() => (open = false)} class="overlay" />
|
<div on:click|self={() => (open = false)} class="overlay" />
|
||||||
{/if}
|
{/if}
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -154,6 +135,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.color-preview {
|
.color-preview {
|
||||||
|
cursor: pointer;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
border: 1px solid #dedada;
|
border: 1px solid #dedada;
|
||||||
}
|
}
|
||||||
|
@ -166,12 +148,12 @@
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
.picker-container {
|
/* .picker-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 3;
|
z-index: 3;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
} */
|
||||||
|
|
||||||
.overlay {
|
.overlay {
|
||||||
position: fixed;
|
position: fixed;
|
|
@ -1,9 +1,14 @@
|
||||||
<script>
|
<script>
|
||||||
|
import {createEventDispatcher} from "svelte"
|
||||||
|
import {keyevents} from "../actions"
|
||||||
|
|
||||||
export let text = ""
|
export let text = ""
|
||||||
export let selected = false
|
export let selected = false
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flatbutton" class:selected on:click>{text}</div>
|
<div class="flatbutton" tabindex="0" use:keyevents={{"Enter": () => dispatch("click")}} class:selected on:click>{text}</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.flatbutton {
|
.flatbutton {
|
||||||
|
@ -19,11 +24,14 @@
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: #f1f3f4;
|
background: #f1f3f4;
|
||||||
|
outline-color: #003cb0;
|
||||||
|
outline-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected {
|
.selected {
|
||||||
color: #ffffff;
|
color: #ffffff;
|
||||||
background-color: #003cb0;
|
background-color: #003cb0;
|
||||||
border: none;
|
border: none;
|
||||||
|
outline: none;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -0,0 +1,37 @@
|
||||||
|
<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,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, createEventDispatcher } from "svelte"
|
import { onMount, createEventDispatcher } from "svelte"
|
||||||
import dragable from "./drag.js"
|
import {drag, keyevents} from "../actions"
|
||||||
|
|
||||||
export let value = 1
|
export let value = 1
|
||||||
export let type = "hue"
|
export let type = "hue"
|
||||||
|
@ -9,6 +9,10 @@
|
||||||
|
|
||||||
let slider
|
let slider
|
||||||
let sliderWidth = 0
|
let sliderWidth = 0
|
||||||
|
let upperLimit = type === "hue" ? 360 : 1
|
||||||
|
let incrementFactor = type === "hue" ? 1 : 0.01
|
||||||
|
|
||||||
|
const isWithinLimit = value => value >= 0 && value <= upperLimit
|
||||||
|
|
||||||
function onSliderChange(mouseX, isDrag = false) {
|
function onSliderChange(mouseX, isDrag = false) {
|
||||||
const { left, width } = slider.getBoundingClientRect()
|
const { left, width } = slider.getBoundingClientRect()
|
||||||
|
@ -17,12 +21,29 @@
|
||||||
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
|
let percentageClick = (clickPosition / sliderWidth).toFixed(2)
|
||||||
|
|
||||||
if (percentageClick >= 0 && percentageClick <= 1) {
|
if (percentageClick >= 0 && percentageClick <= 1) {
|
||||||
let value = type === "hue" ? 360 * percentageClick : percentageClick
|
|
||||||
|
|
||||||
|
let value = type === "hue" ? 360 * percentageClick : percentageClick
|
||||||
dispatch("change", { color: value, isDrag })
|
dispatch("change", { color: value, isDrag })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleLeftKey() {
|
||||||
|
let v = value - incrementFactor
|
||||||
|
if(isWithinLimit(v)) {
|
||||||
|
value = v
|
||||||
|
dispatch("change", { color: value })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRightKey() {
|
||||||
|
let v = value + incrementFactor
|
||||||
|
if(isWithinLimit(v)) {
|
||||||
|
value = v
|
||||||
|
dispatch("change", { color: value })
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$: thumbPosition =
|
$: thumbPosition =
|
||||||
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
|
type === "hue" ? sliderWidth * (value / 360) : sliderWidth * value
|
||||||
|
|
||||||
|
@ -30,14 +51,16 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
tabindex="0"
|
||||||
bind:this={slider}
|
bind:this={slider}
|
||||||
|
use:keyevents={{37: handleLeftKey, 39: handleRightKey}}
|
||||||
bind:clientWidth={sliderWidth}
|
bind:clientWidth={sliderWidth}
|
||||||
on:click={event => onSliderChange(event.clientX)}
|
on:click={event => onSliderChange(event.clientX)}
|
||||||
class="color-format-slider"
|
class="color-format-slider"
|
||||||
class:hue={type === 'hue'}
|
class:hue={type === 'hue'}
|
||||||
class:alpha={type === 'alpha'}>
|
class:alpha={type === 'alpha'}>
|
||||||
<div
|
<div
|
||||||
use:dragable
|
use:drag
|
||||||
on:drag={e => onSliderChange(e.detail, true)}
|
on:drag={e => onSliderChange(e.detail, true)}
|
||||||
on:dragend
|
on:dragend
|
||||||
class="slider-thumb"
|
class="slider-thumb"
|
||||||
|
@ -54,6 +77,8 @@
|
||||||
margin: 10px 0px;
|
margin: 10px 0px;
|
||||||
border: 1px solid #e8e8ef;
|
border: 1px solid #e8e8ef;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
outline-color: #003cb0;
|
||||||
|
outline-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hue {
|
.hue {
|
|
@ -2,6 +2,7 @@
|
||||||
import { createEventDispatcher } from "svelte"
|
import { createEventDispatcher } from "svelte"
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import CheckedBackground from "./CheckedBackground.svelte"
|
import CheckedBackground from "./CheckedBackground.svelte"
|
||||||
|
import {keyevents} from "../actions"
|
||||||
|
|
||||||
export let hovered = false
|
export let hovered = false
|
||||||
export let color = "#fff"
|
export let color = "#fff"
|
||||||
|
@ -12,6 +13,8 @@
|
||||||
<div class="space">
|
<div class="space">
|
||||||
<CheckedBackground borderRadius="6px">
|
<CheckedBackground borderRadius="6px">
|
||||||
<div
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
use:keyevents={{"Enter": () => dispatch("click")}}
|
||||||
in:fade
|
in:fade
|
||||||
class="swatch"
|
class="swatch"
|
||||||
style={`background: ${color};`}
|
style={`background: ${color};`}
|
||||||
|
@ -38,6 +41,8 @@
|
||||||
border: 1px solid #dedada;
|
border: 1px solid #dedada;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
width: 20px;
|
width: 20px;
|
||||||
|
outline-color: #003cb0;
|
||||||
|
outline-width: thin;
|
||||||
}
|
}
|
||||||
|
|
||||||
.space {
|
.space {
|
|
@ -1,4 +1,4 @@
|
||||||
export const buildStyle = styles => {
|
export function buildStyle(styles) {
|
||||||
let str = ""
|
let str = ""
|
||||||
for (let s in styles) {
|
for (let s in styles) {
|
||||||
if (styles[s]) {
|
if (styles[s]) {
|
||||||
|
@ -12,3 +12,9 @@ export const buildStyle = styles => {
|
||||||
export const convertCamel = str => {
|
export const convertCamel = str => {
|
||||||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const debounce = (fn, milliseconds) => {
|
||||||
|
return () => {
|
||||||
|
setTimeout(fn, milliseconds)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
import Colorpreview from "./Colorpreview.svelte"
|
import Colorpreview from "./components/Colorpreview.svelte"
|
||||||
export default Colorpreview
|
export default Colorpreview
|
||||||
|
|
|
@ -144,13 +144,15 @@ export const hslaToHSVA = ([h, s, l, a = 1]) => {
|
||||||
|
|
||||||
export const hsvaToHexa = (hsva, asString = false) => {
|
export const hsvaToHexa = (hsva, asString = false) => {
|
||||||
const [r, g, b, a] = hsvaToRgba(hsva)
|
const [r, g, b, a] = hsvaToRgba(hsva)
|
||||||
|
const padSingle = hex => (hex.length === 1 ? `0${hex}` : hex)
|
||||||
|
|
||||||
const hexa = [r, g, b]
|
let hexa = [r, g, b].map(v => {
|
||||||
.map(v => {
|
|
||||||
let hex = Math.round(v).toString(16)
|
let hex = Math.round(v).toString(16)
|
||||||
return hex.length === 1 ? `0${hex}` : hex
|
return padSingle(hex)
|
||||||
})
|
})
|
||||||
.concat(Math.round(a * 255).toString(16))
|
|
||||||
|
let alpha = padSingle(Math.round(a * 255).toString(16))
|
||||||
|
hexa = [...hexa, alpha]
|
||||||
return asString ? `#${hexa.join("")}` : hexa
|
return asString ? `#${hexa.join("")}` : hexa
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
|
|
||||||
const changeScreen = screen => {
|
const changeScreen = screen => {
|
||||||
store.setCurrentScreen(screen.props._instanceName)
|
store.setCurrentScreen(screen.props._instanceName)
|
||||||
$goto(`./:page/${screen.title}`)
|
$goto(`./:page/${screen.props._instanceName}`)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
<script>
|
<script>
|
||||||
|
import {onMount} from "svelte"
|
||||||
import PropertyGroup from "./PropertyGroup.svelte"
|
import PropertyGroup from "./PropertyGroup.svelte"
|
||||||
import FlatButtonGroup from "./FlatButtonGroup.svelte"
|
import FlatButtonGroup from "./FlatButtonGroup.svelte"
|
||||||
|
|
||||||
|
|
||||||
export let panelDefinition = {}
|
export let panelDefinition = {}
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let componentDefinition = {}
|
export let componentDefinition = {}
|
||||||
export let onStyleChanged = () => {}
|
export let onStyleChanged = () => {}
|
||||||
|
|
||||||
let selectedCategory = "normal"
|
let selectedCategory = "normal"
|
||||||
|
let propGroup = null
|
||||||
|
let currentGroup
|
||||||
|
|
||||||
const getProperties = name => panelDefinition[name]
|
const getProperties = name => panelDefinition[name]
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
// if(propGroup) {
|
||||||
|
// propGroup.addEventListener("scroll", function(e){
|
||||||
|
// console.log("I SCROLLED", e.target.scrollTop)
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
})
|
||||||
|
|
||||||
function onChange(category) {
|
function onChange(category) {
|
||||||
selectedCategory = category
|
selectedCategory = category
|
||||||
}
|
}
|
||||||
|
@ -22,6 +34,7 @@
|
||||||
]
|
]
|
||||||
|
|
||||||
$: propertyGroupNames = Object.keys(panelDefinition)
|
$: propertyGroupNames = Object.keys(panelDefinition)
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="design-view-container">
|
<div class="design-view-container">
|
||||||
|
@ -31,7 +44,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="positioned-wrapper">
|
<div class="positioned-wrapper">
|
||||||
<div class="design-view-property-groups">
|
<div bind:this={propGroup} class="design-view-property-groups">
|
||||||
{#if propertyGroupNames.length > 0}
|
{#if propertyGroupNames.length > 0}
|
||||||
{#each propertyGroupNames as groupName}
|
{#each propertyGroupNames as groupName}
|
||||||
<PropertyGroup
|
<PropertyGroup
|
||||||
|
@ -40,7 +53,9 @@
|
||||||
styleCategory={selectedCategory}
|
styleCategory={selectedCategory}
|
||||||
{onStyleChanged}
|
{onStyleChanged}
|
||||||
{componentDefinition}
|
{componentDefinition}
|
||||||
{componentInstance} />
|
{componentInstance}
|
||||||
|
open={currentGroup === groupName}
|
||||||
|
on:open={() => currentGroup = groupName} />
|
||||||
{/each}
|
{/each}
|
||||||
{:else}
|
{:else}
|
||||||
<div class="no-design">
|
<div class="no-design">
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { onMount, beforeUpdate } from "svelte"
|
import { onMount, beforeUpdate, afterUpdate } from "svelte"
|
||||||
|
import Portal from "svelte-portal"
|
||||||
import { buildStyle } from "../../helpers.js"
|
import { buildStyle } from "../../helpers.js"
|
||||||
export let options = []
|
export let options = []
|
||||||
export let value = ""
|
export let value = ""
|
||||||
|
@ -12,52 +13,61 @@
|
||||||
let selectMenu
|
let selectMenu
|
||||||
let icon
|
let icon
|
||||||
|
|
||||||
let selectYPosition = null
|
let selectAnchor = null;
|
||||||
let availableSpace = 0
|
let dimensions = {top: 0, bottom: 0, left: 0}
|
||||||
|
|
||||||
let positionSide = "top"
|
let positionSide = "top"
|
||||||
let maxHeight = null
|
let maxHeight = 0
|
||||||
let menuHeight
|
let scrollTop = 0;
|
||||||
|
let containerEl = null;
|
||||||
|
|
||||||
const handleStyleBind = value =>
|
const handleStyleBind = value =>
|
||||||
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
|
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (select) {
|
if (select) {
|
||||||
select.addEventListener("keydown", addSelectKeyEvents)
|
select.addEventListener("keydown", handleEnter)
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
select.removeEventListener("keydown", addSelectKeyEvents)
|
select.removeEventListener("keydown", handleEnter)
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function checkPosition() {
|
function handleEscape(e) {
|
||||||
const { bottom, top: spaceAbove } = select.getBoundingClientRect()
|
if(open && e.key === "Escape") {
|
||||||
|
toggleSelect(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDimensions() {
|
||||||
|
const { bottom, top: spaceAbove, left } = selectAnchor.getBoundingClientRect()
|
||||||
const spaceBelow = window.innerHeight - bottom
|
const spaceBelow = window.innerHeight - bottom
|
||||||
|
|
||||||
|
let y;
|
||||||
|
|
||||||
if (spaceAbove > spaceBelow) {
|
if (spaceAbove > spaceBelow) {
|
||||||
positionSide = "bottom"
|
positionSide = "bottom"
|
||||||
maxHeight = `${spaceAbove.toFixed(0) - 20}px`
|
maxHeight = spaceAbove - 20
|
||||||
|
y = (window.innerHeight - spaceAbove)
|
||||||
} else {
|
} else {
|
||||||
positionSide = "top"
|
positionSide = "top"
|
||||||
maxHeight = `${spaceBelow.toFixed(0) - 20}px`
|
y = bottom
|
||||||
}
|
maxHeight = spaceBelow - 20
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSelectKeyEvents(e) {
|
dimensions = {[positionSide]: y, left}
|
||||||
if (e.key === "Enter") {
|
}
|
||||||
if (!open) {
|
|
||||||
|
function handleEnter(e) {
|
||||||
|
if (!open && e.key === "Enter") {
|
||||||
toggleSelect(true)
|
toggleSelect(true)
|
||||||
}
|
}
|
||||||
} else if (e.key === "Escape") {
|
|
||||||
if (open) {
|
|
||||||
toggleSelect(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleSelect(isOpen) {
|
function toggleSelect(isOpen) {
|
||||||
checkPosition()
|
getDimensions()
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
icon.style.transform = "rotate(180deg)"
|
icon.style.transform = "rotate(180deg)"
|
||||||
} else {
|
} else {
|
||||||
|
@ -66,15 +76,17 @@
|
||||||
open = isOpen
|
open = isOpen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function handleClick(val) {
|
function handleClick(val) {
|
||||||
value = val
|
value = val
|
||||||
onChange(value)
|
onChange(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
$: menuStyle = buildStyle({
|
$: menuStyle = buildStyle({
|
||||||
"max-height": maxHeight,
|
"max-height": `${maxHeight.toFixed(0)}px`,
|
||||||
"transform-origin": `center ${positionSide}`,
|
"transform-origin": `center ${positionSide}`,
|
||||||
[positionSide]: "32px",
|
[positionSide]: `${dimensions[positionSide]}px`,
|
||||||
|
"left": `${dimensions.left.toFixed(0)}px`,
|
||||||
})
|
})
|
||||||
|
|
||||||
$: isOptionsObject = options.every(o => typeof o === "object")
|
$: isOptionsObject = options.every(o => typeof o === "object")
|
||||||
|
@ -83,6 +95,10 @@
|
||||||
? options.find(o => o.value === value)
|
? options.find(o => o.value === value)
|
||||||
: {}
|
: {}
|
||||||
|
|
||||||
|
$: if(open && selectMenu) {
|
||||||
|
selectMenu.focus()
|
||||||
|
}
|
||||||
|
|
||||||
$: displayLabel =
|
$: displayLabel =
|
||||||
selectedOption && selectedOption.label ? selectedOption.label : value || ""
|
selectedOption && selectedOption.label ? selectedOption.label : value || ""
|
||||||
</script>
|
</script>
|
||||||
|
@ -92,15 +108,19 @@
|
||||||
bind:this={select}
|
bind:this={select}
|
||||||
class="bb-select-container"
|
class="bb-select-container"
|
||||||
on:click={() => toggleSelect(!open)}>
|
on:click={() => toggleSelect(!open)}>
|
||||||
<div class="bb-select-anchor selected">
|
<div bind:this={selectAnchor} class="bb-select-anchor selected">
|
||||||
<span>{displayLabel}</span>
|
<span>{displayLabel}</span>
|
||||||
<i bind:this={icon} class="ri-arrow-down-s-fill" />
|
<i bind:this={icon} class="ri-arrow-down-s-fill" />
|
||||||
</div>
|
</div>
|
||||||
|
{#if open}
|
||||||
|
<Portal>
|
||||||
<div
|
<div
|
||||||
|
tabindex="0"
|
||||||
|
class:open
|
||||||
bind:this={selectMenu}
|
bind:this={selectMenu}
|
||||||
style={menuStyle}
|
style={menuStyle}
|
||||||
class="bb-select-menu"
|
on:keydown={handleEscape}
|
||||||
class:open>
|
class="bb-select-menu">
|
||||||
<ul>
|
<ul>
|
||||||
{#if isOptionsObject}
|
{#if isOptionsObject}
|
||||||
{#each options as { value: v, label }}
|
{#each options as { value: v, label }}
|
||||||
|
@ -123,14 +143,14 @@
|
||||||
{/if}
|
{/if}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{#if open}
|
|
||||||
<div on:click|self={() => toggleSelect(false)} class="overlay" />
|
<div on:click|self={() => toggleSelect(false)} class="overlay" />
|
||||||
{/if}
|
</Portal>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.overlay {
|
.overlay {
|
||||||
position: absolute;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
|
@ -139,7 +159,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.bb-select-container {
|
.bb-select-container {
|
||||||
position: relative;
|
|
||||||
outline: none;
|
outline: none;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
|
@ -180,6 +199,7 @@
|
||||||
.bb-select-menu {
|
.bb-select-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
outline: none;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
|
@ -190,9 +210,6 @@
|
||||||
height: fit-content !important;
|
height: fit-content !important;
|
||||||
border-bottom-left-radius: 2px;
|
border-bottom-left-radius: 2px;
|
||||||
border-bottom-right-radius: 2px;
|
border-bottom-right-radius: 2px;
|
||||||
border-right: 1px solid var(--grey-4);
|
|
||||||
border-left: 1px solid var(--grey-4);
|
|
||||||
border-bottom: 1px solid var(--grey-4);
|
|
||||||
background-color: var(--grey-2);
|
background-color: var(--grey-2);
|
||||||
transform: scale(0);
|
transform: scale(0);
|
||||||
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
|
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
|
||||||
|
|
|
@ -8,11 +8,12 @@
|
||||||
export let properties = []
|
export let properties = []
|
||||||
export let componentInstance = {}
|
export let componentInstance = {}
|
||||||
export let onStyleChanged = () => {}
|
export let onStyleChanged = () => {}
|
||||||
|
export let open = false
|
||||||
|
|
||||||
$: style = componentInstance["_styles"][styleCategory] || {}
|
$: style = componentInstance["_styles"][styleCategory] || {}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<DetailSummary {name}>
|
<DetailSummary {name} on:open show={open} >
|
||||||
{#each properties as props}
|
{#each properties as props}
|
||||||
<PropertyControl
|
<PropertyControl
|
||||||
label={props.label}
|
label={props.label}
|
||||||
|
|
|
@ -123,7 +123,7 @@ export const margin = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: "Bottom",
|
label: "Bottom",
|
||||||
key: "padding-bottom",
|
key: "margin-bottom",
|
||||||
control: OptionSelect,
|
control: OptionSelect,
|
||||||
options: [
|
options: [
|
||||||
{ label: "None", value: "0px" },
|
{ label: "None", value: "0px" },
|
||||||
|
@ -352,7 +352,6 @@ export const typography = [
|
||||||
"Inter",
|
"Inter",
|
||||||
"Lucida Sans Unicode",
|
"Lucida Sans Unicode",
|
||||||
"Open Sans",
|
"Open Sans",
|
||||||
"Playfair",
|
|
||||||
"Roboto",
|
"Roboto",
|
||||||
"Roboto Mono",
|
"Roboto Mono",
|
||||||
"Times New Roman",
|
"Times New Roman",
|
||||||
|
|
|
@ -321,17 +321,44 @@ export default {
|
||||||
name: "Form",
|
name: "Form",
|
||||||
description: "A component that generates a form from your data.",
|
description: "A component that generates a form from your data.",
|
||||||
icon: "ri-file-edit-fill",
|
icon: "ri-file-edit-fill",
|
||||||
|
commonProps: {},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
_component: "@budibase/standard-components/dataform",
|
||||||
|
name: "Form Basic",
|
||||||
|
icon: "ri-file-edit-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
settings: [{ label: "Model", key: "model", control: ModelSelect }],
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Model",
|
||||||
|
key: "model",
|
||||||
|
control: ModelSelect,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
_component: "@budibase/standard-components/dataform",
|
|
||||||
template: {
|
template: {
|
||||||
component: "@budibase/materialdesign-components/Form",
|
component: "@budibase/materialdesign-components/Form",
|
||||||
description: "Form for saving a record",
|
description: "Form for saving a record",
|
||||||
name: "@budibase/materialdesign-components/recordForm",
|
name: "@budibase/materialdesign-components/recordForm",
|
||||||
},
|
},
|
||||||
children: [],
|
},
|
||||||
|
{
|
||||||
|
_component: "@budibase/standard-components/dataformwide",
|
||||||
|
name: "Form Wide",
|
||||||
|
icon: "ri-file-edit-fill",
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [
|
||||||
|
{
|
||||||
|
label: "Model",
|
||||||
|
key: "model",
|
||||||
|
control: ModelSelect,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Chart",
|
name: "Chart",
|
||||||
|
@ -379,7 +406,7 @@ export default {
|
||||||
{
|
{
|
||||||
name: "List",
|
name: "List",
|
||||||
_component: "@budibase/standard-components/list",
|
_component: "@budibase/standard-components/list",
|
||||||
description: "Shiny list",
|
description: "Renders all children once per record, of a given model",
|
||||||
icon: "ri-file-list-fill",
|
icon: "ri-file-list-fill",
|
||||||
properties: {
|
properties: {
|
||||||
design: { ...all },
|
design: { ...all },
|
||||||
|
@ -387,6 +414,18 @@ export default {
|
||||||
},
|
},
|
||||||
children: [],
|
children: [],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Record Detail",
|
||||||
|
_component: "@budibase/standard-components/recorddetail",
|
||||||
|
description:
|
||||||
|
"Loads a record, using an id from the URL, which can be used with {{ context }}, in children",
|
||||||
|
icon: "ri-profile-line",
|
||||||
|
properties: {
|
||||||
|
design: { ...all },
|
||||||
|
settings: [{ label: "Model", key: "model", control: ModelSelect }],
|
||||||
|
},
|
||||||
|
children: [],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "Map",
|
name: "Map",
|
||||||
_component: "@budibase/standard-components/datamap",
|
_component: "@budibase/standard-components/datamap",
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
import { notifier } from "builderStore/store/notifications"
|
import { notifier } from "builderStore/store/notifications"
|
||||||
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
|
||||||
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
@ -90,26 +91,21 @@
|
||||||
{testResult}
|
{testResult}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
<button class="workflow-button hoverable" on:click={testWorkflow}>
|
<Button secondary wide on:click={testWorkflow}>Test Workflow</Button>
|
||||||
Test
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if selectedTab === 'SETUP'}
|
{#if selectedTab === 'SETUP'}
|
||||||
{#if workflowBlock}
|
{#if workflowBlock}
|
||||||
<WorkflowBlockSetup {workflowBlock} />
|
<WorkflowBlockSetup {workflowBlock} />
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button
|
<Button
|
||||||
|
green
|
||||||
|
wide
|
||||||
data-cy="save-workflow-setup"
|
data-cy="save-workflow-setup"
|
||||||
class="workflow-button hoverable"
|
|
||||||
on:click={saveWorkflow}>
|
on:click={saveWorkflow}>
|
||||||
Save Workflow
|
Save Workflow
|
||||||
</button>
|
</Button>
|
||||||
<button
|
<Button red wide on:click={deleteWorkflowBlock}>Delete Block</Button>
|
||||||
class="delete-workflow-button hoverable"
|
|
||||||
on:click={deleteWorkflowBlock}>
|
|
||||||
Delete Block
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
{:else if $workflowStore.currentWorkflow}
|
{:else if $workflowStore.currentWorkflow}
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
|
@ -141,15 +137,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
<button
|
<Button
|
||||||
|
green
|
||||||
|
wide
|
||||||
data-cy="save-workflow-setup"
|
data-cy="save-workflow-setup"
|
||||||
class="workflow-button hoverable"
|
|
||||||
on:click={saveWorkflow}>
|
on:click={saveWorkflow}>
|
||||||
Save Workflow
|
Save Workflow
|
||||||
</button>
|
</Button>
|
||||||
<button class="delete-workflow-button" on:click={deleteWorkflow}>
|
<Button red wide on:click={deleteWorkflow}>Delete Workflow</Button>
|
||||||
Delete Workflow
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -175,11 +170,12 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
header {
|
header {
|
||||||
font-size: 20px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
font-family: inter;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 18px;
|
margin-bottom: 20px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,13 +186,12 @@
|
||||||
.block-label {
|
.block-label {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: var(--ink);
|
color: var(--grey-7);
|
||||||
margin: 0px 0px 16px 0px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-item {
|
.config-item {
|
||||||
margin: 0px 0px 4px 0px;
|
margin-bottom: 20px;
|
||||||
padding: 12px 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase_input {
|
.budibase_input {
|
||||||
|
@ -214,6 +209,7 @@
|
||||||
header > span {
|
header > span {
|
||||||
color: var(--grey-5);
|
color: var(--grey-5);
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form {
|
.form {
|
||||||
|
@ -228,52 +224,10 @@
|
||||||
|
|
||||||
.buttons {
|
.buttons {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 10px;
|
bottom: 20px;
|
||||||
}
|
display: grid;
|
||||||
|
|
||||||
.delete-workflow-button {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--red);
|
|
||||||
border-radius: 3px;
|
|
||||||
width: 260px;
|
|
||||||
padding: 8px 16px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: var(--white);
|
|
||||||
color: var(--red);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 2ms;
|
|
||||||
align-self: flex-end;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.delete-workflow-button:hover {
|
|
||||||
background: var(--red);
|
|
||||||
border: 1px solid var(--red);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.workflow-button {
|
|
||||||
cursor: pointer;
|
|
||||||
border: 1px solid var(--grey-4);
|
|
||||||
border-radius: 3px;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 16px;
|
gap: 12px;
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
background: white;
|
|
||||||
color: var(--ink);
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 2ms;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.workflow-button:hover {
|
|
||||||
background: var(--grey-1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.access-level {
|
.access-level {
|
||||||
|
@ -301,7 +255,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.passed {
|
.passed {
|
||||||
background: #84c991;
|
background: var(--green);
|
||||||
}
|
}
|
||||||
|
|
||||||
.failed {
|
.failed {
|
||||||
|
|
|
@ -13,15 +13,12 @@
|
||||||
: []
|
: []
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label class="uk-form-label">{workflowBlock.type}: {workflowBlock.name}</label>
|
<label class="selected-label">{workflowBlock.type}: {workflowBlock.name}</label>
|
||||||
{#each workflowParams as [parameter, type]}
|
{#each workflowParams as [parameter, type]}
|
||||||
<div class="block-field">
|
<div class="block-field">
|
||||||
<label class="uk-form-label">{parameter}</label>
|
<label class="label">{parameter}</label>
|
||||||
<div class="uk-form-controls">
|
|
||||||
{#if Array.isArray(type)}
|
{#if Array.isArray(type)}
|
||||||
<select
|
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
|
||||||
class="budibase_input"
|
|
||||||
bind:value={workflowBlock.args[parameter]}>
|
|
||||||
{#each type as option}
|
{#each type as option}
|
||||||
<option value={option}>{option}</option>
|
<option value={option}>{option}</option>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -29,26 +26,24 @@
|
||||||
{:else if type === 'component'}
|
{:else if type === 'component'}
|
||||||
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
|
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'accessLevel'}
|
{:else if type === 'accessLevel'}
|
||||||
<select
|
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
|
||||||
class="budibase__input"
|
|
||||||
bind:value={workflowBlock.args[parameter]}>
|
|
||||||
<option value="ADMIN">Admin</option>
|
<option value="ADMIN">Admin</option>
|
||||||
<option value="POWER_USER">Power User</option>
|
<option value="POWER_USER">Power User</option>
|
||||||
</select>
|
</select>
|
||||||
{:else if type === 'password'}
|
{:else if type === 'password'}
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
class="budibase__input"
|
class="budibase_input"
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'number'}
|
{:else if type === 'number'}
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
class="budibase__input"
|
class="budibase_input"
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'longText'}
|
{:else if type === 'longText'}
|
||||||
<textarea
|
<textarea
|
||||||
type="text"
|
type="text"
|
||||||
class="budibase__input"
|
class="budibase_input"
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
bind:value={workflowBlock.args[parameter]} />
|
||||||
{:else if type === 'model'}
|
{:else if type === 'model'}
|
||||||
<ModelSelector bind:value={workflowBlock.args[parameter]} />
|
<ModelSelector bind:value={workflowBlock.args[parameter]} />
|
||||||
|
@ -57,41 +52,45 @@
|
||||||
{:else if type === 'string'}
|
{:else if type === 'string'}
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="budibase__input"
|
class="budibase_input"
|
||||||
bind:value={workflowBlock.args[parameter]} />
|
bind:value={workflowBlock.args[parameter]} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
{/each}
|
{/each}
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.block-field {
|
.block-field {
|
||||||
border-radius: 3px;
|
display: grid;
|
||||||
background: var(--grey-1);
|
|
||||||
padding: 12px;
|
|
||||||
margin: 0px 0px 4px 0px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.budibase_input {
|
.budibase_input {
|
||||||
height: 36px;
|
height: 36px;
|
||||||
width: 220px;
|
border-radius: 5px;
|
||||||
border-radius: 3px;
|
background-color: var(--grey-2);
|
||||||
border: 1px solid var(--grey-4);
|
border: 1px solid var(--grey-2);
|
||||||
text-align: left;
|
text-align: left;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
text-transform: capitalize;
|
text-transform: capitalize;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-label {
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
min-height: 150px;
|
min-height: 150px;
|
||||||
font-family: inherit;
|
font-family: inherit;
|
||||||
padding: 5px;
|
padding: 12px;
|
||||||
|
margin-top: 8px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -81,7 +81,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.play-button.highlighted {
|
.play-button.highlighted {
|
||||||
background: var(--primary);
|
background: var(--purple);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stop-button.highlighted {
|
.stop-button.highlighted {
|
||||||
|
|
|
@ -67,6 +67,10 @@
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
div:hover {
|
div:hover {
|
||||||
transform: scale(1.05);
|
transform: scale(1.05);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
import { onMount, getContext } from "svelte"
|
import { onMount, getContext } from "svelte"
|
||||||
import { backendUiStore, workflowStore } from "builderStore"
|
import { backendUiStore, workflowStore } from "builderStore"
|
||||||
import CreateWorkflowModal from "./CreateWorkflowModal.svelte"
|
import CreateWorkflowModal from "./CreateWorkflowModal.svelte"
|
||||||
|
import { Button } from "@budibase/bbui"
|
||||||
|
|
||||||
const { open, close } = getContext("simple-modal")
|
const { open, close } = getContext("simple-modal")
|
||||||
|
|
||||||
|
@ -27,10 +28,7 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<button class="new-workflow-button hoverable" on:click={newWorkflow}>
|
<Button purple wide on:click{newWorkflow}>Create New Workflow</Button>
|
||||||
<i class="icon ri-add-circle-fill" />
|
|
||||||
Create New Workflow
|
|
||||||
</button>
|
|
||||||
<ul>
|
<ul>
|
||||||
{#each $workflowStore.workflows as workflow}
|
{#each $workflowStore.workflows as workflow}
|
||||||
<li
|
<li
|
||||||
|
@ -74,10 +72,10 @@
|
||||||
|
|
||||||
.workflow-item {
|
.workflow-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
border-radius: 3px;
|
border-radius: 5px;
|
||||||
padding-left: 12px;
|
padding-left: 12px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
<script>
|
<script>
|
||||||
import Modal from "svelte-simple-modal"
|
import Modal from "svelte-simple-modal"
|
||||||
import { store, workflowStore } from "builderStore"
|
import { store, workflowStore } from "builderStore"
|
||||||
|
import SettingsLink from "components/settings/Link.svelte"
|
||||||
import { get } from "builderStore/api"
|
import { get } from "builderStore/api"
|
||||||
|
|
||||||
import { fade } from "svelte/transition"
|
import { fade } from "svelte/transition"
|
||||||
import { isActive, goto, layout } from "@sveltech/routify"
|
import { isActive, goto, layout, url } from "@sveltech/routify"
|
||||||
|
|
||||||
import { SettingsIcon, PreviewIcon } from "components/common/Icons/"
|
import { SettingsIcon, PreviewIcon } from "components/common/Icons/"
|
||||||
import IconButton from "components/common/IconButton.svelte"
|
import IconButton from "components/common/IconButton.svelte"
|
||||||
|
@ -26,6 +27,22 @@
|
||||||
throw new Error(pkg)
|
throw new Error(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handles navigation between frontend, backend, workflow.
|
||||||
|
// this remembers your last place on each of the sections
|
||||||
|
// e.g. if one of your screens is selected on front end, then
|
||||||
|
// you browse to backend, when you click fronend, you will be
|
||||||
|
// brought back to the same screen
|
||||||
|
const topItemNavigate = path => () => {
|
||||||
|
const activeTopNav = $layout.children.find(c => $isActive(c.path))
|
||||||
|
if (!activeTopNav) return
|
||||||
|
store.update(state => {
|
||||||
|
if (!state.previousTopNavPath) state.previousTopNavPath = {}
|
||||||
|
state.previousTopNavPath[activeTopNav.path] = window.location.pathname.replace("/_builder", "")
|
||||||
|
$goto(state.previousTopNavPath[path] || path)
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal>
|
<Modal>
|
||||||
|
@ -45,7 +62,7 @@
|
||||||
<span
|
<span
|
||||||
class:active={$isActive(path)}
|
class:active={$isActive(path)}
|
||||||
class="topnavitem"
|
class="topnavitem"
|
||||||
on:click={() => $goto(path)}>
|
on:click={topItemNavigate(path)}>
|
||||||
{title}
|
{title}
|
||||||
</span>
|
</span>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -54,12 +71,7 @@
|
||||||
hoverColor="var(--secondary75)"/> -->
|
hoverColor="var(--secondary75)"/> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="toprightnav">
|
<div class="toprightnav">
|
||||||
<span
|
<SettingsLink />
|
||||||
class:active={$isActive(`/settings`)}
|
|
||||||
class="topnavitemright"
|
|
||||||
on:click={() => $goto(`/settings`)}>
|
|
||||||
<SettingsIcon />
|
|
||||||
</span>
|
|
||||||
<span
|
<span
|
||||||
class:active={false}
|
class:active={false}
|
||||||
class="topnavitemright"
|
class="topnavitemright"
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
<slot />
|
|
|
@ -1,36 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
$: instances = $store.appInstances
|
|
||||||
|
|
||||||
async function selectDatabase(database) {
|
|
||||||
backendUiStore.actions.database.select(database)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if ($store.appInstances.length > 0) {
|
|
||||||
await selectDatabase($store.appInstances[0])
|
|
||||||
$goto(`./${$backendUiStore.selectedDatabase._id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="root">
|
|
||||||
<div class="node-view">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.root {
|
|
||||||
height: 100%;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.node-view {
|
|
||||||
overflow-y: auto;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -1,20 +0,0 @@
|
||||||
<script>
|
|
||||||
import { store, backendUiStore } from "builderStore"
|
|
||||||
import { goto } from "@sveltech/routify"
|
|
||||||
import { onMount } from "svelte"
|
|
||||||
|
|
||||||
$: instances = $store.appInstances
|
|
||||||
|
|
||||||
async function selectDatabase(database) {
|
|
||||||
backendUiStore.actions.database.select(database)
|
|
||||||
}
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
if ($store.appInstances.length > 0) {
|
|
||||||
await selectDatabase($store.appInstances[0])
|
|
||||||
$goto(`../${$backendUiStore.selectedDatabase._id}`)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
Please select a database
|
|
|
@ -1,6 +1,6 @@
|
||||||
<script>
|
<script>
|
||||||
import { goto } from "@sveltech/routify"
|
import { goto } from "@sveltech/routify"
|
||||||
$goto("../database")
|
$goto("../model")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<!-- routify:options index=false -->
|
<!-- routify:options index=false -->
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<script>
|
||||||
|
import { params } from "@sveltech/routify"
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
|
||||||
|
if ($params.selectedModel) {
|
||||||
|
const model = $backendUiStore.models.find(m => m._id === $params.selectedModel)
|
||||||
|
if (model) {
|
||||||
|
backendUiStore.actions.models.select(model)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<slot />
|
|
@ -3,7 +3,7 @@
|
||||||
import { Button } from "@budibase/bbui"
|
import { Button } from "@budibase/bbui"
|
||||||
import EmptyModel from "components/nav/ModelNavigator/EmptyModel.svelte"
|
import EmptyModel from "components/nav/ModelNavigator/EmptyModel.svelte"
|
||||||
import ModelDataTable from "components/database/ModelDataTable"
|
import ModelDataTable from "components/database/ModelDataTable"
|
||||||
import { store, backendUiStore } from "builderStore"
|
import { backendUiStore } from "builderStore"
|
||||||
import ActionButton from "components/common/ActionButton.svelte"
|
import ActionButton from "components/common/ActionButton.svelte"
|
||||||
import * as api from "components/database/ModelDataTable/api"
|
import * as api from "components/database/ModelDataTable/api"
|
||||||
import { CreateEditRecordModal } from "components/database/ModelDataTable/modals"
|
import { CreateEditRecordModal } from "components/database/ModelDataTable/modals"
|
|
@ -0,0 +1,35 @@
|
||||||
|
<script>
|
||||||
|
import { backendUiStore } from "builderStore"
|
||||||
|
import { goto, leftover } from "@sveltech/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
async function selectModel(model) {
|
||||||
|
backendUiStore.actions.models.select(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// navigate to first model in list, if not already selected
|
||||||
|
// and this is the final url (i.e. no selectedModel)
|
||||||
|
if (!$leftover && $backendUiStore.models.length > 0 && (!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id)) {
|
||||||
|
$goto(`./${$backendUiStore.models[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="root">
|
||||||
|
<div class="node-view">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root {
|
||||||
|
height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.node-view {
|
||||||
|
overflow-y: auto;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,24 @@
|
||||||
|
<script>
|
||||||
|
import { store, backendUiStore } from "builderStore"
|
||||||
|
import { goto, leftover } from "@sveltech/routify"
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
async function selectModel(model) {
|
||||||
|
backendUiStore.actions.models.select(model)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// navigate to first model in list, if not already selected
|
||||||
|
// and this is the final url (i.e. no selectedModel)
|
||||||
|
if (!$leftover && $backendUiStore.models.length > 0 && (!$backendUiStore.selectedModel || !$backendUiStore.selectedModel._id)) {
|
||||||
|
// this file routes as .../models/index, so, go up one.
|
||||||
|
$goto(`../${$backendUiStore.models[0]._id}`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $backendUiStore.models.length === 0}
|
||||||
|
Please create a model
|
||||||
|
{:else}
|
||||||
|
Please select a model
|
||||||
|
{/if}
|
|
@ -14,7 +14,7 @@
|
||||||
if ($leftover) {
|
if ($leftover) {
|
||||||
// Get the correct screen children.
|
// Get the correct screen children.
|
||||||
const screenChildren = $store.pages[$params.page]._screens.find(
|
const screenChildren = $store.pages[$params.page]._screens.find(
|
||||||
screen => screen.name === $params.screen
|
screen => screen.props._instanceName === $params.screen
|
||||||
).props._children
|
).props._children
|
||||||
findComponent(componentIds, screenChildren)
|
findComponent(componentIds, screenChildren)
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,6 @@
|
||||||
padding: 0;
|
padding: 0;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
z-index: 5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-pane {
|
.preview-pane {
|
||||||
|
|
|
@ -56,13 +56,13 @@ export const screenRouter = ({ screens, onScreenSelected, window }) => {
|
||||||
|
|
||||||
const screenIndex = current !== -1 ? current : fallback
|
const screenIndex = current !== -1 ? current : fallback
|
||||||
|
|
||||||
onScreenSelected(screens[screenIndex], _url)
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
!url.state && history.pushState(_url, null, _url)
|
!url.state && history.pushState(_url, null, _url)
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// ignoring an exception here as the builder runs an iframe, which does not like this
|
// ignoring an exception here as the builder runs an iframe, which does not like this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onScreenSelected(screens[screenIndex], _url)
|
||||||
}
|
}
|
||||||
|
|
||||||
function click(e) {
|
function click(e) {
|
||||||
|
|
|
@ -59,7 +59,7 @@ const _setup = ({ handlerTypes, getCurrentState, bb, store }) => node => {
|
||||||
const propValue = props[propName]
|
const propValue = props[propName]
|
||||||
|
|
||||||
// A little bit of a hack - won't bind if the string doesn't start with {{
|
// A little bit of a hack - won't bind if the string doesn't start with {{
|
||||||
const isBound = typeof propValue === "string" && propValue.startsWith("{{")
|
const isBound = typeof propValue === "string" && propValue.includes("{{")
|
||||||
|
|
||||||
if (isBound) {
|
if (isBound) {
|
||||||
initialProps[propName] = mustache.render(propValue, {
|
initialProps[propName] = mustache.render(propValue, {
|
||||||
|
|
|
@ -63,7 +63,19 @@ exports.create = async function(ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.update = async function() {}
|
exports.update = async function(ctx) {
|
||||||
|
const db = new CouchDB(ctx.user.instanceId)
|
||||||
|
const user = ctx.request.body
|
||||||
|
const dbUser = db.get(ctx.request.body._id)
|
||||||
|
const newData = { ...dbUser, ...user }
|
||||||
|
|
||||||
|
const response = await db.put(newData)
|
||||||
|
user._rev = response.rev
|
||||||
|
|
||||||
|
ctx.status = 200
|
||||||
|
ctx.message = `User ${ctx.request.body.username} updated successfully.`
|
||||||
|
ctx.body = response
|
||||||
|
}
|
||||||
|
|
||||||
exports.destroy = async function(ctx) {
|
exports.destroy = async function(ctx) {
|
||||||
const database = new CouchDB(ctx.user.instanceId)
|
const database = new CouchDB(ctx.user.instanceId)
|
||||||
|
|
|
@ -8,6 +8,7 @@ const router = Router()
|
||||||
router
|
router
|
||||||
.get("/api/users", authorized(LIST_USERS), controller.fetch)
|
.get("/api/users", authorized(LIST_USERS), controller.fetch)
|
||||||
.get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find)
|
.get("/api/users/:username", authorized(USER_MANAGEMENT), controller.find)
|
||||||
|
.put("/api/users/", authorized(USER_MANAGEMENT), controller.update)
|
||||||
.post("/api/users", authorized(USER_MANAGEMENT), controller.create)
|
.post("/api/users", authorized(USER_MANAGEMENT), controller.create)
|
||||||
.delete(
|
.delete(
|
||||||
"/api/users/:username",
|
"/api/users/:username",
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"stylesheets": [],
|
"stylesheets": [],
|
||||||
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
|
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
|
||||||
"props" : {
|
"props" : {
|
||||||
|
"_id": "private-master-root",
|
||||||
"_component": "@budibase/standard-components/container",
|
"_component": "@budibase/standard-components/container",
|
||||||
"_children": [
|
"_children": [
|
||||||
{
|
{
|
||||||
|
@ -19,7 +20,6 @@
|
||||||
"_children": []
|
"_children": []
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_id": 0,
|
|
||||||
"type": "div",
|
"type": "div",
|
||||||
"_styles": {
|
"_styles": {
|
||||||
"active": {},
|
"active": {},
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
"favicon": "./_shared/favicon.png",
|
"favicon": "./_shared/favicon.png",
|
||||||
"stylesheets": [],
|
"stylesheets": [],
|
||||||
"props": {
|
"props": {
|
||||||
|
"_id": "public-master-root",
|
||||||
"_component": "@budibase/standard-components/container",
|
"_component": "@budibase/standard-components/container",
|
||||||
"_children": [
|
"_children": [
|
||||||
{
|
{
|
||||||
|
@ -31,7 +32,6 @@
|
||||||
"logo": ""
|
"logo": ""
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"_id": 1,
|
|
||||||
"type": "div",
|
"type": "div",
|
||||||
"_styles": {
|
"_styles": {
|
||||||
"layout": {},
|
"layout": {},
|
||||||
|
|
|
@ -17,6 +17,9 @@
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 0px;
|
||||||
}
|
}
|
||||||
|
*, *:before, *:after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
{{ each(options.stylesheets) }}
|
{{ each(options.stylesheets) }}
|
||||||
|
@ -31,6 +34,9 @@
|
||||||
<link rel='stylesheet' href='/assets{{ pageStyle }}'>
|
<link rel='stylesheet' href='/assets{{ pageStyle }}'>
|
||||||
{{ /if }}
|
{{ /if }}
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css">
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Roboto+Mono">
|
||||||
|
|
||||||
<script src='/assets/clientFrontendDefinition.js'></script>
|
<script src='/assets/clientFrontendDefinition.js'></script>
|
||||||
<script src='/assets/budibase-client.js'></script>
|
<script src='/assets/budibase-client.js'></script>
|
||||||
|
|
||||||
|
|
|
@ -209,6 +209,13 @@
|
||||||
"model": "models"
|
"model": "models"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"dataformwide": {
|
||||||
|
"description": "an HTML table that fetches data from a model or view and displays it.",
|
||||||
|
"data": true,
|
||||||
|
"props": {
|
||||||
|
"model": "models"
|
||||||
|
}
|
||||||
|
},
|
||||||
"datalist": {
|
"datalist": {
|
||||||
"description": "A configurable data list that attaches to your backend models.",
|
"description": "A configurable data list that attaches to your backend models.",
|
||||||
"data": true,
|
"data": true,
|
||||||
|
@ -228,15 +235,14 @@
|
||||||
"description": "A configurable data list that attaches to your backend models.",
|
"description": "A configurable data list that attaches to your backend models.",
|
||||||
"data": true,
|
"data": true,
|
||||||
"props": {
|
"props": {
|
||||||
"model": "models",
|
"model": "models"
|
||||||
"layout": {
|
|
||||||
"type": "options",
|
|
||||||
"default": "list",
|
|
||||||
"options": [
|
|
||||||
"list",
|
|
||||||
"grid"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"recorddetail": {
|
||||||
|
"description": "Loads a record, using an ID in the url",
|
||||||
|
"data": true,
|
||||||
|
"props": {
|
||||||
|
"model": "models"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"datamap": {
|
"datamap": {
|
||||||
|
|
|
@ -4,6 +4,12 @@
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let model
|
||||||
|
|
||||||
|
const TYPE_MAP = {
|
||||||
|
string: "text",
|
||||||
|
boolean: "checkbox",
|
||||||
|
number: "number",
|
||||||
|
}
|
||||||
|
|
||||||
let username
|
let username
|
||||||
let password
|
let password
|
||||||
let newModel = {
|
let newModel = {
|
||||||
|
@ -59,15 +65,23 @@
|
||||||
|
|
||||||
<form class="form" on:submit|preventDefault>
|
<form class="form" on:submit|preventDefault>
|
||||||
<h1>{modelDef.name} Form</h1>
|
<h1>{modelDef.name} Form</h1>
|
||||||
|
<hr />
|
||||||
<div class="form-content">
|
<div class="form-content">
|
||||||
{#each fields as field}
|
{#each fields as field}
|
||||||
<div class="form-item">
|
<div class="form-item">
|
||||||
<label class="form-label" for="form-stacked-text">{field}</label>
|
<label class="form-label" for="form-stacked-text">{field}</label>
|
||||||
|
{#if schema[field].type === 'string' && schema[field].constraints.inclusion}
|
||||||
|
<select on:blur={handleInput(field)}>
|
||||||
|
{#each schema[field].constraints.inclusion as opt}
|
||||||
|
<option>{opt}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{:else}
|
||||||
<input
|
<input
|
||||||
class="input"
|
class="input"
|
||||||
placeholder={field}
|
type={TYPE_MAP[schema[field].type]}
|
||||||
type={schema[field].type === 'string' ? 'text' : schema[field].type}
|
|
||||||
on:change={handleInput(field)} />
|
on:change={handleInput(field)} />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -143,8 +157,42 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover {
|
button:hover {
|
||||||
background-color: white;
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||||
border-color: #393c44;
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
color: #393c44;
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
vertical-align: bottom;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
*overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
align-items: baseline;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1em 1em;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 5px;
|
||||||
|
font: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
||||||
|
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
||||||
|
background-position: right 17px top 1.5em, right 10px top 1.5em;
|
||||||
|
background-size: 7px 7px, 7px 7px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,173 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
export let _bb
|
||||||
|
export let model
|
||||||
|
|
||||||
|
const TYPE_MAP = {
|
||||||
|
string: "text",
|
||||||
|
boolean: "checkbox",
|
||||||
|
number: "number",
|
||||||
|
}
|
||||||
|
|
||||||
|
let username
|
||||||
|
let password
|
||||||
|
let newModel = {
|
||||||
|
modelId: model,
|
||||||
|
}
|
||||||
|
let store = _bb.store
|
||||||
|
let schema = {}
|
||||||
|
let modelDef = {}
|
||||||
|
$: if (model && model.length !== 0) {
|
||||||
|
fetchModel()
|
||||||
|
}
|
||||||
|
$: fields = Object.keys(schema)
|
||||||
|
async function fetchModel() {
|
||||||
|
const FETCH_MODEL_URL = `/api/models/${model}`
|
||||||
|
const response = await _bb.api.get(FETCH_MODEL_URL)
|
||||||
|
modelDef = await response.json()
|
||||||
|
schema = modelDef.schema
|
||||||
|
}
|
||||||
|
async function save() {
|
||||||
|
const SAVE_RECORD_URL = `/api/${model}/records`
|
||||||
|
const response = await _bb.api.post(SAVE_RECORD_URL, newModel)
|
||||||
|
const json = await response.json()
|
||||||
|
store.update(state => {
|
||||||
|
state[model] = state[model] ? [...state[model], json] : [json]
|
||||||
|
return state
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const handleInput = field => event => {
|
||||||
|
let value
|
||||||
|
if (event.target.type === "checkbox") {
|
||||||
|
value = event.target.checked
|
||||||
|
newModel[field] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (event.target.type === "number") {
|
||||||
|
value = parseInt(event.target.value)
|
||||||
|
newModel[field] = value
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = event.target.value
|
||||||
|
newModel[field] = value
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<form class="form" on:submit|preventDefault>
|
||||||
|
<h1>{modelDef.name} Form</h1>
|
||||||
|
<hr />
|
||||||
|
<div class="form-content">
|
||||||
|
{#each fields as field}
|
||||||
|
<div class="form-item">
|
||||||
|
<label class="form-label" for="form-stacked-text">{field}</label>
|
||||||
|
{#if schema[field].type === 'string' && schema[field].constraints.inclusion}
|
||||||
|
<select on:blur={handleInput(field)}>
|
||||||
|
{#each schema[field].constraints.inclusion as opt}
|
||||||
|
<option>{opt}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{:else}
|
||||||
|
<input
|
||||||
|
class="input"
|
||||||
|
type={TYPE_MAP[schema[field].type]}
|
||||||
|
on:change={handleInput(field)} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<hr />
|
||||||
|
{/each}
|
||||||
|
<div class="button-block">
|
||||||
|
<button on:click={save}>Submit Form</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
padding: 40px;
|
||||||
|
}
|
||||||
|
.form-content {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.input {
|
||||||
|
border-radius: 5px;
|
||||||
|
border: 1px solid #e6e6e6;
|
||||||
|
padding: 1em;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 30% 1fr;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
border: 1px solid #f5f5f5;
|
||||||
|
margin: 40px 0px;
|
||||||
|
}
|
||||||
|
hr:nth-last-child(2) {
|
||||||
|
border: 1px solid #f5f5f5;
|
||||||
|
margin: 40px 0px;
|
||||||
|
}
|
||||||
|
.button-block {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
button {
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 8px 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
background-color: black;
|
||||||
|
outline: none;
|
||||||
|
height: 40px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease 0s;
|
||||||
|
overflow: hidden;
|
||||||
|
outline: none;
|
||||||
|
user-select: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:hover {
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1),
|
||||||
|
0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
transform: scale(2);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
cursor: pointer;
|
||||||
|
display: inline-block;
|
||||||
|
align-items: baseline;
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 1em 1em;
|
||||||
|
border: 1px solid #eaeaea;
|
||||||
|
border-radius: 5px;
|
||||||
|
font: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
-ms-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
|
||||||
|
linear-gradient(135deg, currentColor 50%, transparent 50%);
|
||||||
|
background-position: right 17px top 1.5em, right 10px top 1.5em;
|
||||||
|
background-size: 7px 7px, 7px 7px;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -3,7 +3,6 @@
|
||||||
|
|
||||||
export let _bb
|
export let _bb
|
||||||
export let model
|
export let model
|
||||||
export let layout = "list"
|
|
||||||
|
|
||||||
let headers = []
|
let headers = []
|
||||||
let store = _bb.store
|
let store = _bb.store
|
||||||
|
@ -33,39 +32,5 @@
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<section class:grid={layout === 'grid'} class:list={layout === 'list'}>
|
<section bind:this={target}>
|
||||||
<div class="data-card" bind:this={target} />
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<style>
|
|
||||||
.list {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
font-family: Inter;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 5px 0 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-card {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 5px;
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-key {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 20px;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
<script>
|
||||||
|
import { onMount } from "svelte"
|
||||||
|
|
||||||
|
export let _bb
|
||||||
|
export let model
|
||||||
|
|
||||||
|
let headers = []
|
||||||
|
let store = _bb.store
|
||||||
|
let target
|
||||||
|
|
||||||
|
async function fetchFirstRecord() {
|
||||||
|
const FETCH_RECORDS_URL = `/api/views/all_${model}`
|
||||||
|
const response = await _bb.api.get(FETCH_RECORDS_URL)
|
||||||
|
if (response.status === 200) {
|
||||||
|
const allRecords = await response.json()
|
||||||
|
if (allRecords.length > 0) return allRecords[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchData() {
|
||||||
|
const pathParts = window.location.pathname.split("/")
|
||||||
|
|
||||||
|
let record
|
||||||
|
// if srcdoc, then we assume this is the builder preview
|
||||||
|
if(pathParts.length === 0 || pathParts[0] === "srcdoc") {
|
||||||
|
record = await fetchFirstRecord()
|
||||||
|
} else {
|
||||||
|
const id = pathParts[pathParts.length - 1]
|
||||||
|
const GET_RECORD_URL = `/api/${model}/records/${id}`
|
||||||
|
const response = await _bb.api.get(GET_RECORD_URL)
|
||||||
|
if (response.status === 200) {
|
||||||
|
record = await response.json()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (record) {
|
||||||
|
_bb.attachChildren(target, {
|
||||||
|
hydrate: false,
|
||||||
|
context: record,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to fetch record.", response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
await fetchData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<section bind:this={target}>
|
||||||
|
</section>
|
|
@ -16,9 +16,11 @@ export { default as icon } from "./Icon.svelte"
|
||||||
export { default as Navigation } from "./Navigation.svelte"
|
export { default as Navigation } from "./Navigation.svelte"
|
||||||
export { default as datatable } from "./DataTable.svelte"
|
export { default as datatable } from "./DataTable.svelte"
|
||||||
export { default as dataform } from "./DataForm.svelte"
|
export { default as dataform } from "./DataForm.svelte"
|
||||||
|
export { default as dataformwide } from "./DataFormWide.svelte"
|
||||||
export { default as datachart } from "./DataChart.svelte"
|
export { default as datachart } from "./DataChart.svelte"
|
||||||
export { default as datalist } from "./DataList.svelte"
|
export { default as datalist } from "./DataList.svelte"
|
||||||
export { default as list } from "./List.svelte"
|
export { default as list } from "./List.svelte"
|
||||||
export { default as datasearch } from "./DataSearch.svelte"
|
export { default as datasearch } from "./DataSearch.svelte"
|
||||||
export { default as datamap } from "./DataMap.svelte"
|
export { default as datamap } from "./DataMap.svelte"
|
||||||
export { default as embed } from "./Embed.svelte"
|
export { default as embed } from "./Embed.svelte"
|
||||||
|
export { default as recorddetail } from "./RecordDetail.svelte"
|
||||||
|
|
Loading…
Reference in New Issue