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> |
|||
import { DropdownMenu, TextButton as Button, Icon } from "@budibase/bbui" |
|||
import CreateEditColumnPopover from "../popovers/CreateEditColumnPopover.svelte" |
|||
import { |
|||
DropdownMenu, |
|||
TextButton as Button, |
|||
Icon, |
|||
Modal, |
|||
ModalContent, |
|||
} from "@budibase/bbui" |
|||
import CreateEditColumn from "../modals/CreateEditColumn.svelte" |
|||
|
|||
let anchor |
|||
let dropdown |
|||
let modal |
|||
let fieldName |
|||
</script> |
|||
|
|||
<div bind:this={anchor}> |
|||
<Button text small on:click={dropdown.show}> |
|||
<div> |
|||
<Button text small on:click={modal.show}> |
|||
<Icon name="addcolumn" /> |
|||
Create New Column |
|||
</Button> |
|||
</div> |
|||
<DropdownMenu bind:this={dropdown} {anchor} align="left"> |
|||
<h5>Create Column</h5> |
|||
<CreateEditColumnPopover onClosed={dropdown.hide} /> |
|||
</DropdownMenu> |
|||
|
|||
<style> |
|||
h5 { |
|||
padding: var(--spacing-xl) 0 0 var(--spacing-xl); |
|||
margin: 0; |
|||
font-weight: 500; |
|||
} |
|||
</style> |
|||
<Modal bind:this={modal}> |
|||
<ModalContent |
|||
showCancelButton={false} |
|||
showConfirmButton={false} |
|||
title={'Create Column'}> |
|||
<CreateEditColumn onClosed={modal.hide} /> |
|||
</ModalContent> |
|||
</Modal> |
|||
|
|||
@ -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> |
|||
import { goto } from "@sveltech/routify" |
|||
$goto("../backend") |
|||
$goto("../data") |
|||
</script> |
|||
|
|||
<!-- 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