This commit is contained in:
Martin McKeaveney 2020-10-14 15:08:54 +01:00
commit 78ae3f194c
22 changed files with 311 additions and 181 deletions

View File

@ -53,6 +53,7 @@ export const getStore = () => {
store.saveScreen = saveScreen(store) store.saveScreen = saveScreen(store)
store.setCurrentScreen = setCurrentScreen(store) store.setCurrentScreen = setCurrentScreen(store)
store.deleteScreens = deleteScreens(store)
store.setCurrentPage = setCurrentPage(store) store.setCurrentPage = setCurrentPage(store)
store.createScreen = createScreen(store) store.createScreen = createScreen(store)
store.addStylesheet = addStylesheet(store) store.addStylesheet = addStylesheet(store)
@ -185,6 +186,26 @@ const setCurrentScreen = store => screenName => {
}) })
} }
const deleteScreens = store => (screens, pageName = null) => {
if (!(screens instanceof Array)) {
screens = [screens]
}
store.update(state => {
if (pageName == null) {
pageName = state.pages.main.name
}
for (let screen of screens) {
state.screens = state.screens.filter(c => c.name !== screen.name)
// Remove screen from current page as well
state.pages[pageName]._screens = state.pages[pageName]._screens.filter(
scr => scr.name !== screen.name
)
api.delete(`/_builder/api/pages/${pageName}/screens/${screen.name}`)
}
return state
})
}
const savePage = store => async page => { const savePage = store => async page => {
store.update(state => { store.update(state => {
if (state.currentFrontEndType !== "page" || !state.currentPageName) { if (state.currentFrontEndType !== "page" || !state.currentPageName) {

View File

@ -98,7 +98,7 @@ const createScreen = table => ({
label: "Deals", label: "Deals",
name: `all_${table._id}`, name: `all_${table._id}`,
tableId: table._id, tableId: table._id,
isTable: true, type: "table",
}, },
_instanceName: `${table.name} Table`, _instanceName: `${table.name} Table`,
_children: [], _children: [],

View File

@ -1,9 +1,11 @@
<script> <script>
import { backendUiStore } from "builderStore" import { backendUiStore, store } from "builderStore"
import { notifier } from "builderStore/store/notifications" import { notifier } from "builderStore/store/notifications"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui" import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
import { FIELDS } from "constants/backend" import { FIELDS } from "constants/backend"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import screenTemplates from "builderStore/store/screenTemplates"
import api from "builderStore/api"
export let table export let table
@ -13,6 +15,8 @@
let confirmDeleteDialog let confirmDeleteDialog
let error = "" let error = ""
let originalName = table.name let originalName = table.name
let templateScreens
let willBeDeleted
$: fields = Object.keys(table.schema) $: fields = Object.keys(table.schema)
@ -26,12 +30,18 @@
} }
function showModal() { function showModal() {
const screens = $store.allScreens
templateScreens = screens.filter(screen => screen.props.table === table._id)
willBeDeleted = ["All table data"].concat(
templateScreens.map(screen => `Screen ${screen.props._instanceName}`)
)
hideEditor() hideEditor()
confirmDeleteDialog.show() confirmDeleteDialog.show()
} }
async function deleteTable() { async function deleteTable() {
await backendUiStore.actions.tables.delete(table) await backendUiStore.actions.tables.delete(table)
store.deleteScreens(templateScreens)
notifier.success("Table deleted") notifier.success("Table deleted")
hideEditor() hideEditor()
} }
@ -98,10 +108,21 @@
</DropdownMenu> </DropdownMenu>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
body={`Are you sure you wish to delete the table '${table.name}'? Your data will be deleted and this action cannot be undone.`}
okText="Delete Table" okText="Delete Table"
onOk={deleteTable} onOk={deleteTable}
title="Confirm Delete" /> title="Confirm Delete">
Are you sure you wish to delete the table
<i>{table.name}?</i>
The following will also be deleted:
<b>
<div class="delete-items">
{#each willBeDeleted as item}
<div>{item}</div>
{/each}
</div>
</b>
This action cannot be undone.
</ConfirmDialog>
<style> <style>
div.icon { div.icon {
@ -115,6 +136,17 @@
font-size: 16px; font-size: 16px;
} }
div.delete-items {
margin-top: 10px;
margin-bottom: 10px;
margin-left: 10px;
}
div.delete-items div {
margin-top: 4px;
font-weight: 500;
}
.actions { .actions {
padding: var(--spacing-xl); padding: var(--spacing-xl);
display: grid; display: grid;

View File

@ -12,13 +12,18 @@
</script> </script>
<input type="checkbox" class="checkbox" id="_checkbox" /> <input type="checkbox" class="checkbox" id="_checkbox" />
<div on:click={handleChange}> <div class="checkbox-container" on:click={handleChange}>
<div class="check-div" class:checked> <div class="check-div" class:checked>
<div class="tick_mark" /> <div class="tick_mark" />
</div> </div>
</div> </div>
<style> <style>
.checkbox-container {
position: relative;
z-index: 0;
}
.checkbox { .checkbox {
display: none; display: none;
} }

View File

@ -20,7 +20,10 @@
<Modal bind:this={modal} on:hide={onCancel}> <Modal bind:this={modal} on:hide={onCancel}>
<ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red> <ModalContent onConfirm={onOk} {title} confirmText={okText} {cancelText} red>
<div class="body">{body}</div> <div class="body">
{body}
<slot />
</div>
</ModalContent> </ModalContent>
</Modal> </Modal>

View File

@ -28,7 +28,9 @@
_id: "screenslot-placeholder", _id: "screenslot-placeholder",
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_styles: { _styles: {
normal: {}, normal: {
flex: "1 1 auto",
},
hover: {}, hover: {},
active: {}, active: {},
selected: {}, selected: {},
@ -46,6 +48,7 @@
display: "flex", display: "flex",
"flex-direction": "column", "flex-direction": "column",
"align-items": "center", "align-items": "center",
flex: "1 1 auto",
}, },
hover: {}, hover: {},
active: {}, active: {},

View File

@ -11,38 +11,22 @@ export default `<html>
*, *:before, *:after { *, *:before, *:after {
box-sizing: border-box; box-sizing: border-box;
} }
.lay-__screenslot__text { .container-screenslot-placeholder {
width: 100%; display: flex;
height: 100%; align-items: center;
display: flex; justify-content: center;
justify-content: center; padding: 20px;
align-items: center; text-align: center;
padding: 20px 0; border-style: dashed !important;
border: dashed 2px #ccc; border-width: 1px;
font-family: sans-serif; color: #000000;
font-size: 1.2rem; background: #fafafa;
color: #999; flex: 1 1 auto;
text-transform: uppercase; }
font-weight: bold; .container-screenslot-placeholder span {
} display: block;
margin-bottom: 10px;
.container-screenslot-placeholder { }
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
text-align: center;
border-style: dashed !important;
border-width: 1px;
color: #000000;
background: #fafafa;
height: 94%;
}
.container-screenslot-placeholder span {
display: block;
margin-bottom: 10px;
}
</style> </style>
<script src='/assets/budibase-client.js'></script> <script src='/assets/budibase-client.js'></script>
<script> <script>

View File

@ -17,26 +17,13 @@
} }
const deleteScreen = () => { const deleteScreen = () => {
store.deleteScreens(screen, $store.currentPageName)
// update the page if required
store.update(state => { store.update(state => {
// Remove screen from screens
const screens = state.screens.filter(c => c.name !== screen.name)
state.screens = screens
// Remove screen from current page as well
const pageScreens = state.pages[state.currentPageName]._screens.filter(
scr => scr.name !== screen.name
)
state.pages[state.currentPageName]._screens = pageScreens
if (state.currentPreviewItem.name === screen.name) { if (state.currentPreviewItem.name === screen.name) {
store.setCurrentPage($store.currentPageName) store.setCurrentPage($store.currentPageName)
$goto(`./:page/page-layout`) $goto(`./:page/page-layout`)
} }
api.delete(
`/_builder/api/pages/${state.currentPageName}/screens/${screen.name}`
)
return state return state
}) })
} }

View File

@ -61,9 +61,9 @@
sort: detailScreen.props._component, sort: detailScreen.props._component,
}) })
} }
return urls
} }
return urls
} }
</script> </script>

View File

@ -261,6 +261,16 @@ export const padding = [
] ]
export const size = [ export const size = [
{
label: "Flex",
key: "flex",
control: OptionSelect,
defaultValue: "0 1 auto",
options: [
{ label: "Shrink", value: "0 1 auto" },
{ label: "Grow", value: "1 1 auto" },
],
},
{ {
label: "Width", label: "Width",
key: "width", key: "width",

View File

@ -1,13 +1,14 @@
import fetchBindableProperties from "../src/builderStore/fetchBindableProperties" import fetchBindableProperties from "../src/builderStore/fetchBindableProperties"
describe("fetch bindable properties", () => { describe("fetch bindable properties", () => {
it("should return bindable properties from screen components", () => { it("should return bindable properties from screen components", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "heading-id", componentInstanceId: "heading-id",
...testData() ...testData(),
}) })
const componentBinding = result.find(r => r.instance._id === "search-input-id" && r.type === "instance") const componentBinding = result.find(
r => r.instance._id === "search-input-id" && r.type === "instance"
)
expect(componentBinding).toBeDefined() expect(componentBinding).toBeDefined()
expect(componentBinding.type).toBe("instance") expect(componentBinding.type).toBe("instance")
expect(componentBinding.runtimeBinding).toBe("search-input-id.value") expect(componentBinding.runtimeBinding).toBe("search-input-id.value")
@ -16,28 +17,38 @@ describe("fetch bindable properties", () => {
it("should not return bindable components when not in their context", () => { it("should not return bindable components when not in their context", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "heading-id", componentInstanceId: "heading-id",
...testData() ...testData(),
}) })
const componentBinding = result.find(r => r.instance._id === "list-item-input-id") const componentBinding = result.find(
r => r.instance._id === "list-item-input-id"
)
expect(componentBinding).not.toBeDefined() expect(componentBinding).not.toBeDefined()
}) })
it("should return table schema, when inside a context", () => { it("should return table schema, when inside a context", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "list-item-input-id", componentInstanceId: "list-item-input-id",
...testData() ...testData(),
}) })
const contextBindings = result.filter(r => r.instance._id === "list-id" && r.type==="context") const contextBindings = result.filter(
r => r.instance._id === "list-id" && r.type === "context"
)
// 2 fields + _id + _rev // 2 fields + _id + _rev
expect(contextBindings.length).toBe(4) expect(contextBindings.length).toBe(4)
const namebinding = contextBindings.find(b => b.runtimeBinding === "data.name") const namebinding = contextBindings.find(
b => b.runtimeBinding === "data.name"
)
expect(namebinding).toBeDefined() expect(namebinding).toBeDefined()
expect(namebinding.readableBinding).toBe("list-name.Test Table.name") expect(namebinding.readableBinding).toBe("list-name.Test Table.name")
const descriptionbinding = contextBindings.find(b => b.runtimeBinding === "data.description") const descriptionbinding = contextBindings.find(
b => b.runtimeBinding === "data.description"
)
expect(descriptionbinding).toBeDefined() expect(descriptionbinding).toBeDefined()
expect(descriptionbinding.readableBinding).toBe("list-name.Test Table.description") expect(descriptionbinding.readableBinding).toBe(
"list-name.Test Table.description"
)
const idbinding = contextBindings.find(b => b.runtimeBinding === "data._id") const idbinding = contextBindings.find(b => b.runtimeBinding === "data._id")
expect(idbinding).toBeDefined() expect(idbinding).toBeDefined()
@ -47,35 +58,51 @@ describe("fetch bindable properties", () => {
it("should return table schema, for grantparent context", () => { it("should return table schema, for grantparent context", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "child-list-item-input-id", componentInstanceId: "child-list-item-input-id",
...testData() ...testData(),
}) })
const contextBindings = result.filter(r => r.type==="context") const contextBindings = result.filter(r => r.type === "context")
// 2 fields + _id + _rev ... x 2 tables // 2 fields + _id + _rev ... x 2 tables
expect(contextBindings.length).toBe(8) expect(contextBindings.length).toBe(8)
const namebinding_parent = contextBindings.find(b => b.runtimeBinding === "parent.data.name") const namebinding_parent = contextBindings.find(
b => b.runtimeBinding === "parent.data.name"
)
expect(namebinding_parent).toBeDefined() expect(namebinding_parent).toBeDefined()
expect(namebinding_parent.readableBinding).toBe("list-name.Test Table.name") expect(namebinding_parent.readableBinding).toBe("list-name.Test Table.name")
const descriptionbinding_parent = contextBindings.find(b => b.runtimeBinding === "parent.data.description") const descriptionbinding_parent = contextBindings.find(
b => b.runtimeBinding === "parent.data.description"
)
expect(descriptionbinding_parent).toBeDefined() expect(descriptionbinding_parent).toBeDefined()
expect(descriptionbinding_parent.readableBinding).toBe("list-name.Test Table.description") expect(descriptionbinding_parent.readableBinding).toBe(
"list-name.Test Table.description"
)
const namebinding_own = contextBindings.find(b => b.runtimeBinding === "data.name") const namebinding_own = contextBindings.find(
b => b.runtimeBinding === "data.name"
)
expect(namebinding_own).toBeDefined() expect(namebinding_own).toBeDefined()
expect(namebinding_own.readableBinding).toBe("child-list-name.Test Table.name") expect(namebinding_own.readableBinding).toBe(
"child-list-name.Test Table.name"
)
const descriptionbinding_own = contextBindings.find(b => b.runtimeBinding === "data.description") const descriptionbinding_own = contextBindings.find(
b => b.runtimeBinding === "data.description"
)
expect(descriptionbinding_own).toBeDefined() expect(descriptionbinding_own).toBeDefined()
expect(descriptionbinding_own.readableBinding).toBe("child-list-name.Test Table.description") expect(descriptionbinding_own.readableBinding).toBe(
"child-list-name.Test Table.description"
)
}) })
it("should return bindable component props, from components in same context", () => { it("should return bindable component props, from components in same context", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "list-item-heading-id", componentInstanceId: "list-item-heading-id",
...testData() ...testData(),
}) })
const componentBinding = result.find(r => r.instance._id === "list-item-input-id" && r.type === "instance") const componentBinding = result.find(
r => r.instance._id === "list-item-input-id" && r.type === "instance"
)
expect(componentBinding).toBeDefined() expect(componentBinding).toBeDefined()
expect(componentBinding.runtimeBinding).toBe("list-item-input-id.value") expect(componentBinding.runtimeBinding).toBe("list-item-input-id.value")
}) })
@ -83,125 +110,140 @@ describe("fetch bindable properties", () => {
it("should not return components from child context", () => { it("should not return components from child context", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "list-item-heading-id", componentInstanceId: "list-item-heading-id",
...testData() ...testData(),
}) })
const componentBinding = result.find(r => r.instance._id === "child-list-item-input-id" && r.type === "instance") const componentBinding = result.find(
r =>
r.instance._id === "child-list-item-input-id" && r.type === "instance"
)
expect(componentBinding).not.toBeDefined() expect(componentBinding).not.toBeDefined()
}) })
it("should return bindable component props, from components in same context (when nested context)", () => { it("should return bindable component props, from components in same context (when nested context)", () => {
const result = fetchBindableProperties({ const result = fetchBindableProperties({
componentInstanceId: "child-list-item-heading-id", componentInstanceId: "child-list-item-heading-id",
...testData() ...testData(),
}) })
const componentBinding = result.find(r => r.instance._id === "child-list-item-input-id" && r.type === "instance") const componentBinding = result.find(
r =>
r.instance._id === "child-list-item-input-id" && r.type === "instance"
)
expect(componentBinding).toBeDefined() expect(componentBinding).toBeDefined()
}) })
}) })
const testData = () => { const testData = () => {
const screen = { const screen = {
instanceName: "test screen", instanceName: "test screen",
name: "screen-id", name: "screen-id",
route: "/", route: "/",
props: { props: {
_id:"screent-root-id", _id: "screent-root-id",
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_children: [ _children: [
{ {
_id: "heading-id", _id: "heading-id",
_instanceName: "list item heading", _instanceName: "list item heading",
_component: "@budibase/standard-components/heading", _component: "@budibase/standard-components/heading",
text: "Screen Title" text: "Screen Title",
}, },
{ {
_id: "search-input-id", _id: "search-input-id",
_instanceName: "Search Input", _instanceName: "Search Input",
_component: "@budibase/standard-components/input", _component: "@budibase/standard-components/input",
value: "search phrase" value: "search phrase",
}, },
{ {
_id: "list-id", _id: "list-id",
_component: "@budibase/standard-components/list", _component: "@budibase/standard-components/list",
_instanceName: "list-name", _instanceName: "list-name",
table: { isTable: true, tableId: "test-table-id", label: "Test Table", name: "all_test-table-id" }, table: {
type: "table",
tableId: "test-table-id",
label: "Test Table",
name: "all_test-table-id",
},
_children: [ _children: [
{ {
_id: "list-item-heading-id", _id: "list-item-heading-id",
_instanceName: "list item heading", _instanceName: "list item heading",
_component: "@budibase/standard-components/heading", _component: "@budibase/standard-components/heading",
text: "hello" text: "hello",
}, },
{ {
_id: "list-item-input-id", _id: "list-item-input-id",
_instanceName: "List Item Input", _instanceName: "List Item Input",
_component: "@budibase/standard-components/input", _component: "@budibase/standard-components/input",
value: "list item" value: "list item",
}, },
{ {
_id: "child-list-id", _id: "child-list-id",
_component: "@budibase/standard-components/list", _component: "@budibase/standard-components/list",
_instanceName: "child-list-name", _instanceName: "child-list-name",
table: { isTable: true, tableId: "test-table-id", label: "Test Table", name: "all_test-table-id"}, table: {
type: "table",
tableId: "test-table-id",
label: "Test Table",
name: "all_test-table-id",
},
_children: [ _children: [
{ {
_id: "child-list-item-heading-id", _id: "child-list-item-heading-id",
_instanceName: "child list item heading", _instanceName: "child list item heading",
_component: "@budibase/standard-components/heading", _component: "@budibase/standard-components/heading",
text: "hello" text: "hello",
}, },
{ {
_id: "child-list-item-input-id", _id: "child-list-item-input-id",
_instanceName: "Child List Item Input", _instanceName: "Child List Item Input",
_component: "@budibase/standard-components/input", _component: "@budibase/standard-components/input",
value: "child list item" value: "child list item",
}, },
] ],
}, },
] ],
}, },
] ],
} },
} }
const tables = [{ const tables = [
_id: "test-table-id", {
name: "Test Table", _id: "test-table-id",
name: "Test Table",
schema: { schema: {
name: { name: {
type: "string" type: "string",
}, },
description: { description: {
type: "string" type: "string",
} },
} },
}] },
]
const components = { const components = {
"@budibase/standard-components/container" : { "@budibase/standard-components/container": {
props: {}, props: {},
}, },
"@budibase/standard-components/list" : { "@budibase/standard-components/list": {
context: "table", context: "table",
props: { props: {
table: "string" table: "string",
}, },
}, },
"@budibase/standard-components/input" : { "@budibase/standard-components/input": {
bindable: "value", bindable: "value",
props: { props: {
value: "string" value: "string",
}, },
}, },
"@budibase/standard-components/heading" : { "@budibase/standard-components/heading": {
props: { props: {
text: "string" text: "string",
}, },
}, },
} }
return { screen, tables, components } return { screen, tables, components }
} }

View File

@ -3,7 +3,7 @@ const ClientDb = require("../../db/clientDb")
const { getPackageForBuilder, buildPage } = require("../../utilities/builder") const { getPackageForBuilder, buildPage } = require("../../utilities/builder")
const env = require("../../environment") const env = require("../../environment")
const instanceController = require("./instance") const instanceController = require("./instance")
const { copy, exists, readFile, writeFile } = require("fs-extra") const { copy, existsSync, readFile, writeFile } = require("fs-extra")
const { budibaseAppsDir } = require("../../utilities/budibaseDir") const { budibaseAppsDir } = require("../../utilities/budibaseDir")
const sqrl = require("squirrelly") const sqrl = require("squirrelly")
const setBuilderToken = require("../../utilities/builder/setBuilderToken") const setBuilderToken = require("../../utilities/builder/setBuilderToken")
@ -116,6 +116,12 @@ exports.delete = async function(ctx) {
const db = new CouchDB(ClientDb.name(getClientId(ctx))) const db = new CouchDB(ClientDb.name(getClientId(ctx)))
const app = await db.get(ctx.params.applicationId) const app = await db.get(ctx.params.applicationId)
const result = await db.remove(app) const result = await db.remove(app)
for (let instance of app.instances) {
const instanceDb = new CouchDB(instance._id)
await instanceDb.destroy()
}
// remove top level directory
await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), { await fs.rmdir(join(budibaseAppsDir(), ctx.params.applicationId), {
recursive: true, recursive: true,
}) })
@ -137,7 +143,7 @@ const createEmptyAppPackage = async (ctx, app) => {
const appsFolder = budibaseAppsDir() const appsFolder = budibaseAppsDir()
const newAppFolder = resolve(appsFolder, app._id) const newAppFolder = resolve(appsFolder, app._id)
if (await exists(newAppFolder)) { if (existsSync(newAppFolder)) {
ctx.throw(400, "App folder already exists for this application") ctx.throw(400, "App folder already exists for this application")
} }

View File

@ -0,0 +1,17 @@
/**
* This controller is not currently fully implemented. Screens are
* currently managed as part of the pages API, please look in api/routes/page.js
* for routes and controllers.
*/
exports.fetch = async ctx => {
ctx.throw(501)
}
exports.save = async ctx => {
ctx.throw(501)
}
exports.destroy = async ctx => {
ctx.throw(501)
}

View File

@ -1,9 +1,9 @@
{ {
"title": "{{ name }}", "title": "{{ name }}",
"favicon": "./_shared/favicon.png", "favicon": "./_shared/favicon.png",
"stylesheets": [], "stylesheets": [],
"componentLibraries": ["@budibase/standard-components"], "componentLibraries": ["@budibase/standard-components"],
"props": { "props": {
"_id": "private-master-root", "_id": "private-master-root",
"_component": "@budibase/standard-components/container", "_component": "@budibase/standard-components/container",
"_children": [ "_children": [
@ -62,10 +62,11 @@
"_component": "##builtin/screenslot", "_component": "##builtin/screenslot",
"_styles": { "_styles": {
"normal": { "normal": {
"padding": "0px", "flex": "1 1 auto",
"align-items": "flex-start", "display": "flex",
"height": "100vh", "flex-direction": "column",
"background-image": "None" "justify-content": "flex-start",
"align-items": "stretch"
}, },
"hover": {}, "hover": {},
"active": {}, "active": {},
@ -84,10 +85,9 @@
"flex-direction": "column", "flex-direction": "column",
"align-items": "stretch", "align-items": "stretch",
"justify-content": "flex-start", "justify-content": "flex-start",
"height": "",
"max-width": "",
"margin-right": "auto", "margin-right": "auto",
"margin-left": "auto" "margin-left": "auto",
"min-height": "100%"
}, },
"selected": {} "selected": {}
}, },
@ -95,6 +95,6 @@
"className": "", "className": "",
"onLoad": [] "onLoad": []
}, },
"_css": "", "_css": "",
"uiFunctions": "" "uiFunctions": ""
} }

View File

@ -1,4 +1,4 @@
const { exists, readFile, writeFile, ensureDir } = require("fs-extra") const { existsSync, readFile, writeFile, ensureDir } = require("fs-extra")
const { join, resolve } = require("./centralPath") const { join, resolve } = require("./centralPath")
const Sqrl = require("squirrelly") const Sqrl = require("squirrelly")
const uuid = require("uuid") const uuid = require("uuid")
@ -28,7 +28,7 @@ const setCouchDbUrl = async opts => {
const createDevEnvFile = async opts => { const createDevEnvFile = async opts => {
const destConfigFile = join(opts.dir, "./.env") const destConfigFile = join(opts.dir, "./.env")
let createConfig = !(await exists(destConfigFile)) || opts.quiet let createConfig = !existsSync(destConfigFile) || opts.quiet
if (createConfig) { if (createConfig) {
const template = await readFile( const template = await readFile(
resolve(__dirname, "..", "..", ".env.template"), resolve(__dirname, "..", "..", ".env.template"),

View File

@ -21,6 +21,11 @@
export let height = 500 export let height = 500
export let pagination export let pagination
// These can never change at runtime so don't need to be reactive
let canEdit = editable && datasource && datasource.type !== "view"
let canAddDelete = editable && datasource && datasource.type === "table"
let store = _bb.store
let dataLoaded = false let dataLoaded = false
let data let data
let columnDefs let columnDefs
@ -32,35 +37,42 @@
minWidth: 150, minWidth: 150,
filter: true, filter: true,
}, },
rowSelection: editable ? "multiple" : false, rowSelection: canEdit ? "multiple" : false,
suppressRowClickSelection: !editable, suppressRowClickSelection: !canEdit,
paginationAutoPageSize: true, paginationAutoPageSize: true,
pagination, pagination,
} }
let store = _bb.store
onMount(async () => { onMount(async () => {
if (datasource.tableId) { if (!isEmpty(datasource)) {
const jsonTable = await _bb.api.get(`/api/tables/${datasource.tableId}`) data = await fetchData(datasource, $store)
table = await jsonTable.json() let schema = {}
const { schema } = table
if (!isEmpty(datasource)) { // Get schema for datasource
data = await fetchData(datasource, $store) // Views with "Calculate" applied provide their own schema.
columnDefs = Object.keys(schema).map((key, i) => { // For everything else, use the tableId property to pull to table schema
return { if (datasource.schema) {
headerCheckboxSelection: i === 0 && editable, schema = datasource.schema
checkboxSelection: i === 0 && editable, } else {
valueSetter: setters.get(schema[key].type), const jsonTable = await _bb.api.get(`/api/tables/${datasource.tableId}`)
headerName: key.charAt(0).toUpperCase() + key.slice(1), table = await jsonTable.json()
field: key, schema = table.schema
hide: shouldHideField(key),
sortable: true,
editable: editable,
cellRenderer: getRenderer(schema[key], editable),
autoHeight: true,
}
})
} }
columnDefs = Object.keys(schema).map((key, i) => {
return {
headerCheckboxSelection: i === 0 && canEdit,
checkboxSelection: i === 0 && canEdit,
valueSetter: setters.get(schema[key].type),
headerName: key.charAt(0).toUpperCase() + key.slice(1),
field: key,
hide: shouldHideField(key),
sortable: true,
editable: canEdit,
cellRenderer: getRenderer(schema[key], canEdit),
autoHeight: true,
}
})
dataLoaded = true dataLoaded = true
} }
}) })
@ -117,7 +129,7 @@
<div style="--grid-height: {height}px"> <div style="--grid-height: {height}px">
{#if dataLoaded} {#if dataLoaded}
{#if editable} {#if canAddDelete}
<div class="controls"> <div class="controls">
<CreateRowButton {_bb} {table} on:newRow={handleNewRow} /> <CreateRowButton {_bb} {table} on:newRow={handleNewRow} />
{#if selectedRows.length > 0} {#if selectedRows.length > 0}

View File

@ -12,7 +12,7 @@
export let color export let color
export let stripeColor export let stripeColor
export let borderColor export let borderColor
export let datasource = {} export let datasource
export let _bb export let _bb
let data = [] let data = []
@ -35,14 +35,21 @@
const FETCH_TABLE_URL = `/api/tables/${tableId}` const FETCH_TABLE_URL = `/api/tables/${tableId}`
const response = await _bb.api.get(FETCH_TABLE_URL) const response = await _bb.api.get(FETCH_TABLE_URL)
const table = await response.json() const table = await response.json()
schema = table.schema return table.schema
} }
onMount(async () => { onMount(async () => {
if (!isEmpty(datasource)) { if (!isEmpty(datasource)) {
data = await fetchData(datasource, $store) data = await fetchData(datasource, $store)
if (data && data.length) {
await fetchTable(data[0].tableId) // 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) headers = Object.keys(schema).filter(shouldDisplayField)
} }
} }
@ -54,7 +61,6 @@
if (name === "type") return false if (name === "type") return false
// tables are always tied to a single tableId, this is irrelevant // tables are always tied to a single tableId, this is irrelevant
if (name === "tableId") return false if (name === "tableId") return false
return true return true
} }
@ -95,11 +101,11 @@
{#each sorted as row (row._id)} {#each sorted as row (row._id)}
<tr> <tr>
{#each headers as header} {#each headers as header}
{#if schema[header]} {#if schema[header] !== undefined}
<!-- Rudimentary solution for attachments on array given this entire table will be replaced by AG Grid --> <!-- Rudimentary solution for attachments on array given this entire table will be replaced by AG Grid -->
{#if schema[header].type === 'attachment'} {#if schema[header] && schema[header].type === 'attachment'}
<AttachmentList files={row[header]} /> <AttachmentList files={row[header]} />
{:else if schema[header].type === 'link'} {:else if schema[header] && schema[header].type === 'link'}
<td>{row[header] ? row[header].length : 0} related row(s)</td> <td>{row[header] ? row[header].length : 0} related row(s)</td>
{:else} {:else}
<td>{row[header] == null ? '' : row[header]}</td> <td>{row[header] == null ? '' : row[header]}</td>

View File

@ -14,7 +14,7 @@
let rowId let rowId
let errors = {} let errors = {}
$: schema = $store.data && $store.data._table.schema $: schema = $store.data && $store.data._table && $store.data._table.schema
$: fields = schema ? Object.keys(schema) : [] $: fields = schema ? Object.keys(schema) : []
</script> </script>

View File

@ -19,7 +19,7 @@
} }
onMount(async () => { onMount(async () => {
if (table) { if (table && typeof table === "string") {
const tableObj = await fetchTable(table) const tableObj = await fetchTable(table)
row.tableId = table row.tableId = table
row._table = tableObj row._table = tableObj

View File

@ -6,16 +6,16 @@ export default async function fetchData(datasource, store) {
if (name) { if (name) {
let rows = [] let rows = []
if (type === "table") { if (type === "table") {
rows = await fetchTableData() rows = fetchTableData()
} else if (type === "view") { } else if (type === "view") {
rows = await fetchViewData() rows = fetchViewData()
} else if (type === "link") { } else if (type === "link") {
rows = await fetchLinkedRowsData() rows = fetchLinkedRowsData()
} }
// Fetch table schema so we can check for linked rows // Fetch table schema so we can check for linked rows
if (rows && rows.length) { if (rows && rows.length) {
const table = await fetchTable(rows[0].tableId) const table = await fetchTable()
const keys = Object.keys(table.schema) const keys = Object.keys(table.schema)
rows.forEach(row => { rows.forEach(row => {
for (let key of keys) { for (let key of keys) {
@ -27,10 +27,12 @@ export default async function fetchData(datasource, store) {
} }
return rows return rows
} else {
return []
} }
async function fetchTable(id) { async function fetchTable() {
const FETCH_TABLE_URL = `/api/tables/${id}` const FETCH_TABLE_URL = `/api/tables/${datasource.tableId}`
const response = await api.get(FETCH_TABLE_URL) const response = await api.get(FETCH_TABLE_URL)
return await response.json() return await response.json()
} }