forked from tsai/budibase
158 changed files with 5025 additions and 1247 deletions
@ -0,0 +1,54 @@ |
|||||
|
<script> |
||||
|
import { ActionButton, Modal, notifications } from "@budibase/bbui" |
||||
|
import CreateEditRelationship from "../../Datasources/CreateEditRelationship.svelte" |
||||
|
import { datasources, tables } from "../../../../stores/backend" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
export let table |
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
$: plusTables = datasource?.plus |
||||
|
? Object.values(datasource?.entities || {}) |
||||
|
: [] |
||||
|
$: datasource = $datasources.list.find( |
||||
|
source => source._id === table?.sourceId |
||||
|
) |
||||
|
|
||||
|
let modal |
||||
|
|
||||
|
async function saveRelationship() { |
||||
|
try { |
||||
|
// Create datasource |
||||
|
await datasources.save(datasource) |
||||
|
notifications.success(`Relationship information saved.`) |
||||
|
const tableList = await tables.fetch() |
||||
|
await tables.select(tableList.find(tbl => tbl._id === table._id)) |
||||
|
dispatch("updatecolumns") |
||||
|
} catch (err) { |
||||
|
notifications.error(`Error saving relationship info: ${err}`) |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
{#if table.sourceId} |
||||
|
<div> |
||||
|
<ActionButton |
||||
|
icon="DataCorrelated" |
||||
|
primary |
||||
|
size="S" |
||||
|
quiet |
||||
|
on:click={modal.show} |
||||
|
> |
||||
|
Define existing relationship |
||||
|
</ActionButton> |
||||
|
</div> |
||||
|
<Modal bind:this={modal}> |
||||
|
<CreateEditRelationship |
||||
|
{datasource} |
||||
|
save={saveRelationship} |
||||
|
close={modal.hide} |
||||
|
{plusTables} |
||||
|
selectedFromTable={table} |
||||
|
/> |
||||
|
</Modal> |
||||
|
{/if} |
||||
@ -0,0 +1,15 @@ |
|||||
|
<script> |
||||
|
import { ActionButton, Modal } from "@budibase/bbui" |
||||
|
import ImportModal from "../modals/ImportModal.svelte" |
||||
|
|
||||
|
export let tableId |
||||
|
|
||||
|
let modal |
||||
|
</script> |
||||
|
|
||||
|
<ActionButton icon="DataUpload" size="S" quiet on:click={modal.show}> |
||||
|
Import |
||||
|
</ActionButton> |
||||
|
<Modal bind:this={modal}> |
||||
|
<ImportModal {tableId} on:updaterows /> |
||||
|
</Modal> |
||||
@ -0,0 +1,43 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Label, notifications, Body } from "@budibase/bbui" |
||||
|
import TableDataImport from "../../TableNavigator/TableDataImport.svelte" |
||||
|
import api from "builderStore/api" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
export let tableId |
||||
|
let dataImport |
||||
|
|
||||
|
$: valid = dataImport?.csvString != null && dataImport?.valid |
||||
|
|
||||
|
async function importData() { |
||||
|
const response = await api.post(`/api/tables/${tableId}/import`, { |
||||
|
dataImport, |
||||
|
}) |
||||
|
if (response.status !== 200) { |
||||
|
const error = await response.text() |
||||
|
notifications.error(`Unable to import data - ${error}`) |
||||
|
} else { |
||||
|
notifications.success("Rows successfully imported.") |
||||
|
} |
||||
|
dispatch("updaterows") |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
title="Import Data" |
||||
|
confirmText="Import" |
||||
|
onConfirm={importData} |
||||
|
disabled={!valid} |
||||
|
> |
||||
|
<Body |
||||
|
>Import rows to an existing table from a CSV. Only columns from the CSV |
||||
|
which exist in the table will be imported.</Body |
||||
|
> |
||||
|
<Label grey extraSmall>CSV to import</Label> |
||||
|
<TableDataImport bind:dataImport bind:existingTableId={tableId} /> |
||||
|
</ModalContent> |
||||
|
|
||||
|
<style> |
||||
|
</style> |
||||
@ -1,15 +0,0 @@ |
|||||
<script> |
|
||||
import { Input } from "@budibase/bbui" |
|
||||
import { isJSBinding } from "@budibase/string-templates" |
|
||||
|
|
||||
export let value |
|
||||
|
|
||||
$: isJS = isJSBinding(value) |
|
||||
</script> |
|
||||
|
|
||||
<Input |
|
||||
{...$$props} |
|
||||
value={isJS ? "(JavaScript function)" : value} |
|
||||
readonly={isJS} |
|
||||
on:change |
|
||||
/> |
|
||||
@ -0,0 +1,45 @@ |
|||||
|
<script> |
||||
|
import { ModalContent, Body, Input } from "@budibase/bbui" |
||||
|
import { tables, datasources } from "stores/backend" |
||||
|
import { goto } from "@roxi/routify" |
||||
|
|
||||
|
export let datasource |
||||
|
|
||||
|
let name = "" |
||||
|
$: valid = name && name.length > 0 && !datasource?.entities[name] |
||||
|
$: error = |
||||
|
name && datasource?.entities[name] ? "Table name already in use." : null |
||||
|
|
||||
|
function buildDefaultTable(tableName, datasourceId) { |
||||
|
return { |
||||
|
name: tableName, |
||||
|
type: "external", |
||||
|
primary: ["id"], |
||||
|
sourceId: datasourceId, |
||||
|
schema: { |
||||
|
id: { |
||||
|
autocolumn: true, |
||||
|
type: "number", |
||||
|
}, |
||||
|
}, |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function saveTable() { |
||||
|
const table = await tables.save(buildDefaultTable(name, datasource._id)) |
||||
|
await datasources.fetch() |
||||
|
$goto(`../../table/${table._id}`) |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<ModalContent |
||||
|
title="Create new table" |
||||
|
confirmText="Create" |
||||
|
onConfirm={saveTable} |
||||
|
disabled={!valid} |
||||
|
> |
||||
|
<Body |
||||
|
>Provide a name for your new table; you can add columns once it is created.</Body |
||||
|
> |
||||
|
<Input label="Table Name" bind:error bind:value={name} /> |
||||
|
</ModalContent> |
||||
@ -1,43 +0,0 @@ |
|||||
<script> |
|
||||
import { ModalContent, Select, Body } from "@budibase/bbui" |
|
||||
import { tables } from "stores/backend" |
|
||||
|
|
||||
export let datasource |
|
||||
export let plusTables |
|
||||
export let save |
|
||||
|
|
||||
async function saveDisplayColumns() { |
|
||||
// be explicit about copying over |
|
||||
for (let table of plusTables) { |
|
||||
datasource.entities[table.name].primaryDisplay = table.primaryDisplay |
|
||||
} |
|
||||
save() |
|
||||
await tables.fetch() |
|
||||
} |
|
||||
|
|
||||
function getColumnOptions(table) { |
|
||||
if (!table || !table.schema) { |
|
||||
return [] |
|
||||
} |
|
||||
return Object.entries(table.schema) |
|
||||
.filter(field => field[1].type !== "link") |
|
||||
.map(([fieldName]) => fieldName) |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<ModalContent |
|
||||
title="Edit display columns" |
|
||||
confirmText="Save" |
|
||||
onConfirm={saveDisplayColumns} |
|
||||
> |
|
||||
<Body |
|
||||
>Select the columns that will be shown when displaying relationships.</Body |
|
||||
> |
|
||||
{#each plusTables as table} |
|
||||
<Select |
|
||||
label={table.name} |
|
||||
options={getColumnOptions(table)} |
|
||||
bind:value={table.primaryDisplay} |
|
||||
/> |
|
||||
{/each} |
|
||||
</ModalContent> |
|
||||
@ -0,0 +1,12 @@ |
|||||
|
<script> |
||||
|
import { getContext, setContext } from "svelte" |
||||
|
|
||||
|
const component = getContext("component") |
||||
|
|
||||
|
// We need to set a block context to know we're inside a block, but also |
||||
|
// to be able to reference the actual component ID of the block from |
||||
|
// any depth |
||||
|
setContext("block", { id: $component.id }) |
||||
|
</script> |
||||
|
|
||||
|
<slot /> |
||||
@ -0,0 +1,35 @@ |
|||||
|
<script> |
||||
|
import { getContext } from "svelte" |
||||
|
import { generate } from "shortid" |
||||
|
import Component from "components/Component.svelte" |
||||
|
|
||||
|
export let type |
||||
|
export let props |
||||
|
export let styles |
||||
|
export let context |
||||
|
|
||||
|
// ID is only exposed as a prop so that it can be bound to from parent |
||||
|
// block components |
||||
|
export let id |
||||
|
|
||||
|
const block = getContext("block") |
||||
|
const rand = generate() |
||||
|
|
||||
|
// Create a fake component instance so that we can use the core Component |
||||
|
// to render this part of the block, taking advantage of binding enrichment |
||||
|
$: id = `${block.id}-${context ?? rand}` |
||||
|
$: instance = { |
||||
|
_component: `@budibase/standard-components/${type}`, |
||||
|
_id: id, |
||||
|
_styles: { |
||||
|
normal: { |
||||
|
...styles, |
||||
|
}, |
||||
|
}, |
||||
|
...props, |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Component {instance}> |
||||
|
<slot /> |
||||
|
</Component> |
||||
@ -0,0 +1,272 @@ |
|||||
|
<script> |
||||
|
import { onMount, getContext } from "svelte" |
||||
|
import Block from "components/Block.svelte" |
||||
|
import BlockComponent from "components/BlockComponent.svelte" |
||||
|
import { Heading } from "@budibase/bbui" |
||||
|
|
||||
|
export let title |
||||
|
export let dataSource |
||||
|
export let searchColumns |
||||
|
export let filter |
||||
|
export let sortColumn |
||||
|
export let sortOrder |
||||
|
export let paginate |
||||
|
export let limit |
||||
|
export let showTitleButton |
||||
|
export let titleButtonText |
||||
|
export let titleButtonURL |
||||
|
export let titleButtonPeek |
||||
|
export let cardTitle |
||||
|
export let cardSubtitle |
||||
|
export let cardDescription |
||||
|
export let cardImageURL |
||||
|
export let linkCardTitle |
||||
|
export let cardURL |
||||
|
export let cardPeek |
||||
|
export let cardHorizontal |
||||
|
export let showCardButton |
||||
|
export let cardButtonText |
||||
|
export let cardButtonOnClick |
||||
|
export let linkColumn |
||||
|
|
||||
|
const { API, styleable } = getContext("sdk") |
||||
|
const context = getContext("context") |
||||
|
const component = getContext("component") |
||||
|
const schemaComponentMap = { |
||||
|
string: "stringfield", |
||||
|
options: "optionsfield", |
||||
|
number: "numberfield", |
||||
|
datetime: "datetimefield", |
||||
|
boolean: "booleanfield", |
||||
|
} |
||||
|
|
||||
|
let formId |
||||
|
let dataProviderId |
||||
|
let repeaterId |
||||
|
let schema |
||||
|
|
||||
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema) |
||||
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId) |
||||
|
$: cardWidth = cardHorizontal ? 420 : 300 |
||||
|
$: fullCardURL = buildFullCardUrl( |
||||
|
linkCardTitle, |
||||
|
cardURL, |
||||
|
repeaterId, |
||||
|
linkColumn |
||||
|
) |
||||
|
$: titleButtonAction = [ |
||||
|
{ |
||||
|
"##eventHandlerType": "Navigate To", |
||||
|
parameters: { |
||||
|
peek: titleButtonPeek, |
||||
|
url: titleButtonURL, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
// Enrich the default filter with the specified search fields |
||||
|
const enrichFilter = (filter, columns, formId) => { |
||||
|
let enrichedFilter = [...(filter || [])] |
||||
|
columns?.forEach(column => { |
||||
|
enrichedFilter.push({ |
||||
|
field: column.name, |
||||
|
operator: column.type === "string" ? "string" : "equal", |
||||
|
type: "string", |
||||
|
valueType: "Binding", |
||||
|
value: `{{ [${formId}].[${column.name}] }}`, |
||||
|
}) |
||||
|
}) |
||||
|
return enrichedFilter |
||||
|
} |
||||
|
|
||||
|
// Determine data types for search fields and only use those that are valid |
||||
|
const enrichSearchColumns = (searchColumns, schema) => { |
||||
|
let enrichedColumns = [] |
||||
|
searchColumns?.forEach(column => { |
||||
|
const schemaType = schema?.[column]?.type |
||||
|
const componentType = schemaComponentMap[schemaType] |
||||
|
if (componentType) { |
||||
|
enrichedColumns.push({ |
||||
|
name: column, |
||||
|
componentType, |
||||
|
type: schemaType, |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return enrichedColumns.slice(0, 3) |
||||
|
} |
||||
|
|
||||
|
// Builds a full details page URL for the card title |
||||
|
const buildFullCardUrl = (link, url, repeaterId, linkColumn) => { |
||||
|
if (!link || !url || !repeaterId) { |
||||
|
return null |
||||
|
} |
||||
|
const col = linkColumn || "_id" |
||||
|
const split = url.split("/:") |
||||
|
return `${split[0]}/{{ [${repeaterId}].[${col}] }}` |
||||
|
} |
||||
|
|
||||
|
// Load the datasource schema on mount so we can determine column types |
||||
|
onMount(async () => { |
||||
|
if (dataSource) { |
||||
|
schema = await API.fetchDatasourceSchema(dataSource) |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<Block> |
||||
|
<div class="card-list" use:styleable={$component.styles}> |
||||
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}> |
||||
|
{#if title || enrichedSearchColumns?.length || showTitleButton} |
||||
|
<div class="header" class:mobile={$context.device.mobile}> |
||||
|
<div class="title"> |
||||
|
<Heading>{title || ""}</Heading> |
||||
|
</div> |
||||
|
<div class="controls"> |
||||
|
{#if enrichedSearchColumns?.length} |
||||
|
<div |
||||
|
class="search" |
||||
|
style="--cols:{enrichedSearchColumns?.length}" |
||||
|
> |
||||
|
{#each enrichedSearchColumns as column} |
||||
|
<BlockComponent |
||||
|
type={column.componentType} |
||||
|
props={{ |
||||
|
field: column.name, |
||||
|
placeholder: column.name, |
||||
|
text: column.name, |
||||
|
autoWidth: true, |
||||
|
}} |
||||
|
/> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
{#if showTitleButton} |
||||
|
<BlockComponent |
||||
|
type="button" |
||||
|
props={{ |
||||
|
onClick: titleButtonAction, |
||||
|
text: titleButtonText, |
||||
|
type: "cta", |
||||
|
}} |
||||
|
/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
{/if} |
||||
|
<BlockComponent |
||||
|
type="dataprovider" |
||||
|
bind:id={dataProviderId} |
||||
|
props={{ |
||||
|
dataSource, |
||||
|
filter: enrichedFilter, |
||||
|
sortColumn, |
||||
|
sortOrder, |
||||
|
paginate, |
||||
|
limit, |
||||
|
}} |
||||
|
> |
||||
|
<BlockComponent |
||||
|
type="repeater" |
||||
|
bind:id={repeaterId} |
||||
|
context="repeater" |
||||
|
props={{ |
||||
|
dataProvider: `{{ literal [${dataProviderId}] }}`, |
||||
|
direction: "row", |
||||
|
hAlign: "stretch", |
||||
|
vAlign: "top", |
||||
|
gap: "M", |
||||
|
noRowsMessage: "No rows found", |
||||
|
}} |
||||
|
styles={{ |
||||
|
display: "grid", |
||||
|
"grid-template-columns": `repeat(auto-fill, minmax(${cardWidth}px, 1fr))`, |
||||
|
}} |
||||
|
> |
||||
|
<BlockComponent |
||||
|
type="spectrumcard" |
||||
|
props={{ |
||||
|
title: cardTitle, |
||||
|
subtitle: cardSubtitle, |
||||
|
description: cardDescription, |
||||
|
imageURL: cardImageURL, |
||||
|
horizontal: cardHorizontal, |
||||
|
showButton: showCardButton, |
||||
|
buttonText: cardButtonText, |
||||
|
buttonOnClick: cardButtonOnClick, |
||||
|
linkURL: fullCardURL, |
||||
|
linkPeek: cardPeek, |
||||
|
}} |
||||
|
styles={{ |
||||
|
width: "auto", |
||||
|
}} |
||||
|
/> |
||||
|
</BlockComponent> |
||||
|
</BlockComponent> |
||||
|
</BlockComponent> |
||||
|
</div> |
||||
|
</Block> |
||||
|
|
||||
|
<style> |
||||
|
.header { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.title :global(.spectrum-Heading) { |
||||
|
flex: 1 1 auto; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.controls { |
||||
|
flex: 0 1 auto; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.search { |
||||
|
flex: 0 1 auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(var(--cols), minmax(120px, 200px)); |
||||
|
} |
||||
|
.search :global(.spectrum-InputGroup) { |
||||
|
min-width: 0; |
||||
|
} |
||||
|
|
||||
|
/* Mobile styles */ |
||||
|
.mobile { |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
.mobile .controls { |
||||
|
flex-direction: column-reverse; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
.mobile .search { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,229 @@ |
|||||
|
<script> |
||||
|
import { onMount, getContext } from "svelte" |
||||
|
import Block from "components/Block.svelte" |
||||
|
import BlockComponent from "components/BlockComponent.svelte" |
||||
|
import { Heading } from "@budibase/bbui" |
||||
|
|
||||
|
export let title |
||||
|
export let dataSource |
||||
|
export let searchColumns |
||||
|
export let filter |
||||
|
export let sortColumn |
||||
|
export let sortOrder |
||||
|
export let paginate |
||||
|
export let tableColumns |
||||
|
export let showAutoColumns |
||||
|
export let rowCount |
||||
|
export let quiet |
||||
|
export let size |
||||
|
export let linkRows |
||||
|
export let linkURL |
||||
|
export let linkColumn |
||||
|
export let linkPeek |
||||
|
export let showTitleButton |
||||
|
export let titleButtonText |
||||
|
export let titleButtonURL |
||||
|
export let titleButtonPeek |
||||
|
|
||||
|
const { API, styleable } = getContext("sdk") |
||||
|
const context = getContext("context") |
||||
|
const component = getContext("component") |
||||
|
const schemaComponentMap = { |
||||
|
string: "stringfield", |
||||
|
options: "optionsfield", |
||||
|
number: "numberfield", |
||||
|
datetime: "datetimefield", |
||||
|
boolean: "booleanfield", |
||||
|
} |
||||
|
|
||||
|
let formId |
||||
|
let dataProviderId |
||||
|
let schema |
||||
|
|
||||
|
$: enrichedSearchColumns = enrichSearchColumns(searchColumns, schema) |
||||
|
$: enrichedFilter = enrichFilter(filter, enrichedSearchColumns, formId) |
||||
|
$: titleButtonAction = [ |
||||
|
{ |
||||
|
"##eventHandlerType": "Navigate To", |
||||
|
parameters: { |
||||
|
peek: titleButtonPeek, |
||||
|
url: titleButtonURL, |
||||
|
}, |
||||
|
}, |
||||
|
] |
||||
|
|
||||
|
// Enrich the default filter with the specified search fields |
||||
|
const enrichFilter = (filter, columns, formId) => { |
||||
|
let enrichedFilter = [...(filter || [])] |
||||
|
columns?.forEach(column => { |
||||
|
enrichedFilter.push({ |
||||
|
field: column.name, |
||||
|
operator: column.type === "string" ? "string" : "equal", |
||||
|
type: "string", |
||||
|
valueType: "Binding", |
||||
|
value: `{{ [${formId}].[${column.name}] }}`, |
||||
|
}) |
||||
|
}) |
||||
|
return enrichedFilter |
||||
|
} |
||||
|
|
||||
|
// Determine data types for search fields and only use those that are valid |
||||
|
const enrichSearchColumns = (searchColumns, schema) => { |
||||
|
let enrichedColumns = [] |
||||
|
searchColumns?.forEach(column => { |
||||
|
const schemaType = schema?.[column]?.type |
||||
|
const componentType = schemaComponentMap[schemaType] |
||||
|
if (componentType) { |
||||
|
enrichedColumns.push({ |
||||
|
name: column, |
||||
|
componentType, |
||||
|
type: schemaType, |
||||
|
}) |
||||
|
} |
||||
|
}) |
||||
|
return enrichedColumns.slice(0, 3) |
||||
|
} |
||||
|
|
||||
|
// Load the datasource schema on mount so we can determine column types |
||||
|
onMount(async () => { |
||||
|
if (dataSource) { |
||||
|
schema = await API.fetchDatasourceSchema(dataSource) |
||||
|
} |
||||
|
}) |
||||
|
</script> |
||||
|
|
||||
|
<Block> |
||||
|
<div class={size} use:styleable={$component.styles}> |
||||
|
<BlockComponent type="form" bind:id={formId} props={{ dataSource }}> |
||||
|
{#if title || enrichedSearchColumns?.length || showTitleButton} |
||||
|
<div class="header" class:mobile={$context.device.mobile}> |
||||
|
<div class="title"> |
||||
|
<Heading>{title || ""}</Heading> |
||||
|
</div> |
||||
|
<div class="controls"> |
||||
|
{#if enrichedSearchColumns?.length} |
||||
|
<div |
||||
|
class="search" |
||||
|
style="--cols:{enrichedSearchColumns?.length}" |
||||
|
> |
||||
|
{#each enrichedSearchColumns as column} |
||||
|
<BlockComponent |
||||
|
type={column.componentType} |
||||
|
props={{ |
||||
|
field: column.name, |
||||
|
placeholder: column.name, |
||||
|
text: column.name, |
||||
|
autoWidth: true, |
||||
|
}} |
||||
|
/> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
{#if showTitleButton} |
||||
|
<BlockComponent |
||||
|
type="button" |
||||
|
props={{ |
||||
|
onClick: titleButtonAction, |
||||
|
text: titleButtonText, |
||||
|
type: "cta", |
||||
|
}} |
||||
|
/> |
||||
|
{/if} |
||||
|
</div> |
||||
|
</div> |
||||
|
{/if} |
||||
|
<BlockComponent |
||||
|
type="dataprovider" |
||||
|
bind:id={dataProviderId} |
||||
|
props={{ |
||||
|
dataSource, |
||||
|
filter: enrichedFilter, |
||||
|
sortColumn, |
||||
|
sortOrder, |
||||
|
paginate, |
||||
|
limit: rowCount, |
||||
|
}} |
||||
|
> |
||||
|
<BlockComponent |
||||
|
type="table" |
||||
|
props={{ |
||||
|
dataProvider: `{{ literal [${dataProviderId}] }}`, |
||||
|
columns: tableColumns, |
||||
|
showAutoColumns, |
||||
|
rowCount, |
||||
|
quiet, |
||||
|
size, |
||||
|
linkRows, |
||||
|
linkURL, |
||||
|
linkColumn, |
||||
|
linkPeek, |
||||
|
}} |
||||
|
/> |
||||
|
</BlockComponent> |
||||
|
</BlockComponent> |
||||
|
</div> |
||||
|
</Block> |
||||
|
|
||||
|
<style> |
||||
|
.header { |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
margin-bottom: 20px; |
||||
|
} |
||||
|
|
||||
|
.title { |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
.title :global(.spectrum-Heading) { |
||||
|
flex: 1 1 auto; |
||||
|
white-space: nowrap; |
||||
|
overflow: hidden; |
||||
|
text-overflow: ellipsis; |
||||
|
} |
||||
|
|
||||
|
.controls { |
||||
|
flex: 0 1 auto; |
||||
|
display: flex; |
||||
|
flex-direction: row; |
||||
|
justify-content: flex-end; |
||||
|
align-items: center; |
||||
|
gap: 20px; |
||||
|
} |
||||
|
.controls :global(.spectrum-InputGroup .spectrum-InputGroup-input) { |
||||
|
width: 100%; |
||||
|
} |
||||
|
|
||||
|
.search { |
||||
|
flex: 0 1 auto; |
||||
|
gap: 10px; |
||||
|
max-width: 100%; |
||||
|
display: grid; |
||||
|
grid-template-columns: repeat(var(--cols), minmax(120px, 200px)); |
||||
|
} |
||||
|
.search :global(.spectrum-InputGroup) { |
||||
|
min-width: 0; |
||||
|
} |
||||
|
|
||||
|
/* Mobile styles */ |
||||
|
.mobile { |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
.mobile .controls { |
||||
|
flex-direction: column-reverse; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
} |
||||
|
.mobile .search { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
justify-content: flex-start; |
||||
|
align-items: stretch; |
||||
|
position: relative; |
||||
|
width: 100%; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,2 @@ |
|||||
|
export { default as tableblock } from "./TableBlock.svelte" |
||||
|
export { default as cardsblock } from "./CardsBlock.svelte" |
||||
@ -0,0 +1,9 @@ |
|||||
|
FROM mcr.microsoft.com/mssql/server |
||||
|
|
||||
|
ENV ACCEPT_EULA=Y |
||||
|
ENV SA_PASSWORD=Passw0rd |
||||
|
|
||||
|
COPY ./data / |
||||
|
|
||||
|
ENTRYPOINT [ "/bin/bash", "entrypoint.sh" ] |
||||
|
CMD [ "/opt/mssql/bin/sqlservr" ] |
||||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue