mirror of https://github.com/Budibase/budibase.git
11 changed files with 576 additions and 564 deletions
@ -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> |
|||
|
|||
@ -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> |
|||
|
|||
@ -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