mirror of https://github.com/Budibase/budibase.git
committed by
GitHub
50 changed files with 731 additions and 820 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,125 @@ |
|||||
|
<script> |
||||
|
import { onMount, onDestroy } from "svelte" |
||||
|
import { Modal, ModalContent } from "@budibase/bbui" |
||||
|
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
||||
|
|
||||
|
const SORT_ICON_MAP = { |
||||
|
asc: "ri-arrow-down-fill", |
||||
|
desc: "ri-arrow-up-fill", |
||||
|
} |
||||
|
|
||||
|
export let field |
||||
|
export let displayName |
||||
|
export let column |
||||
|
export let enableSorting = true |
||||
|
export let showColumnMenu |
||||
|
export let progressSort |
||||
|
export let editable |
||||
|
|
||||
|
let menuButton |
||||
|
let sortDirection = "" |
||||
|
let modal |
||||
|
let hovered |
||||
|
let filterActive |
||||
|
|
||||
|
function toggleMenu() { |
||||
|
showColumnMenu(menuButton) |
||||
|
} |
||||
|
|
||||
|
function onSort(event) { |
||||
|
progressSort(event.shiftKey) |
||||
|
} |
||||
|
|
||||
|
function showModal() { |
||||
|
modal.show() |
||||
|
} |
||||
|
|
||||
|
function setSort() { |
||||
|
sortDirection = column.getSort() |
||||
|
} |
||||
|
|
||||
|
function setFilterActive(e) { |
||||
|
filterActive = e.column.filterActive |
||||
|
} |
||||
|
|
||||
|
onMount(() => { |
||||
|
column.addEventListener("sortChanged", setSort) |
||||
|
column.addEventListener("filterActiveChanged", setFilterActive) |
||||
|
}) |
||||
|
|
||||
|
onDestroy(() => { |
||||
|
column.removeEventListener("sortChanged", setSort) |
||||
|
column.removeEventListener("filterActiveChanged", setFilterActive) |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<header |
||||
|
on:click={onSort} |
||||
|
data-cy="table-header" |
||||
|
on:mouseover={() => (hovered = true)} |
||||
|
on:mouseleave={() => (hovered = false)}> |
||||
|
<div> |
||||
|
<span class="column-header-name">{displayName}</span> |
||||
|
<i class={`${SORT_ICON_MAP[sortDirection]} sort-icon`} /> |
||||
|
</div> |
||||
|
<Modal bind:this={modal}> |
||||
|
<ModalContent |
||||
|
showCancelButton={false} |
||||
|
showConfirmButton={false} |
||||
|
title={`Edit Column: ${field.name}`}> |
||||
|
<CreateEditColumn onClosed={modal.hide} {field} /> |
||||
|
</ModalContent> |
||||
|
</Modal> |
||||
|
<section class:show={hovered || filterActive}> |
||||
|
{#if editable && hovered} |
||||
|
<span on:click|stopPropagation={showModal}> |
||||
|
<i class="ri-pencil-line" /> |
||||
|
</span> |
||||
|
{/if} |
||||
|
<span on:click|stopPropagation={toggleMenu} bind:this={menuButton}> |
||||
|
<i class="ri-filter-line" class:active={filterActive} /> |
||||
|
</span> |
||||
|
</section> |
||||
|
</header> |
||||
|
|
||||
|
<style> |
||||
|
header { |
||||
|
font-family: Inter; |
||||
|
font-weight: 600; |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
height: 100%; |
||||
|
width: 100%; |
||||
|
align-items: center; |
||||
|
color: var(--ink); |
||||
|
} |
||||
|
|
||||
|
section { |
||||
|
opacity: 0; |
||||
|
transition: 0.3s all; |
||||
|
} |
||||
|
|
||||
|
section.show { |
||||
|
opacity: 1; |
||||
|
} |
||||
|
|
||||
|
.sort-icon { |
||||
|
position: relative; |
||||
|
top: 2px; |
||||
|
} |
||||
|
|
||||
|
i { |
||||
|
transition: 0.2s all; |
||||
|
font-size: var(--font-size-m); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
i:hover { |
||||
|
color: var(--blue); |
||||
|
} |
||||
|
|
||||
|
i.active, |
||||
|
i:hover { |
||||
|
color: var(--blue); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,35 @@ |
|||||
|
import TableHeader from "./TableHeader.svelte" |
||||
|
|
||||
|
export default class TableHeaderWrapper { |
||||
|
constructor() {} |
||||
|
|
||||
|
init(params) { |
||||
|
this.agParams = params |
||||
|
this.container = document.createElement("div") |
||||
|
this.container.style.height = "100%" |
||||
|
this.container.style.width = "100%" |
||||
|
|
||||
|
this.headerComponent = new TableHeader({ |
||||
|
target: this.container, |
||||
|
props: params, |
||||
|
}) |
||||
|
this.gui = this.container |
||||
|
} |
||||
|
|
||||
|
// can get called more than once, you should return the HTML element
|
||||
|
getGui() { |
||||
|
return this.gui |
||||
|
} |
||||
|
|
||||
|
// gets called when a new Column Definition has been set for this header
|
||||
|
refresh(params) { |
||||
|
this.agParams = params |
||||
|
this.headerComponent = new TableHeader({ |
||||
|
target: this.container, |
||||
|
props: params, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
// optional method, gets called once, when component is destroyed
|
||||
|
destroy() {} |
||||
|
} |
||||
@ -0,0 +1,29 @@ |
|||||
|
<script> |
||||
|
import Spinner from "components/common/Spinner.svelte" |
||||
|
import { fade } from "svelte/transition" |
||||
|
</script> |
||||
|
|
||||
|
<div class="ag-overlay-loading-center loading-container"> |
||||
|
<div transition:fade class="loading-overlay"> |
||||
|
<img |
||||
|
height="30" |
||||
|
width="30" |
||||
|
src="/_builder/assets/bb-logo.svg" |
||||
|
alt="Budibase icon" /> |
||||
|
<span> Loading Your Data </span> |
||||
|
<Spinner size="12" /> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.loading-overlay { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
font-family: var(--font-sans); |
||||
|
font-weight: 500; |
||||
|
} |
||||
|
|
||||
|
.loading-overlay > * { |
||||
|
margin-right: var(--spacing-m); |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,17 @@ |
|||||
|
import LoadingOverlay from "./LoadingOverlay.svelte" |
||||
|
|
||||
|
export default class LoadingOverlayWrapper { |
||||
|
init(params) { |
||||
|
this.gui = document.createElement("div") |
||||
|
new LoadingOverlay({ |
||||
|
target: this.gui, |
||||
|
props: { |
||||
|
params, |
||||
|
}, |
||||
|
}) |
||||
|
} |
||||
|
|
||||
|
getGui() { |
||||
|
return this.gui |
||||
|
} |
||||
|
} |
||||
@ -1,136 +0,0 @@ |
|||||
<script> |
|
||||
export let data |
|
||||
export let currentPage = 0 |
|
||||
export let pageItemCount |
|
||||
export let ITEMS_PER_PAGE |
|
||||
|
|
||||
let numPages = 0 |
|
||||
$: numPages = Math.ceil((data?.length ?? 0) / ITEMS_PER_PAGE) |
|
||||
$: displayAllPages = numPages <= 10 |
|
||||
$: pagesAroundCurrent = getPagesAroundCurrent(currentPage, numPages) |
|
||||
|
|
||||
const next = () => { |
|
||||
if (currentPage + 1 === numPages) return |
|
||||
currentPage = currentPage + 1 |
|
||||
} |
|
||||
|
|
||||
const previous = () => { |
|
||||
if (currentPage == 0) return |
|
||||
currentPage = currentPage - 1 |
|
||||
} |
|
||||
|
|
||||
const selectPage = page => { |
|
||||
currentPage = page |
|
||||
} |
|
||||
|
|
||||
function getPagesAroundCurrent(current, max) { |
|
||||
const start = Math.max(current - 2, 1) |
|
||||
const end = Math.min(current + 2, max - 2) |
|
||||
let pages = [] |
|
||||
for (let i = start; i <= end; i++) { |
|
||||
pages.push(i) |
|
||||
} |
|
||||
return pages |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="pagination"> |
|
||||
<div class="pagination__buttons"> |
|
||||
<button on:click={previous} disabled={currentPage === 0}><</button> |
|
||||
{#if displayAllPages} |
|
||||
{#each Array(numPages) as _, idx} |
|
||||
<button |
|
||||
class:selected={idx === currentPage} |
|
||||
on:click={() => selectPage(idx)}> |
|
||||
{idx + 1} |
|
||||
</button> |
|
||||
{/each} |
|
||||
{:else} |
|
||||
<button class:selected={currentPage === 0} on:click={() => selectPage(0)}> |
|
||||
1 |
|
||||
</button> |
|
||||
{#if currentPage > 3}<button disabled>...</button>{/if} |
|
||||
{#each pagesAroundCurrent as idx} |
|
||||
<button |
|
||||
class:selected={idx === currentPage} |
|
||||
on:click={() => selectPage(idx)}> |
|
||||
{idx + 1} |
|
||||
</button> |
|
||||
{/each} |
|
||||
{#if currentPage < numPages - 4}<button disabled>...</button>{/if} |
|
||||
<button |
|
||||
class:selected={currentPage === numPages - 1} |
|
||||
on:click={() => selectPage(numPages - 1)}> |
|
||||
{numPages} |
|
||||
</button> |
|
||||
{/if} |
|
||||
<button |
|
||||
on:click={next} |
|
||||
disabled={currentPage === numPages - 1 || numPages === 0}> |
|
||||
> |
|
||||
</button> |
|
||||
</div> |
|
||||
|
|
||||
<p> |
|
||||
{#if numPages > 1} |
|
||||
Showing |
|
||||
{ITEMS_PER_PAGE * currentPage + 1} |
|
||||
- |
|
||||
{ITEMS_PER_PAGE * currentPage + pageItemCount} |
|
||||
of |
|
||||
{data.length} |
|
||||
rows |
|
||||
{:else if numPages === 1}Showing all {data.length} row(s){/if} |
|
||||
</p> |
|
||||
</div> |
|
||||
|
|
||||
<style> |
|
||||
.pagination { |
|
||||
width: 100%; |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
align-items: center; |
|
||||
justify-content: center; |
|
||||
} |
|
||||
|
|
||||
.pagination__buttons { |
|
||||
display: flex; |
|
||||
border: 1px solid var(--grey-4); |
|
||||
border-radius: var(--border-radius-s); |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.pagination__buttons button { |
|
||||
display: inline-block; |
|
||||
padding: var(--spacing-s) 0; |
|
||||
text-align: center; |
|
||||
margin: 0; |
|
||||
background: #fff; |
|
||||
border: none; |
|
||||
outline: none; |
|
||||
border-right: 1px solid var(--grey-4); |
|
||||
text-transform: capitalize; |
|
||||
min-width: 20px; |
|
||||
transition: 0.3s background-color; |
|
||||
font-family: var(--font-sans); |
|
||||
color: var(--grey-6); |
|
||||
width: 40px; |
|
||||
} |
|
||||
.pagination__buttons button:last-child { |
|
||||
border-right: none; |
|
||||
} |
|
||||
.pagination__buttons button:hover { |
|
||||
cursor: pointer; |
|
||||
background-color: var(--grey-1); |
|
||||
} |
|
||||
.pagination__buttons button.selected { |
|
||||
background: var(--blue); |
|
||||
color: white; |
|
||||
} |
|
||||
|
|
||||
p { |
|
||||
font-size: var(--font-size-s); |
|
||||
margin: var(--spacing-xl) 0; |
|
||||
color: var(--grey-6); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,27 +1,28 @@ |
|||||
<script> |
<script> |
||||
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui" |
import { |
||||
import CreateEditColumnPopover from "../popovers/CreateEditColumnPopover.svelte" |
DropdownMenu, |
||||
|
TextButton as Button, |
||||
|
Icon, |
||||
|
Modal, |
||||
|
ModalContent, |
||||
|
} from "@budibase/bbui" |
||||
|
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
||||
|
|
||||
let anchor |
let modal |
||||
let dropdown |
|
||||
let fieldName |
let fieldName |
||||
</script> |
</script> |
||||
|
|
||||
<div bind:this={anchor}> |
<div> |
||||
<Button text small on:click={dropdown.show}> |
<Button text small on:click={modal.show}> |
||||
<Icon name="addcolumn" /> |
<Icon name="addcolumn" /> |
||||
Create New Column |
Create New Column |
||||
</Button> |
</Button> |
||||
</div> |
</div> |
||||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
<Modal bind:this={modal}> |
||||
<h5>Create Column</h5> |
<ModalContent |
||||
<CreateEditColumnPopover onClosed={dropdown.hide} /> |
showCancelButton={false} |
||||
</DropdownMenu> |
showConfirmButton={false} |
||||
|
title={'Create Column'}> |
||||
<style> |
<CreateEditColumn onClosed={modal.hide} /> |
||||
h5 { |
</ModalContent> |
||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
</Modal> |
||||
margin: 0; |
|
||||
font-weight: 500; |
|
||||
} |
|
||||
</style> |
|
||||
|
|||||
@ -0,0 +1,34 @@ |
|||||
|
<script> |
||||
|
import { TextButton, Icon, Modal, ModalContent } from "@budibase/bbui" |
||||
|
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte" |
||||
|
|
||||
|
export let selectedRows |
||||
|
export let deleteRows |
||||
|
|
||||
|
let modal |
||||
|
|
||||
|
async function confirmDeletion() { |
||||
|
await deleteRows() |
||||
|
modal.hide() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div> |
||||
|
<TextButton small text on:click={modal.show}> |
||||
|
<Icon name="delete" /> |
||||
|
Delete |
||||
|
{selectedRows.length} |
||||
|
row(s) |
||||
|
</TextButton> |
||||
|
</div> |
||||
|
<Modal bind:this={modal}> |
||||
|
<ModalContent |
||||
|
red |
||||
|
confirmText="Delete" |
||||
|
onConfirm={confirmDeletion} |
||||
|
title="Confirm Row Deletion"> |
||||
|
Are you sure you want to delete |
||||
|
{selectedRows.length} |
||||
|
row{selectedRows.length > 1 ? 's' : ''}? |
||||
|
</ModalContent> |
||||
|
</Modal> |
||||
@ -0,0 +1,26 @@ |
|||||
|
<script> |
||||
|
export let columnName |
||||
|
export let row |
||||
|
export let selectRelationship |
||||
|
|
||||
|
$: count = |
||||
|
row && columnName && Array.isArray(row[columnName]) |
||||
|
? row[columnName].length |
||||
|
: 0 |
||||
|
</script> |
||||
|
|
||||
|
<div class:link={count} on:click={() => selectRelationship(row, columnName)}> |
||||
|
{count} |
||||
|
related row(s) |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.link { |
||||
|
text-decoration: underline; |
||||
|
} |
||||
|
|
||||
|
.link:hover { |
||||
|
color: var(--grey-6); |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,79 @@ |
|||||
|
import AttachmentList from "./AttachmentCell.svelte" |
||||
|
import EditRow from "../modals/EditRow.svelte" |
||||
|
import DeleteRow from "../modals/DeleteRow.svelte" |
||||
|
import RelationshipDisplay from "./RelationshipCell.svelte" |
||||
|
|
||||
|
const renderers = { |
||||
|
attachment: attachmentRenderer, |
||||
|
link: linkedRowRenderer, |
||||
|
} |
||||
|
|
||||
|
export function getRenderer(schema, editable) { |
||||
|
if (renderers[schema.type]) { |
||||
|
return renderers[schema.type](schema.options, schema.constraints, editable) |
||||
|
} else { |
||||
|
return false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
export function deleteRowRenderer(params) { |
||||
|
const container = document.createElement("div") |
||||
|
|
||||
|
new DeleteRow({ |
||||
|
target: container, |
||||
|
props: { |
||||
|
row: params.data, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
return container |
||||
|
} |
||||
|
|
||||
|
export function editRowRenderer(params) { |
||||
|
const container = document.createElement("div") |
||||
|
|
||||
|
new EditRow({ |
||||
|
target: container, |
||||
|
props: { |
||||
|
row: params.data, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
return container |
||||
|
} |
||||
|
|
||||
|
/* eslint-disable no-unused-vars */ |
||||
|
function attachmentRenderer(options, constraints, editable) { |
||||
|
return params => { |
||||
|
const container = document.createElement("div") |
||||
|
|
||||
|
const attachmentInstance = new AttachmentList({ |
||||
|
target: container, |
||||
|
props: { |
||||
|
files: params.value || [], |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
return container |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/* eslint-disable no-unused-vars */ |
||||
|
function linkedRowRenderer() { |
||||
|
return params => { |
||||
|
let container = document.createElement("div") |
||||
|
container.style.display = "grid" |
||||
|
container.style.height = "100%" |
||||
|
|
||||
|
new RelationshipDisplay({ |
||||
|
target: container, |
||||
|
props: { |
||||
|
row: params.data, |
||||
|
columnName: params.column.colId, |
||||
|
selectRelationship: params.selectRelationship, |
||||
|
}, |
||||
|
}) |
||||
|
|
||||
|
return container |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,42 @@ |
|||||
|
<script> |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import { DropdownMenu, Icon, Modal } from "@budibase/bbui" |
||||
|
import * as api from "../api" |
||||
|
import { notifier } from "builderStore/store/notifications" |
||||
|
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
||||
|
|
||||
|
export let row |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
let confirmDeleteDialog |
||||
|
|
||||
|
function showDelete() { |
||||
|
confirmDeleteDialog.show() |
||||
|
} |
||||
|
|
||||
|
async function deleteRow() { |
||||
|
await api.deleteRow(row) |
||||
|
notifier.success("Row deleted") |
||||
|
backendUiStore.actions.rows.delete(row) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<div on:click={showDelete}><i class="ri-delete-row" /></div> |
||||
|
<ConfirmDialog |
||||
|
bind:this={confirmDeleteDialog} |
||||
|
body={`Are you sure you wish to delete this row? Your data will be deleted and this action cannot be undone.`} |
||||
|
okText="Delete Row" |
||||
|
onOk={deleteRow} |
||||
|
title="Confirm Delete" /> |
||||
|
|
||||
|
<style> |
||||
|
.ri-delete-bin-line:hover { |
||||
|
cursor: pointer; |
||||
|
} |
||||
|
|
||||
|
div { |
||||
|
display: flex; |
||||
|
justify-content: center; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,22 @@ |
|||||
|
<script> |
||||
|
import { backendUiStore } from "builderStore" |
||||
|
import { DropdownMenu, Icon, Modal, Button } from "@budibase/bbui" |
||||
|
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte" |
||||
|
|
||||
|
export let row |
||||
|
|
||||
|
let anchor |
||||
|
let dropdown |
||||
|
let confirmDeleteDialog |
||||
|
let modal |
||||
|
|
||||
|
function showModal(e) { |
||||
|
e.stopPropagation() |
||||
|
modal.show() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Button translucent small on:click={showModal}>Edit</Button> |
||||
|
<Modal bind:this={modal}> |
||||
|
<CreateEditRowModal {row} /> |
||||
|
</Modal> |
||||
@ -1,145 +0,0 @@ |
|||||
<script> |
|
||||
import { backendUiStore } from "builderStore" |
|
||||
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" |
|
||||
import { FIELDS } from "constants/backend" |
|
||||
import CreateEditColumnPopover from "./CreateEditColumnPopover.svelte" |
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|
||||
import { notifier } from "../../../../builderStore/store/notifications" |
|
||||
|
|
||||
export let field |
|
||||
|
|
||||
let anchor |
|
||||
let dropdown |
|
||||
let editing |
|
||||
let confirmDeleteDialog |
|
||||
|
|
||||
$: sortColumn = $backendUiStore.sort && $backendUiStore.sort.column |
|
||||
$: sortDirection = $backendUiStore.sort && $backendUiStore.sort.direction |
|
||||
$: type = field?.type |
|
||||
|
|
||||
function showEditor() { |
|
||||
editing = true |
|
||||
} |
|
||||
|
|
||||
function hideEditor() { |
|
||||
dropdown.hide() |
|
||||
editing = false |
|
||||
} |
|
||||
|
|
||||
function showDelete() { |
|
||||
dropdown.hide() |
|
||||
confirmDeleteDialog.show() |
|
||||
} |
|
||||
|
|
||||
function deleteColumn() { |
|
||||
if (field.name === $backendUiStore.selectedTable.primaryDisplay) { |
|
||||
notifier.danger("You cannot delete the display column") |
|
||||
} else { |
|
||||
backendUiStore.actions.tables.deleteField(field) |
|
||||
notifier.success("Column deleted") |
|
||||
} |
|
||||
hideEditor() |
|
||||
} |
|
||||
|
|
||||
function sort(direction, column) { |
|
||||
backendUiStore.update(state => { |
|
||||
if (direction !== "none") { |
|
||||
state.sort = { direction, column } |
|
||||
} else { |
|
||||
state.sort = undefined |
|
||||
} |
|
||||
return state |
|
||||
}) |
|
||||
hideEditor() |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div class="container" bind:this={anchor} on:click={dropdown.show}> |
|
||||
<span>{field.name}</span> |
|
||||
<Icon name="arrowdown" /> |
|
||||
</div> |
|
||||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|
||||
{#if editing} |
|
||||
<h5>Edit Column</h5> |
|
||||
<CreateEditColumnPopover onClosed={hideEditor} {field} /> |
|
||||
{:else} |
|
||||
<ul> |
|
||||
{#if type !== 'link'} |
|
||||
<li data-cy="edit-column-header" on:click={showEditor}> |
|
||||
<Icon name="edit" /> |
|
||||
Edit |
|
||||
</li> |
|
||||
{/if} |
|
||||
<li data-cy="delete-column-header" on:click={showDelete}> |
|
||||
<Icon name="delete" /> |
|
||||
Delete |
|
||||
</li> |
|
||||
{#if sortDirection === 'desc' || sortDirection === 'asc'} |
|
||||
<li on:click={() => sort('none', field.name)}> |
|
||||
<Icon name="close" /> |
|
||||
Remove sort |
|
||||
</li> |
|
||||
{/if} |
|
||||
{#if sortDirection === 'desc' || sortColumn !== field.name} |
|
||||
<li on:click={() => sort('asc', field.name)}> |
|
||||
<Icon name="sortascending" /> |
|
||||
Sort A - Z |
|
||||
</li> |
|
||||
{/if} |
|
||||
{#if sortDirection === 'asc' || sortColumn !== field.name} |
|
||||
<li on:click={() => sort('desc', field.name)}> |
|
||||
<Icon name="sortdescending" /> |
|
||||
Sort Z - A |
|
||||
</li> |
|
||||
{/if} |
|
||||
</ul> |
|
||||
{/if} |
|
||||
</DropdownMenu> |
|
||||
<ConfirmDialog |
|
||||
bind:this={confirmDeleteDialog} |
|
||||
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`} |
|
||||
okText="Delete Column" |
|
||||
onOk={deleteColumn} |
|
||||
title="Confirm Delete" /> |
|
||||
|
|
||||
<style> |
|
||||
.container { |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
justify-content: flex-start; |
|
||||
align-items: center; |
|
||||
gap: var(--spacing-xs); |
|
||||
} |
|
||||
|
|
||||
h5 { |
|
||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|
||||
margin: 0; |
|
||||
font-weight: 500; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
list-style: none; |
|
||||
padding-left: 0; |
|
||||
margin: 0; |
|
||||
padding: var(--spacing-s) 0; |
|
||||
} |
|
||||
|
|
||||
li { |
|
||||
display: flex; |
|
||||
font-family: var(--font-sans); |
|
||||
font-size: var(--font-size-xs); |
|
||||
color: var(--ink); |
|
||||
padding: var(--spacing-s) var(--spacing-m); |
|
||||
margin: auto 0px; |
|
||||
align-items: center; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
li:hover { |
|
||||
background-color: var(--grey-2); |
|
||||
} |
|
||||
|
|
||||
li:active { |
|
||||
color: var(--blue); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,94 +0,0 @@ |
|||||
<script> |
|
||||
import { backendUiStore } from "builderStore" |
|
||||
import { DropdownMenu, Icon, Modal } from "@budibase/bbui" |
|
||||
import CreateEditRowModal from "../modals/CreateEditRowModal.svelte" |
|
||||
import * as api from "../api" |
|
||||
import { notifier } from "builderStore/store/notifications" |
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|
||||
|
|
||||
export let row |
|
||||
|
|
||||
let anchor |
|
||||
let dropdown |
|
||||
let confirmDeleteDialog |
|
||||
let modal |
|
||||
|
|
||||
function showModal() { |
|
||||
dropdown.hide() |
|
||||
modal.show() |
|
||||
} |
|
||||
|
|
||||
function showDelete() { |
|
||||
dropdown.hide() |
|
||||
confirmDeleteDialog.show() |
|
||||
} |
|
||||
|
|
||||
async function deleteRow() { |
|
||||
await api.deleteRow(row) |
|
||||
notifier.success("Row deleted") |
|
||||
backendUiStore.actions.rows.delete(row) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<div bind:this={anchor} on:click={dropdown.show}> |
|
||||
<i class="ri-more-line" /> |
|
||||
</div> |
|
||||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|
||||
<ul> |
|
||||
<li data-cy="edit-row" on:click={showModal}> |
|
||||
<Icon name="edit" /> |
|
||||
<span>Edit</span> |
|
||||
</li> |
|
||||
<li data-cy="delete-row" on:click={showDelete}> |
|
||||
<Icon name="delete" /> |
|
||||
<span>Delete</span> |
|
||||
</li> |
|
||||
</ul> |
|
||||
</DropdownMenu> |
|
||||
<ConfirmDialog |
|
||||
bind:this={confirmDeleteDialog} |
|
||||
body={`Are you sure you wish to delete this row? Your data will be deleted and this action cannot be undone.`} |
|
||||
okText="Delete Row" |
|
||||
onOk={deleteRow} |
|
||||
title="Confirm Delete" /> |
|
||||
<Modal bind:this={modal}> |
|
||||
<CreateEditRowModal {row} /> |
|
||||
</Modal> |
|
||||
|
|
||||
<style> |
|
||||
.ri-more-line:hover { |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
h5 { |
|
||||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|
||||
margin: 0; |
|
||||
font-weight: 500; |
|
||||
} |
|
||||
|
|
||||
ul { |
|
||||
list-style: none; |
|
||||
padding-left: 0; |
|
||||
margin: 0; |
|
||||
padding: var(--spacing-s) 0; |
|
||||
} |
|
||||
|
|
||||
li { |
|
||||
display: flex; |
|
||||
font-family: var(--font-sans); |
|
||||
color: var(--ink); |
|
||||
padding: var(--spacing-s) var(--spacing-m); |
|
||||
margin: auto 0px; |
|
||||
align-items: center; |
|
||||
cursor: pointer; |
|
||||
font-size: var(--font-size-xs); |
|
||||
} |
|
||||
|
|
||||
li:hover { |
|
||||
background-color: var(--grey-2); |
|
||||
} |
|
||||
|
|
||||
li:active { |
|
||||
color: var(--blue); |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,6 +1,6 @@ |
|||||
<script> |
<script> |
||||
import { goto } from "@sveltech/routify" |
import { goto } from "@sveltech/routify" |
||||
$goto("../backend") |
$goto("../data") |
||||
</script> |
</script> |
||||
|
|
||||
<!-- routify:options index=false --> |
<!-- routify:options index=false --> |
||||
|
|||||
Binary file not shown.
Binary file not shown.
@ -1,201 +0,0 @@ |
|||||
<script> |
|
||||
import { onMount } from "svelte" |
|
||||
import { cssVars } from "./cssVars" |
|
||||
import ArrowUp from "./icons/ArrowUp.svelte" |
|
||||
import ArrowDown from "./icons/ArrowDown.svelte" |
|
||||
import fsort from "fast-sort" |
|
||||
import fetchData from "./fetchData.js" |
|
||||
import { isEmpty } from "lodash/fp" |
|
||||
import AttachmentList from "./attachments/AttachmentList.svelte" |
|
||||
|
|
||||
export let backgroundColor |
|
||||
export let color |
|
||||
export let stripeColor |
|
||||
export let borderColor |
|
||||
export let datasource |
|
||||
export let _bb |
|
||||
|
|
||||
let data = [] |
|
||||
let headers = [] |
|
||||
let sort = {} |
|
||||
let sorted = [] |
|
||||
let schema = {} |
|
||||
let store = _bb.store |
|
||||
|
|
||||
$: cssVariables = { |
|
||||
backgroundColor, |
|
||||
color, |
|
||||
stripeColor, |
|
||||
borderColor, |
|
||||
} |
|
||||
|
|
||||
$: sorted = sort.direction ? fsort(data)[sort.direction](sort.column) : data |
|
||||
|
|
||||
async function fetchTable(tableId) { |
|
||||
const FETCH_TABLE_URL = `/api/tables/${tableId}` |
|
||||
const response = await _bb.api.get(FETCH_TABLE_URL) |
|
||||
const table = await response.json() |
|
||||
return table.schema |
|
||||
} |
|
||||
|
|
||||
onMount(async () => { |
|
||||
if (!isEmpty(datasource)) { |
|
||||
data = await fetchData(datasource, $store) |
|
||||
|
|
||||
// Get schema for datasource |
|
||||
// Views with "Calculate" applied provide their own schema. |
|
||||
// For everything else, use the tableId property to pull to table schema |
|
||||
if (datasource.schema) { |
|
||||
schema = datasource.schema |
|
||||
headers = Object.keys(schema).filter(shouldDisplayField) |
|
||||
} else { |
|
||||
schema = await fetchTable(datasource.tableId) |
|
||||
headers = Object.keys(schema).filter(shouldDisplayField) |
|
||||
} |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
const shouldDisplayField = name => { |
|
||||
if (name.startsWith("_")) return false |
|
||||
// always 'row' |
|
||||
if (name === "type") return false |
|
||||
// tables are always tied to a single tableId, this is irrelevant |
|
||||
if (name === "tableId") return false |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
function sortColumn(column) { |
|
||||
if (column === sort.column) { |
|
||||
sort = { |
|
||||
direction: sort.direction === "asc" ? "desc" : null, |
|
||||
column: sort.direction === "asc" ? sort.column : null, |
|
||||
} |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
sort = { |
|
||||
column, |
|
||||
direction: "asc", |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<table use:cssVars={cssVariables}> |
|
||||
<thead> |
|
||||
<tr> |
|
||||
{#each headers as header} |
|
||||
<th on:click={() => sortColumn(header)}> |
|
||||
<span> |
|
||||
{header} |
|
||||
{#if sort.column === header} |
|
||||
<svelte:component |
|
||||
this={sort.direction === 'asc' ? ArrowDown : ArrowUp} |
|
||||
style="height: 1em;" /> |
|
||||
{/if} |
|
||||
</span> |
|
||||
</th> |
|
||||
{/each} |
|
||||
</tr> |
|
||||
</thead> |
|
||||
<tbody> |
|
||||
{#each sorted as row (row._id)} |
|
||||
<tr> |
|
||||
{#each headers as header} |
|
||||
{#if schema[header] !== undefined} |
|
||||
<!-- Rudimentary solution for attachments on array given this entire table will be replaced by AG Grid --> |
|
||||
{#if schema[header] && schema[header].type === 'attachment'} |
|
||||
<AttachmentList files={row[header]} /> |
|
||||
{:else if schema[header] && schema[header].type === 'link'} |
|
||||
<td>{row[header] ? row[header].length : 0} related row(s)</td> |
|
||||
{:else} |
|
||||
<td>{row[header] == null ? '' : row[header]}</td> |
|
||||
{/if} |
|
||||
{/if} |
|
||||
{/each} |
|
||||
</tr> |
|
||||
{/each} |
|
||||
</tbody> |
|
||||
</table> |
|
||||
|
|
||||
<style> |
|
||||
table { |
|
||||
width: 100%; |
|
||||
border-collapse: collapse; |
|
||||
overflow: scroll; /* Scrollbar are always visible */ |
|
||||
overflow: auto; /* Scrollbar is displayed as it's needed */ |
|
||||
} |
|
||||
|
|
||||
/* Zebra striping */ |
|
||||
tr:nth-of-type(odd) { |
|
||||
background: var(--stripeColor); |
|
||||
} |
|
||||
|
|
||||
th { |
|
||||
background-color: var(--backgroundColor); |
|
||||
color: var(--color); |
|
||||
font-weight: bold; |
|
||||
text-transform: capitalize; |
|
||||
cursor: pointer; |
|
||||
} |
|
||||
|
|
||||
th span { |
|
||||
display: flex; |
|
||||
align-items: center; |
|
||||
} |
|
||||
|
|
||||
td, |
|
||||
th { |
|
||||
padding: 16px; |
|
||||
border: 1px solid var(--borderColor); |
|
||||
text-align: left; |
|
||||
} |
|
||||
|
|
||||
@media only screen and (max-width: 760px), |
|
||||
(min-device-width: 768px) and (max-device-width: 1024px) { |
|
||||
table { |
|
||||
width: 100%; |
|
||||
} |
|
||||
|
|
||||
/* Force table to not be like tables anymore */ |
|
||||
table, |
|
||||
thead, |
|
||||
tbody, |
|
||||
th, |
|
||||
td, |
|
||||
tr { |
|
||||
display: block; |
|
||||
} |
|
||||
|
|
||||
/* Hide table headers (but not display: none;, for accessibility) */ |
|
||||
thead tr { |
|
||||
position: absolute; |
|
||||
top: -9999px; |
|
||||
left: -9999px; |
|
||||
} |
|
||||
|
|
||||
tr { |
|
||||
border: 1px solid var(--borderColor); |
|
||||
} |
|
||||
|
|
||||
td { |
|
||||
/* Behave like a "row" */ |
|
||||
border: none; |
|
||||
border-bottom: 1px solid #eee; |
|
||||
position: relative; |
|
||||
padding-left: 10%; |
|
||||
} |
|
||||
|
|
||||
td:before { |
|
||||
/* Now like a table header */ |
|
||||
position: absolute; |
|
||||
/* Top/left values mimic padding */ |
|
||||
top: 6px; |
|
||||
left: 6px; |
|
||||
width: 45%; |
|
||||
padding-right: 10px; |
|
||||
white-space: nowrap; |
|
||||
/* Label the data */ |
|
||||
content: attr(data-column); |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
Loading…
Reference in new issue