Standardise sizing of all settings and design properties
This commit is contained in:
parent
3b869d2ff4
commit
4e4aa5c525
|
@ -48,7 +48,7 @@
|
|||
{/each}
|
||||
{:else}
|
||||
<div class="no-design">
|
||||
This component does not have any design properties.
|
||||
This component doesn't have any design properties.
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
@ -61,13 +61,7 @@
|
|||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.design-view-container :global(.property-group-name .name) {
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.design-view-container :global(.property-group-container) {
|
||||
padding: 4px 0;
|
||||
gap: var(--spacing-l);
|
||||
}
|
||||
|
||||
.design-view-state-categories {
|
||||
|
@ -85,10 +79,15 @@
|
|||
min-height: 0;
|
||||
margin: 0 -20px;
|
||||
padding: 0 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
|
||||
.no-design {
|
||||
font-size: var(--font-size-s);
|
||||
color: var(--grey-6);
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--grey-5);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
return [
|
||||
...$store.screens
|
||||
.filter(
|
||||
screen =>
|
||||
(screen) =>
|
||||
screen.props._component.endsWith("/rowdetail") ||
|
||||
screen.route.endsWith(":id")
|
||||
)
|
||||
.map(screen => ({
|
||||
.map((screen) => ({
|
||||
name: screen.props._instanceName,
|
||||
url: screen.route,
|
||||
sort: screen.props._component,
|
||||
|
@ -29,7 +29,13 @@
|
|||
</script>
|
||||
|
||||
<div>
|
||||
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value>
|
||||
<DataList
|
||||
editable
|
||||
secondary
|
||||
extraThin
|
||||
on:blur={handleBlur}
|
||||
on:change
|
||||
bind:value>
|
||||
<option value="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
|
|
|
@ -1,291 +1,291 @@
|
|||
<script>
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import { createEventDispatcher, tick } from "svelte"
|
||||
|
||||
import icons from "./icons.js"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let maxIconsPerPage = 30
|
||||
|
||||
let searchTerm = ""
|
||||
let selectedLetter = "A"
|
||||
|
||||
let currentPage = 1
|
||||
let filteredIcons = findIconByTerm(selectedLetter)
|
||||
|
||||
$: dispatch("change", value)
|
||||
|
||||
const alphabet = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
]
|
||||
let buttonAnchor, dropdown
|
||||
let loading = false
|
||||
|
||||
function findIconByTerm(term) {
|
||||
const r = new RegExp(`\^${term}`, "i")
|
||||
return icons.filter(i => r.test(i.label))
|
||||
}
|
||||
|
||||
async function switchLetter(letter) {
|
||||
currentPage = 1
|
||||
searchTerm = ""
|
||||
loading = true
|
||||
selectedLetter = letter
|
||||
filteredIcons = findIconByTerm(letter)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function findIconOnPage() {
|
||||
loading = true
|
||||
const iconIdx = filteredIcons.findIndex(i => i.value === value)
|
||||
if (iconIdx !== -1) {
|
||||
currentPage = Math.ceil(iconIdx / maxIconsPerPage)
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function setSelectedUI() {
|
||||
if (value) {
|
||||
const letter = displayValue.substring(0, 1)
|
||||
await switchLetter(letter)
|
||||
await findIconOnPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function pageClick(next) {
|
||||
loading = true
|
||||
if (next && currentPage < totalPages) {
|
||||
currentPage++
|
||||
} else if (!next && currentPage > 1) {
|
||||
currentPage--
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function searchForIcon(e) {
|
||||
currentPage = 1
|
||||
loading = true
|
||||
filteredIcons = findIconByTerm(searchTerm)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
$: displayValue = value ? value.substring(7) : "Pick Icon"
|
||||
|
||||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
|
||||
$: pageEndIdx = maxIconsPerPage * currentPage
|
||||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx)
|
||||
|
||||
$: pagerText = `Page ${currentPage} of ${totalPages}`
|
||||
</script>
|
||||
|
||||
<div bind:this={buttonAnchor}>
|
||||
<Button secondary on:click={dropdown.show}>{displayValue}</Button>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={dropdown}
|
||||
on:open={setSelectedUI}
|
||||
anchor={buttonAnchor}>
|
||||
<div class="container">
|
||||
<div class="search-area">
|
||||
<div class="alphabet-area">
|
||||
{#each alphabet as letter, idx}
|
||||
<span
|
||||
class="letter"
|
||||
class:letter-selected={letter === selectedLetter}
|
||||
on:click={() => switchLetter(letter)}>
|
||||
{letter}
|
||||
</span>
|
||||
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
<div class="input-wrapper">
|
||||
<Input bind:value={searchTerm} thin placeholder="Search Icon" />
|
||||
</div>
|
||||
<Button secondary on:click={searchForIcon}>Search</Button>
|
||||
</div>
|
||||
<div class="page-area">
|
||||
<div class="pager">
|
||||
<span on:click={() => pageClick(false)}>
|
||||
<i class="page-btn fas fa-chevron-left" />
|
||||
</span>
|
||||
<span>{pagerText}</span>
|
||||
<span on:click={() => pageClick(true)}>
|
||||
<i class="page-btn fas fa-chevron-right" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if pagedIcons.length > 0}
|
||||
<div class="icon-area">
|
||||
{#if !loading}
|
||||
{#each pagedIcons as icon}
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === icon.value}
|
||||
on:click={() => (value = icon.value)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`${icon.value} fa-3x`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon.label}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-icons">
|
||||
<h5>
|
||||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`}
|
||||
</h5>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 610px;
|
||||
height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0px 10px 15px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.icon-area {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.no-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alphabet-area {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 15px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.page-area {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.letter {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.letter:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.letter-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: var(--border-dark);
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
cursor: pointer;
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { DropdownMenu, Button, Input } from "@budibase/bbui"
|
||||
import { createEventDispatcher, tick } from "svelte"
|
||||
|
||||
import icons from "./icons.js"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
export let maxIconsPerPage = 30
|
||||
|
||||
let searchTerm = ""
|
||||
let selectedLetter = "A"
|
||||
|
||||
let currentPage = 1
|
||||
let filteredIcons = findIconByTerm(selectedLetter)
|
||||
|
||||
$: dispatch("change", value)
|
||||
|
||||
const alphabet = [
|
||||
"A",
|
||||
"B",
|
||||
"C",
|
||||
"D",
|
||||
"E",
|
||||
"F",
|
||||
"G",
|
||||
"H",
|
||||
"I",
|
||||
"J",
|
||||
"K",
|
||||
"L",
|
||||
"M",
|
||||
"N",
|
||||
"O",
|
||||
"P",
|
||||
"Q",
|
||||
"R",
|
||||
"S",
|
||||
"T",
|
||||
"U",
|
||||
"V",
|
||||
"W",
|
||||
"X",
|
||||
"Y",
|
||||
"Z",
|
||||
]
|
||||
let buttonAnchor, dropdown
|
||||
let loading = false
|
||||
|
||||
function findIconByTerm(term) {
|
||||
const r = new RegExp(`\^${term}`, "i")
|
||||
return icons.filter((i) => r.test(i.label))
|
||||
}
|
||||
|
||||
async function switchLetter(letter) {
|
||||
currentPage = 1
|
||||
searchTerm = ""
|
||||
loading = true
|
||||
selectedLetter = letter
|
||||
filteredIcons = findIconByTerm(letter)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function findIconOnPage() {
|
||||
loading = true
|
||||
const iconIdx = filteredIcons.findIndex((i) => i.value === value)
|
||||
if (iconIdx !== -1) {
|
||||
currentPage = Math.ceil(iconIdx / maxIconsPerPage)
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function setSelectedUI() {
|
||||
if (value) {
|
||||
const letter = displayValue.substring(0, 1)
|
||||
await switchLetter(letter)
|
||||
await findIconOnPage()
|
||||
}
|
||||
}
|
||||
|
||||
async function pageClick(next) {
|
||||
loading = true
|
||||
if (next && currentPage < totalPages) {
|
||||
currentPage++
|
||||
} else if (!next && currentPage > 1) {
|
||||
currentPage--
|
||||
}
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
async function searchForIcon(e) {
|
||||
currentPage = 1
|
||||
loading = true
|
||||
filteredIcons = findIconByTerm(searchTerm)
|
||||
await tick() //svg icons do not update without tick
|
||||
loading = false
|
||||
}
|
||||
|
||||
$: displayValue = value ? value.substring(7) : "Pick Icon"
|
||||
|
||||
$: totalPages = Math.ceil(filteredIcons.length / maxIconsPerPage)
|
||||
$: pageEndIdx = maxIconsPerPage * currentPage
|
||||
$: pagedIcons = filteredIcons.slice(pageEndIdx - maxIconsPerPage, pageEndIdx)
|
||||
|
||||
$: pagerText = `Page ${currentPage} of ${totalPages}`
|
||||
</script>
|
||||
|
||||
<div bind:this={buttonAnchor}>
|
||||
<Button secondary small on:click={dropdown.show}>{displayValue}</Button>
|
||||
</div>
|
||||
<DropdownMenu
|
||||
bind:this={dropdown}
|
||||
on:open={setSelectedUI}
|
||||
anchor={buttonAnchor}>
|
||||
<div class="container">
|
||||
<div class="search-area">
|
||||
<div class="alphabet-area">
|
||||
{#each alphabet as letter, idx}
|
||||
<span
|
||||
class="letter"
|
||||
class:letter-selected={letter === selectedLetter}
|
||||
on:click={() => switchLetter(letter)}>
|
||||
{letter}
|
||||
</span>
|
||||
{#if idx !== alphabet.length - 1}<span>-</span>{/if}
|
||||
{/each}
|
||||
</div>
|
||||
<div class="search-input">
|
||||
<div class="input-wrapper">
|
||||
<Input bind:value={searchTerm} thin placeholder="Search Icon" />
|
||||
</div>
|
||||
<Button secondary on:click={searchForIcon}>Search</Button>
|
||||
</div>
|
||||
<div class="page-area">
|
||||
<div class="pager">
|
||||
<span on:click={() => pageClick(false)}>
|
||||
<i class="page-btn fas fa-chevron-left" />
|
||||
</span>
|
||||
<span>{pagerText}</span>
|
||||
<span on:click={() => pageClick(true)}>
|
||||
<i class="page-btn fas fa-chevron-right" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if pagedIcons.length > 0}
|
||||
<div class="icon-area">
|
||||
{#if !loading}
|
||||
{#each pagedIcons as icon}
|
||||
<div
|
||||
class="icon-container"
|
||||
class:selected={value === icon.value}
|
||||
on:click={() => (value = icon.value)}>
|
||||
<div class="icon-preview">
|
||||
<i class={`${icon.value} fa-3x`} />
|
||||
</div>
|
||||
<div class="icon-label">{icon.label}</div>
|
||||
</div>
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="no-icons">
|
||||
<h5>
|
||||
{`There is no icons for this ${searchTerm ? 'search' : 'page'}`}
|
||||
</h5>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
width: 610px;
|
||||
height: 350px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 0px 10px 15px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.search-area {
|
||||
flex: 0 0 80px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.icon-area {
|
||||
flex: 1;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
grid-gap: 5px;
|
||||
justify-content: flex-start;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.no-icons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.alphabet-area {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
padding-bottom: 10px;
|
||||
padding-right: 15px;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
||||
.loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
width: 100%;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 510px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.page-area {
|
||||
padding: 10px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.letter {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.letter:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.letter-selected {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon-container {
|
||||
height: 100px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
border: var(--border-dark);
|
||||
}
|
||||
|
||||
.icon-container:hover {
|
||||
cursor: pointer;
|
||||
background: var(--grey-2);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background: var(--grey-3);
|
||||
}
|
||||
|
||||
.icon-preview {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon-label {
|
||||
flex: 0 0 20px;
|
||||
text-align: center;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.page-btn {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.page-btn:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -175,7 +175,7 @@
|
|||
.bb-select-anchor {
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: var(--spacing-m);
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
background-color: var(--grey-2);
|
||||
border-radius: var(--border-radius-m);
|
||||
align-items: center;
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
height: 90%;
|
||||
width: 2rem;
|
||||
background: var(--grey-2);
|
||||
right: 10px;
|
||||
right: 4px;
|
||||
--spacing-s: 0;
|
||||
border-left: 0.5px solid var(--grey-3);
|
||||
outline-color: var(--blue);
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
$: style = componentInstance["_styles"][styleCategory] || {}
|
||||
</script>
|
||||
|
||||
<DetailSummary {name} on:open show={open}>
|
||||
<DetailSummary {name} on:open show={open} thin>
|
||||
<div>
|
||||
{#each properties as props}
|
||||
<PropertyControl
|
||||
|
@ -34,6 +34,5 @@
|
|||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
gap: var(--spacing-s);
|
||||
margin-top: var(--spacing-m);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -8,4 +8,4 @@
|
|||
export let name, value, placeholder, type
|
||||
</script>
|
||||
|
||||
<Input {name} {value} {placeholder} {type} thin on:change />
|
||||
<Input {name} {value} {placeholder} {type} extraThin on:change />
|
||||
|
|
|
@ -1,88 +1,94 @@
|
|||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
||||
$: urls = getUrls()
|
||||
|
||||
const handleBlur = () => dispatch("change", value)
|
||||
|
||||
// this will get urls of all screens, but only
|
||||
// choose detail screens that are usable in the current context
|
||||
// and substitute the :id param for the actual {{ ._id }} binding
|
||||
const getUrls = () => {
|
||||
const urls = [
|
||||
...$store.screens
|
||||
.filter(screen => !screen.props._component.endsWith("/rowdetail"))
|
||||
.map(screen => ({
|
||||
name: screen.props._instanceName,
|
||||
url: screen.route,
|
||||
sort: screen.props._component,
|
||||
})),
|
||||
]
|
||||
|
||||
const bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
const detailScreens = $store.screens.filter(screen =>
|
||||
screen.props._component.endsWith("/rowdetail")
|
||||
)
|
||||
|
||||
for (let detailScreen of detailScreens) {
|
||||
const idBinding = bindableProperties.find(p => {
|
||||
if (
|
||||
p.type === "context" &&
|
||||
p.runtimeBinding.endsWith("._id") &&
|
||||
p.table
|
||||
) {
|
||||
const tableId =
|
||||
typeof p.table === "string" ? p.table : p.table.tableId
|
||||
return tableId === detailScreen.props.table
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (idBinding) {
|
||||
urls.push({
|
||||
name: detailScreen.props._instanceName,
|
||||
url: detailScreen.route.replace(
|
||||
":id",
|
||||
`{{ ${idBinding.runtimeBinding} }}`
|
||||
),
|
||||
sort: detailScreen.props._component,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<DataList editable secondary thin on:blur={handleBlur} on:change bind:value>
|
||||
<option value="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div :global(> div) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { DataList } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value = ""
|
||||
|
||||
$: urls = getUrls()
|
||||
|
||||
const handleBlur = () => dispatch("change", value)
|
||||
|
||||
// this will get urls of all screens, but only
|
||||
// choose detail screens that are usable in the current context
|
||||
// and substitute the :id param for the actual {{ ._id }} binding
|
||||
const getUrls = () => {
|
||||
const urls = [
|
||||
...$store.screens
|
||||
.filter((screen) => !screen.props._component.endsWith("/rowdetail"))
|
||||
.map((screen) => ({
|
||||
name: screen.props._instanceName,
|
||||
url: screen.route,
|
||||
sort: screen.props._component,
|
||||
})),
|
||||
]
|
||||
|
||||
const bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
const detailScreens = $store.screens.filter((screen) =>
|
||||
screen.props._component.endsWith("/rowdetail")
|
||||
)
|
||||
|
||||
for (let detailScreen of detailScreens) {
|
||||
const idBinding = bindableProperties.find((p) => {
|
||||
if (
|
||||
p.type === "context" &&
|
||||
p.runtimeBinding.endsWith("._id") &&
|
||||
p.table
|
||||
) {
|
||||
const tableId =
|
||||
typeof p.table === "string" ? p.table : p.table.tableId
|
||||
return tableId === detailScreen.props.table
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
if (idBinding) {
|
||||
urls.push({
|
||||
name: detailScreen.props._instanceName,
|
||||
url: detailScreen.route.replace(
|
||||
":id",
|
||||
`{{ ${idBinding.runtimeBinding} }}`
|
||||
),
|
||||
sort: detailScreen.props._component,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return urls
|
||||
}
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<DataList
|
||||
editable
|
||||
secondary
|
||||
extraThin
|
||||
on:blur={handleBlur}
|
||||
on:change
|
||||
bind:value>
|
||||
<option value="" />
|
||||
{#each urls as url}
|
||||
<option value={url.url}>{url.name}</option>
|
||||
{/each}
|
||||
</DataList>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
flex: 1 1 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
div :global(> div) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -125,7 +125,9 @@
|
|||
{/if}
|
||||
{/each}
|
||||
{:else}
|
||||
<div class="empty">This component does not have any settings.</div>
|
||||
<div class="empty">
|
||||
This component doesn't have any additional settings.
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -139,9 +141,9 @@
|
|||
}
|
||||
|
||||
.empty {
|
||||
font-size: var(--font-size-s);
|
||||
font-size: var(--font-size-xs);
|
||||
margin-top: var(--spacing-m);
|
||||
color: var(--grey-6);
|
||||
color: var(--grey-5);
|
||||
}
|
||||
|
||||
.duplicate-name {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
</script>
|
||||
|
||||
<div>
|
||||
<Select thin secondary wide on:change {value}>
|
||||
<Select extraThin secondary wide on:change {value}>
|
||||
<option value="">Choose a table</option>
|
||||
{#each $backendUiStore.tables as table}
|
||||
<option value={table._id}>{table.name}</option>
|
||||
|
|
|
@ -1,163 +1,163 @@
|
|||
<script>
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
||||
export let value = {}
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
$: tables = $backendUiStore.tables.map(m => ({
|
||||
label: m.name,
|
||||
name: `all_${m._id}`,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
}))
|
||||
|
||||
$: views = $backendUiStore.tables.reduce((acc, cur) => {
|
||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
||||
label: key,
|
||||
name: key,
|
||||
...value,
|
||||
type: "view",
|
||||
}))
|
||||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter(x => x.fieldSchema.type === "link")
|
||||
.map(property => ({
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
name: `all_${property.fieldSchema.tableId}`,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
type: "link",
|
||||
}))
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each tables as table}
|
||||
<li
|
||||
class:selected={value === table}
|
||||
on:click={() => handleSelected(table)}>
|
||||
{table.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li
|
||||
class:selected={value === view}
|
||||
on:click={() => handleSelected(view)}>
|
||||
{view.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li
|
||||
class:selected={value === link}
|
||||
on:click={() => handleSelected(link)}>
|
||||
{link.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.dropdownbutton {
|
||||
background-color: var(--grey-2);
|
||||
border: var(--border-transparent);
|
||||
padding: var(--spacing-m);
|
||||
border-radius: var(--border-radius-m);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dropdownbutton:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--grey-3);
|
||||
}
|
||||
.dropdownbutton span {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.dropdownbutton :global(svg) {
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
padding: var(--spacing-m) 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
.title {
|
||||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-m) 0 var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
import { Button, Icon, DropdownMenu, Spacer, Heading } from "@budibase/bbui"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { store, backendUiStore } from "builderStore"
|
||||
import fetchBindableProperties from "../../builderStore/fetchBindableProperties"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
let anchorRight, dropdownRight
|
||||
|
||||
export let value = {}
|
||||
|
||||
function handleSelected(selected) {
|
||||
dispatch("change", selected)
|
||||
dropdownRight.hide()
|
||||
}
|
||||
|
||||
$: tables = $backendUiStore.tables.map((m) => ({
|
||||
label: m.name,
|
||||
name: `all_${m._id}`,
|
||||
tableId: m._id,
|
||||
type: "table",
|
||||
}))
|
||||
|
||||
$: views = $backendUiStore.tables.reduce((acc, cur) => {
|
||||
let viewsArr = Object.entries(cur.views).map(([key, value]) => ({
|
||||
label: key,
|
||||
name: key,
|
||||
...value,
|
||||
type: "view",
|
||||
}))
|
||||
return [...acc, ...viewsArr]
|
||||
}, [])
|
||||
|
||||
$: bindableProperties = fetchBindableProperties({
|
||||
componentInstanceId: $store.currentComponentInfo._id,
|
||||
components: $store.components,
|
||||
screen: $store.currentPreviewItem,
|
||||
tables: $backendUiStore.tables,
|
||||
})
|
||||
|
||||
$: links = bindableProperties
|
||||
.filter((x) => x.fieldSchema.type === "link")
|
||||
.map((property) => ({
|
||||
label: property.readableBinding,
|
||||
fieldName: property.fieldSchema.name,
|
||||
name: `all_${property.fieldSchema.tableId}`,
|
||||
tableId: property.fieldSchema.tableId,
|
||||
type: "link",
|
||||
}))
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="dropdownbutton"
|
||||
bind:this={anchorRight}
|
||||
on:click={dropdownRight.show}>
|
||||
<span>{value.label ? value.label : 'Table / View'}</span>
|
||||
<Icon name="arrowdown" />
|
||||
</div>
|
||||
<DropdownMenu bind:this={dropdownRight} anchor={anchorRight}>
|
||||
<div class="dropdown">
|
||||
<div class="title">
|
||||
<Heading extraSmall>Tables</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each tables as table}
|
||||
<li
|
||||
class:selected={value === table}
|
||||
on:click={() => handleSelected(table)}>
|
||||
{table.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Views</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each views as view}
|
||||
<li
|
||||
class:selected={value === view}
|
||||
on:click={() => handleSelected(view)}>
|
||||
{view.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
<hr />
|
||||
<div class="title">
|
||||
<Heading extraSmall>Relationships</Heading>
|
||||
</div>
|
||||
<ul>
|
||||
{#each links as link}
|
||||
<li
|
||||
class:selected={value === link}
|
||||
on:click={() => handleSelected(link)}>
|
||||
{link.label}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</div>
|
||||
</DropdownMenu>
|
||||
|
||||
<style>
|
||||
.dropdownbutton {
|
||||
background-color: var(--grey-2);
|
||||
border: var(--border-transparent);
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
border-radius: var(--border-radius-m);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.dropdownbutton:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--grey-3);
|
||||
}
|
||||
.dropdownbutton span {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1 1 auto;
|
||||
text-align: left;
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
.dropdownbutton :global(svg) {
|
||||
margin: -4px 0;
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
padding: var(--spacing-m) 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
.title {
|
||||
padding: 0 var(--spacing-m) var(--spacing-xs) var(--spacing-m);
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-m) 0 var(--spacing-xl) 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
li {
|
||||
cursor: pointer;
|
||||
margin: 0px;
|
||||
padding: var(--spacing-s) var(--spacing-m);
|
||||
font-size: var(--font-size-xs);
|
||||
}
|
||||
|
||||
.selected {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--grey-4);
|
||||
}
|
||||
</style>
|
||||
|
|
Loading…
Reference in New Issue