forked from tsai/budibase
committed by
GitHub
53 changed files with 1037 additions and 527 deletions
@ -0,0 +1,57 @@ |
|||||
|
<script> |
||||
|
import "@spectrum-css/pagination/dist/index-vars.css" |
||||
|
import "@spectrum-css/actionbutton/dist/index-vars.css" |
||||
|
import "@spectrum-css/typography/dist/index-vars.css" |
||||
|
|
||||
|
export let page |
||||
|
export let goToPrevPage |
||||
|
export let goToNextPage |
||||
|
export let hasPrevPage = true |
||||
|
export let hasNextPage = true |
||||
|
</script> |
||||
|
|
||||
|
<nav class="spectrum-Pagination spectrum-Pagination--explicit"> |
||||
|
<div |
||||
|
href="#" |
||||
|
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-prevButton" |
||||
|
on:click={hasPrevPage ? goToPrevPage : null} |
||||
|
class:is-disabled={!hasPrevPage} |
||||
|
> |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronLeft100" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
aria-label="ChevronLeft" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" /> |
||||
|
</svg> |
||||
|
</div> |
||||
|
<span class="spectrum-Body--secondary spectrum-Pagination-counter"> |
||||
|
Page {page} |
||||
|
</span> |
||||
|
<div |
||||
|
href="#" |
||||
|
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-ActionButton--quiet spectrum-Pagination-nextButton" |
||||
|
on:click={hasNextPage ? goToNextPage : null} |
||||
|
class:is-disabled={!hasNextPage} |
||||
|
> |
||||
|
<svg |
||||
|
class="spectrum-Icon spectrum-UIIcon-ChevronRight100" |
||||
|
focusable="false" |
||||
|
aria-hidden="true" |
||||
|
aria-label="ChevronLeft" |
||||
|
> |
||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" /> |
||||
|
</svg> |
||||
|
</div> |
||||
|
</nav> |
||||
|
|
||||
|
<style> |
||||
|
.spectrum-Pagination-counter { |
||||
|
margin-left: 0; |
||||
|
user-select: none; |
||||
|
} |
||||
|
.is-disabled:hover { |
||||
|
cursor: initial; |
||||
|
} |
||||
|
</style> |
||||
@ -1,71 +0,0 @@ |
|||||
<script> |
|
||||
import { Button, Drawer, Body, DrawerContent, Layout } from "@budibase/bbui" |
|
||||
import { createEventDispatcher } from "svelte" |
|
||||
import { notifications } from "@budibase/bbui" |
|
||||
import { |
|
||||
getDatasourceForProvider, |
|
||||
getSchemaForDatasource, |
|
||||
} from "builderStore/dataBinding" |
|
||||
import SaveFields from "./EventsEditor/actions/SaveFields.svelte" |
|
||||
import { currentAsset } from "builderStore" |
|
||||
|
|
||||
const dispatch = createEventDispatcher() |
|
||||
|
|
||||
export let value = {} |
|
||||
export let componentInstance |
|
||||
let drawer |
|
||||
let tempValue = value |
|
||||
|
|
||||
$: schemaFields = getSchemaFields(componentInstance) |
|
||||
|
|
||||
const getSchemaFields = component => { |
|
||||
const datasource = getDatasourceForProvider($currentAsset, component) |
|
||||
const { schema } = getSchemaForDatasource(datasource) |
|
||||
return Object.values(schema || {}) |
|
||||
} |
|
||||
|
|
||||
const saveFilter = async () => { |
|
||||
dispatch("change", tempValue) |
|
||||
notifications.success("Filters saved.") |
|
||||
drawer.hide() |
|
||||
} |
|
||||
|
|
||||
const onFieldsChanged = event => { |
|
||||
tempValue = event.detail |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<Button secondary on:click={drawer.show}>Define Filters</Button> |
|
||||
<Drawer bind:this={drawer} title="Filtering"> |
|
||||
<Button cta slot="buttons" on:click={saveFilter}>Save</Button> |
|
||||
<DrawerContent slot="body"> |
|
||||
<Layout> |
|
||||
<Body size="S"> |
|
||||
{#if !Object.keys(tempValue || {}).length} |
|
||||
Add your first filter column. |
|
||||
{:else} |
|
||||
Results are filtered to only those which match all of the following |
|
||||
constaints. |
|
||||
{/if} |
|
||||
</Body> |
|
||||
<div class="fields"> |
|
||||
<SaveFields |
|
||||
parameterFields={value} |
|
||||
{schemaFields} |
|
||||
valueLabel="Equals" |
|
||||
on:change={onFieldsChanged} |
|
||||
/> |
|
||||
</div> |
|
||||
</Layout> |
|
||||
</DrawerContent> |
|
||||
</Drawer> |
|
||||
|
|
||||
<style> |
|
||||
.fields { |
|
||||
display: grid; |
|
||||
column-gap: var(--spacing-l); |
|
||||
row-gap: var(--spacing-s); |
|
||||
align-items: center; |
|
||||
grid-template-columns: auto 1fr auto 1fr auto; |
|
||||
} |
|
||||
</style> |
|
||||
@ -0,0 +1,90 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
notifications, |
||||
|
Button, |
||||
|
Drawer, |
||||
|
Body, |
||||
|
DrawerContent, |
||||
|
Layout, |
||||
|
} from "@budibase/bbui" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import { |
||||
|
getDatasourceForProvider, |
||||
|
getSchemaForDatasource, |
||||
|
} from "builderStore/dataBinding" |
||||
|
import LuceneFilterBuilder from "./LuceneFilterBuilder.svelte" |
||||
|
import { currentAsset } from "builderStore" |
||||
|
import SaveFields from "../EventsEditor/actions/SaveFields.svelte" |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
export let value = [] |
||||
|
export let componentInstance |
||||
|
let drawer |
||||
|
let tempValue = value |
||||
|
|
||||
|
$: numFilters = Array.isArray(tempValue) |
||||
|
? tempValue.length |
||||
|
: Object.keys(tempValue || {}).length |
||||
|
$: dataSource = getDatasourceForProvider($currentAsset, componentInstance) |
||||
|
$: schema = getSchemaForDatasource(dataSource)?.schema |
||||
|
$: schemaFields = Object.values(schema || {}) |
||||
|
$: internalTable = dataSource?.type === "table" |
||||
|
|
||||
|
// Reset value if value is wrong type for the datasource. |
||||
|
// Lucene editor needs an array, and simple editor needs an object. |
||||
|
$: { |
||||
|
if (internalTable && !Array.isArray(value)) { |
||||
|
tempValue = [] |
||||
|
dispatch("change", []) |
||||
|
} else if (!internalTable && Array.isArray(value)) { |
||||
|
tempValue = {} |
||||
|
dispatch("change", {}) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const saveFilter = async () => { |
||||
|
dispatch("change", tempValue) |
||||
|
notifications.success("Filters saved.") |
||||
|
drawer.hide() |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<Button secondary on:click={drawer.show}>Define Filters</Button> |
||||
|
<Drawer bind:this={drawer} title="Filtering"> |
||||
|
<Button cta slot="buttons" on:click={saveFilter}>Save</Button> |
||||
|
<DrawerContent slot="body"> |
||||
|
<Layout> |
||||
|
<Body size="S"> |
||||
|
{#if !numFilters} |
||||
|
Add your first filter column. |
||||
|
{:else} |
||||
|
Results are filtered to only those which match all of the following |
||||
|
constaints. |
||||
|
{/if} |
||||
|
</Body> |
||||
|
{#if internalTable} |
||||
|
<LuceneFilterBuilder bind:value={tempValue} {schemaFields} /> |
||||
|
{:else} |
||||
|
<div class="fields"> |
||||
|
<SaveFields |
||||
|
parameterFields={Array.isArray(value) ? {} : value} |
||||
|
{schemaFields} |
||||
|
valueLabel="Equals" |
||||
|
on:change={e => (tempValue = e.detail)} |
||||
|
/> |
||||
|
</div> |
||||
|
{/if} |
||||
|
</Layout> |
||||
|
</DrawerContent> |
||||
|
</Drawer> |
||||
|
|
||||
|
<style> |
||||
|
.fields { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
align-items: center; |
||||
|
grid-template-columns: auto 1fr auto 1fr auto; |
||||
|
} |
||||
|
</style> |
||||
@ -0,0 +1,237 @@ |
|||||
|
<script> |
||||
|
import { |
||||
|
DatePicker, |
||||
|
ActionButton, |
||||
|
Button, |
||||
|
Select, |
||||
|
Combobox, |
||||
|
Input, |
||||
|
} from "@budibase/bbui" |
||||
|
import { store, currentAsset } from "builderStore" |
||||
|
import { getBindableProperties } from "builderStore/dataBinding" |
||||
|
import { createEventDispatcher } from "svelte" |
||||
|
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" |
||||
|
import { generate } from "shortid" |
||||
|
|
||||
|
const dispatch = createEventDispatcher() |
||||
|
|
||||
|
export let schemaFields |
||||
|
export let value |
||||
|
|
||||
|
const OperatorOptions = { |
||||
|
Equals: { |
||||
|
value: "equal", |
||||
|
label: "Equals", |
||||
|
}, |
||||
|
NotEquals: { |
||||
|
value: "notEqual", |
||||
|
label: "Not equals", |
||||
|
}, |
||||
|
Empty: { |
||||
|
value: "empty", |
||||
|
label: "Is empty", |
||||
|
}, |
||||
|
NotEmpty: { |
||||
|
value: "notEmpty", |
||||
|
label: "Is not empty", |
||||
|
}, |
||||
|
StartsWith: { |
||||
|
value: "string", |
||||
|
label: "Starts with", |
||||
|
}, |
||||
|
Like: { |
||||
|
value: "fuzzy", |
||||
|
label: "Like", |
||||
|
}, |
||||
|
MoreThan: { |
||||
|
value: "rangeLow", |
||||
|
label: "More than", |
||||
|
}, |
||||
|
LessThan: { |
||||
|
value: "rangeHigh", |
||||
|
label: "Less than", |
||||
|
}, |
||||
|
} |
||||
|
const BannedTypes = ["link", "attachment"] |
||||
|
$: bindableProperties = getBindableProperties( |
||||
|
$currentAsset, |
||||
|
$store.selectedComponentId |
||||
|
) |
||||
|
$: fieldOptions = (schemaFields ?? []) |
||||
|
.filter(field => !BannedTypes.includes(field.type)) |
||||
|
.map(field => field.name) |
||||
|
|
||||
|
const addField = () => { |
||||
|
value = [ |
||||
|
...value, |
||||
|
{ |
||||
|
id: generate(), |
||||
|
field: null, |
||||
|
operator: OperatorOptions.Equals.value, |
||||
|
value: null, |
||||
|
valueType: "Value", |
||||
|
}, |
||||
|
] |
||||
|
} |
||||
|
|
||||
|
const removeField = id => { |
||||
|
value = value.filter(field => field.id !== id) |
||||
|
} |
||||
|
|
||||
|
const getValidOperatorsForType = type => { |
||||
|
const Op = OperatorOptions |
||||
|
if (type === "string") { |
||||
|
return [ |
||||
|
Op.Equals, |
||||
|
Op.NotEquals, |
||||
|
Op.StartsWith, |
||||
|
Op.Like, |
||||
|
Op.Empty, |
||||
|
Op.NotEmpty, |
||||
|
] |
||||
|
} else if (type === "number") { |
||||
|
return [ |
||||
|
Op.Equals, |
||||
|
Op.NotEquals, |
||||
|
Op.MoreThan, |
||||
|
Op.LessThan, |
||||
|
Op.Empty, |
||||
|
Op.NotEmpty, |
||||
|
] |
||||
|
} else if (type === "options") { |
||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] |
||||
|
} else if (type === "boolean") { |
||||
|
return [Op.Equals, Op.NotEquals, Op.Empty, Op.NotEmpty] |
||||
|
} else if (type === "longform") { |
||||
|
return [ |
||||
|
Op.Equals, |
||||
|
Op.NotEquals, |
||||
|
Op.StartsWith, |
||||
|
Op.Like, |
||||
|
Op.Empty, |
||||
|
Op.NotEmpty, |
||||
|
] |
||||
|
} else if (type === "datetime") { |
||||
|
return [ |
||||
|
Op.Equals, |
||||
|
Op.NotEquals, |
||||
|
Op.MoreThan, |
||||
|
Op.LessThan, |
||||
|
Op.Empty, |
||||
|
Op.NotEmpty, |
||||
|
] |
||||
|
} |
||||
|
return [] |
||||
|
} |
||||
|
|
||||
|
const onFieldChange = (expression, field) => { |
||||
|
// Update the field type |
||||
|
expression.type = schemaFields.find(x => x.name === field)?.type |
||||
|
|
||||
|
// Ensure a valid operator is set |
||||
|
const validOperators = getValidOperatorsForType(expression.type) |
||||
|
if (!validOperators.includes(expression.operator)) { |
||||
|
expression.operator = |
||||
|
validOperators[0]?.value ?? OperatorOptions.Equals.value |
||||
|
onOperatorChange(expression, expression.operator) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const onOperatorChange = (expression, operator) => { |
||||
|
const noValueOptions = [ |
||||
|
OperatorOptions.Empty.value, |
||||
|
OperatorOptions.NotEmpty.value, |
||||
|
] |
||||
|
expression.noValue = noValueOptions.includes(operator) |
||||
|
if (expression.noValue) { |
||||
|
expression.value = null |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const getFieldOptions = field => { |
||||
|
const schema = schemaFields.find(x => x.name === field) |
||||
|
return schema?.constraints?.inclusion || [] |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
{#if value?.length} |
||||
|
<div class="fields"> |
||||
|
{#each value as expression, idx} |
||||
|
<Select |
||||
|
bind:value={expression.field} |
||||
|
options={fieldOptions} |
||||
|
on:change={e => onFieldChange(expression, e.detail)} |
||||
|
placeholder="Column" |
||||
|
/> |
||||
|
<Select |
||||
|
disabled={!expression.field} |
||||
|
options={getValidOperatorsForType(expression.type)} |
||||
|
bind:value={expression.operator} |
||||
|
on:change={e => onOperatorChange(expression, e.detail)} |
||||
|
placeholder={null} |
||||
|
/> |
||||
|
<Select |
||||
|
disabled={expression.noValue || !expression.field} |
||||
|
options={["Value", "Binding"]} |
||||
|
bind:value={expression.valueType} |
||||
|
placeholder={null} |
||||
|
/> |
||||
|
{#if expression.valueType === "Binding"} |
||||
|
<DrawerBindableInput |
||||
|
disabled={expression.noValue} |
||||
|
title={`Value for "${expression.field}"`} |
||||
|
value={expression.value} |
||||
|
placeholder="Value" |
||||
|
bindings={bindableProperties} |
||||
|
on:change={event => (expression.value = event.detail)} |
||||
|
/> |
||||
|
{:else if ["string", "longform", "number"].includes(expression.type)} |
||||
|
<Input disabled={expression.noValue} bind:value={expression.value} /> |
||||
|
{:else if expression.type === "options"} |
||||
|
<Combobox |
||||
|
disabled={expression.noValue} |
||||
|
options={getFieldOptions(expression.field)} |
||||
|
bind:value={expression.value} |
||||
|
/> |
||||
|
{:else if expression.type === "boolean"} |
||||
|
<Combobox |
||||
|
disabled |
||||
|
options={[ |
||||
|
{ label: "True", value: true }, |
||||
|
{ label: "False", value: false }, |
||||
|
]} |
||||
|
bind:value={expression.value} |
||||
|
/> |
||||
|
{:else if expression.type === "datetime"} |
||||
|
<DatePicker |
||||
|
disabled={expression.noValue} |
||||
|
bind:value={expression.value} |
||||
|
/> |
||||
|
{:else} |
||||
|
<DrawerBindableInput disabled /> |
||||
|
{/if} |
||||
|
|
||||
|
<ActionButton |
||||
|
size="S" |
||||
|
quiet |
||||
|
icon="Close" |
||||
|
on:click={() => removeField(expression.id)} |
||||
|
/> |
||||
|
{/each} |
||||
|
</div> |
||||
|
{/if} |
||||
|
<div> |
||||
|
<Button icon="AddCircle" size="M" secondary on:click={addField}> |
||||
|
Add expression |
||||
|
</Button> |
||||
|
</div> |
||||
|
|
||||
|
<style> |
||||
|
.fields { |
||||
|
display: grid; |
||||
|
column-gap: var(--spacing-l); |
||||
|
row-gap: var(--spacing-s); |
||||
|
align-items: center; |
||||
|
grid-template-columns: 1fr 120px 120px 1fr auto; |
||||
|
} |
||||
|
</style> |
||||
@ -1,18 +1,26 @@ |
|||||
const { QueryBuilder, buildSearchUrl, search } = require("./utils") |
const { fullSearch, paginatedSearch } = require("./utils") |
||||
|
const CouchDB = require("../../../db") |
||||
|
const { outputProcessing } = require("../../../utilities/rowProcessor") |
||||
|
|
||||
exports.rowSearch = async ctx => { |
exports.rowSearch = async ctx => { |
||||
const appId = ctx.appId |
const appId = ctx.appId |
||||
const { tableId } = ctx.params |
const { tableId } = ctx.params |
||||
const { bookmark, query, raw } = ctx.request.body |
const db = new CouchDB(appId) |
||||
let url |
const { paginate, query, ...params } = ctx.request.body |
||||
if (query) { |
params.tableId = tableId |
||||
url = new QueryBuilder(appId, query, bookmark).addTable(tableId).complete() |
|
||||
} else if (raw) { |
let response |
||||
url = buildSearchUrl({ |
if (paginate) { |
||||
appId, |
response = await paginatedSearch(appId, query, params) |
||||
query: raw, |
} else { |
||||
bookmark, |
response = await fullSearch(appId, query, params) |
||||
}) |
} |
||||
|
|
||||
|
// Enrich search results with relationships
|
||||
|
if (response.rows && response.rows.length) { |
||||
|
const table = await db.get(tableId) |
||||
|
response.rows = await outputProcessing(appId, table, response.rows) |
||||
} |
} |
||||
ctx.body = await search(url) |
|
||||
|
ctx.body = response |
||||
} |
} |
||||
|
|||||
@ -1,195 +0,0 @@ |
|||||
<script> |
|
||||
import { getContext } from "svelte" |
|
||||
import { |
|
||||
Button, |
|
||||
DatePicker, |
|
||||
Label, |
|
||||
Select, |
|
||||
Toggle, |
|
||||
Input, |
|
||||
} from "@budibase/bbui" |
|
||||
|
|
||||
const { API, styleable, Provider, builderStore, ActionTypes } = getContext( |
|
||||
"sdk" |
|
||||
) |
|
||||
const component = getContext("component") |
|
||||
|
|
||||
export let table |
|
||||
export let columns = [] |
|
||||
export let pageSize |
|
||||
export let noRowsMessage |
|
||||
|
|
||||
let rows = [] |
|
||||
let loaded = false |
|
||||
let search = {} |
|
||||
let tableDefinition |
|
||||
let schema |
|
||||
|
|
||||
let nextBookmark = null |
|
||||
let bookmark = null |
|
||||
let lastBookmark = null |
|
||||
|
|
||||
$: fetchData(table, bookmark) |
|
||||
// omit empty strings |
|
||||
$: parsedSearch = Object.keys(search).reduce( |
|
||||
(acc, next) => |
|
||||
search[next] === "" ? acc : { ...acc, [next]: search[next] }, |
|
||||
{} |
|
||||
) |
|
||||
$: actions = [ |
|
||||
{ |
|
||||
type: ActionTypes.RefreshDatasource, |
|
||||
callback: () => fetchData(table, bookmark), |
|
||||
metadata: { datasource: { type: "table", tableId: table } }, |
|
||||
}, |
|
||||
] |
|
||||
|
|
||||
async function fetchData(table, mark) { |
|
||||
if (table) { |
|
||||
const tableDef = await API.fetchTableDefinition(table) |
|
||||
schema = tableDef.schema |
|
||||
const output = await API.searchTableData({ |
|
||||
tableId: table, |
|
||||
search: parsedSearch, |
|
||||
pagination: { |
|
||||
pageSize, |
|
||||
bookmark: mark, |
|
||||
}, |
|
||||
}) |
|
||||
rows = output.rows |
|
||||
nextBookmark = output.bookmark |
|
||||
} |
|
||||
loaded = true |
|
||||
} |
|
||||
|
|
||||
function nextPage() { |
|
||||
lastBookmark = bookmark |
|
||||
bookmark = nextBookmark |
|
||||
} |
|
||||
|
|
||||
function previousPage() { |
|
||||
nextBookmark = bookmark |
|
||||
if (lastBookmark !== bookmark) { |
|
||||
bookmark = lastBookmark |
|
||||
} else { |
|
||||
// special case for going back to beginning |
|
||||
bookmark = null |
|
||||
lastBookmark = null |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<Provider {actions}> |
|
||||
<div use:styleable={$component.styles}> |
|
||||
<div class="query-builder"> |
|
||||
{#if schema} |
|
||||
{#each columns as field} |
|
||||
<div class="form-field"> |
|
||||
<Label extraSmall grey>{schema[field].name}</Label> |
|
||||
{#if schema[field].type === "options"} |
|
||||
<Select secondary bind:value={search[field]}> |
|
||||
<option value="">Choose an option</option> |
|
||||
{#each schema[field].constraints.inclusion as opt} |
|
||||
<option>{opt}</option> |
|
||||
{/each} |
|
||||
</Select> |
|
||||
{:else if schema[field].type === "datetime"} |
|
||||
<DatePicker bind:value={search[field]} /> |
|
||||
{:else if schema[field].type === "boolean"} |
|
||||
<Toggle text={schema[field].name} bind:checked={search[field]} /> |
|
||||
{:else if schema[field].type === "number"} |
|
||||
<Input type="number" bind:value={search[field]} /> |
|
||||
{:else if schema[field].type === "string"} |
|
||||
<Input bind:value={search[field]} /> |
|
||||
{/if} |
|
||||
</div> |
|
||||
{/each} |
|
||||
{/if} |
|
||||
<div class="actions"> |
|
||||
<Button |
|
||||
secondary |
|
||||
on:click={() => { |
|
||||
search = {} |
|
||||
bookmark = null |
|
||||
}} |
|
||||
> |
|
||||
Reset |
|
||||
</Button> |
|
||||
<Button |
|
||||
primary |
|
||||
on:click={() => { |
|
||||
bookmark = null |
|
||||
fetchData(table, bookmark) |
|
||||
}} |
|
||||
> |
|
||||
Search |
|
||||
</Button> |
|
||||
</div> |
|
||||
</div> |
|
||||
{#if loaded} |
|
||||
{#if rows.length > 0} |
|
||||
{#if $component.children === 0 && $builderStore.inBuilder} |
|
||||
<p><i class="ri-image-line" />Add some components to display.</p> |
|
||||
{:else} |
|
||||
{#each rows as row} |
|
||||
<Provider data={row}> |
|
||||
<slot /> |
|
||||
</Provider> |
|
||||
{/each} |
|
||||
{/if} |
|
||||
{:else if noRowsMessage} |
|
||||
<p><i class="ri-search-2-line" />{noRowsMessage}</p> |
|
||||
{/if} |
|
||||
{/if} |
|
||||
<div class="pagination"> |
|
||||
{#if lastBookmark != null || bookmark != null} |
|
||||
<Button primary on:click={previousPage}>Back</Button> |
|
||||
{/if} |
|
||||
{#if nextBookmark != null && rows.length !== 0} |
|
||||
<Button primary on:click={nextPage}>Next</Button> |
|
||||
{/if} |
|
||||
</div> |
|
||||
</div> |
|
||||
</Provider> |
|
||||
|
|
||||
<style> |
|
||||
p { |
|
||||
margin: 0 var(--spacing-m); |
|
||||
background-color: var(--grey-2); |
|
||||
color: var(--grey-6); |
|
||||
font-size: var(--font-size-s); |
|
||||
padding: var(--spacing-l); |
|
||||
border-radius: var(--border-radius-s); |
|
||||
display: grid; |
|
||||
place-items: center; |
|
||||
} |
|
||||
p i { |
|
||||
margin-bottom: var(--spacing-m); |
|
||||
font-size: 1.5rem; |
|
||||
color: var(--grey-5); |
|
||||
} |
|
||||
|
|
||||
.query-builder { |
|
||||
padding: var(--spacing-m); |
|
||||
border-radius: var(--border-radius-s); |
|
||||
} |
|
||||
|
|
||||
.actions { |
|
||||
display: grid; |
|
||||
grid-gap: var(--spacing-s); |
|
||||
justify-content: flex-end; |
|
||||
grid-auto-flow: column; |
|
||||
} |
|
||||
|
|
||||
.form-field { |
|
||||
margin-bottom: var(--spacing-m); |
|
||||
} |
|
||||
|
|
||||
.pagination { |
|
||||
display: grid; |
|
||||
grid-gap: var(--spacing-s); |
|
||||
justify-content: flex-end; |
|
||||
margin-top: var(--spacing-m); |
|
||||
grid-auto-flow: column; |
|
||||
} |
|
||||
</style> |
|
||||
Loading…
Reference in new issue