Merge branch 'spectrum-bbui' of github.com:Budibase/budibase into spectrum-bbui
This commit is contained in:
commit
1a067fd492
|
@ -66,6 +66,7 @@
|
||||||
"@spectrum-css/tabs": "^3.0.1",
|
"@spectrum-css/tabs": "^3.0.1",
|
||||||
"@spectrum-css/textfield": "^3.0.1",
|
"@spectrum-css/textfield": "^3.0.1",
|
||||||
"@spectrum-css/toast": "^3.0.1",
|
"@spectrum-css/toast": "^3.0.1",
|
||||||
|
"@spectrum-css/treeview": "^3.0.2",
|
||||||
"@spectrum-css/typography": "^3.0.1",
|
"@spectrum-css/typography": "^3.0.1",
|
||||||
"@spectrum-css/underlay": "^2.0.9",
|
"@spectrum-css/underlay": "^2.0.9",
|
||||||
"@spectrum-css/vars": "^3.0.1",
|
"@spectrum-css/vars": "^3.0.1",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
export let disabled = undefined;
|
export let disabled = undefined;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<li on:click class="spectrum-Menu-item" class:is-disabled={disabled} role="menuitem" tabindex="0">
|
<li on:click|preventDefault class="spectrum-Menu-item" class:is-disabled={disabled} role="menuitem" tabindex="0">
|
||||||
<span class="spectrum-Menu-itemLabel">
|
<span class="spectrum-Menu-itemLabel">
|
||||||
{#if icon}
|
{#if icon}
|
||||||
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-Menu-itemIcon" focusable="false" aria-hidden="true" aria-label={icon}>
|
<svg class="spectrum-Icon spectrum-Icon--sizeM spectrum-Menu-itemIcon" focusable="false" aria-hidden="true" aria-label={icon}>
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
<script>
|
||||||
|
export let selected = false;
|
||||||
|
export let open = false;
|
||||||
|
export let title;
|
||||||
|
export let icon;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<li
|
||||||
|
class:is-selected={selected} class:is-open={open} class="spectrum-TreeView-item">
|
||||||
|
<a on:click class="spectrum-TreeView-itemLink" href="#">
|
||||||
|
{#if $$slots.default}
|
||||||
|
<svg class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-TreeView-itemIndicator" focusable="false" aria-hidden="true">
|
||||||
|
<use xlink:href="#spectrum-css-icon-Chevron100" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
{#if icon}
|
||||||
|
<svg class="spectrum-TreeView-itemIcon spectrum-Icon spectrum-Icon--sizeM" focusable="false" aria-hidden="true" aria-label="Layers">
|
||||||
|
<use xlink:href="#spectrum-icon-18-{icon}" />
|
||||||
|
</svg>
|
||||||
|
{/if}
|
||||||
|
<span class="spectrum-TreeView-itemLabel">{title}</span>
|
||||||
|
</a>
|
||||||
|
{#if $$slots.default}
|
||||||
|
<ul class="spectrum-TreeView">
|
||||||
|
<slot />
|
||||||
|
</ul>
|
||||||
|
{/if}
|
||||||
|
</li>
|
|
@ -0,0 +1,11 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/treeview/dist/index-vars.css"
|
||||||
|
|
||||||
|
export let quiet = false
|
||||||
|
export let standalone = true
|
||||||
|
export let width = '250px';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<ul class:spectrum-TreeView--standalone={standalone} class:spectrum-TreeView--quiet={quiet} class="spectrum-TreeView" style="width: {width}">
|
||||||
|
<slot />
|
||||||
|
</ul>
|
|
@ -42,6 +42,8 @@ export { default as Context } from "./context"
|
||||||
export { default as Table } from "./Table/Table.svelte"
|
export { default as Table } from "./Table/Table.svelte"
|
||||||
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
export { default as Tabs } from "./Tabs/Tabs.svelte"
|
||||||
export { default as Tab } from "./Tabs/Tab.svelte"
|
export { default as Tab } from "./Tabs/Tab.svelte"
|
||||||
|
export { default as TreeView } from "./TreeView/Tree.svelte"
|
||||||
|
export { default as TreeItem } from "./TreeView/Item.svelte"
|
||||||
export { default as Divider } from "./Divider/Divider.svelte"
|
export { default as Divider } from "./Divider/Divider.svelte"
|
||||||
export { default as Search } from "./Form/Search.svelte"
|
export { default as Search } from "./Form/Search.svelte"
|
||||||
|
|
||||||
|
|
|
@ -211,6 +211,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.2.tgz#8e27cce799b1b1d0054a88b135dddf7fbf1bdc78"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/toast/-/toast-3.0.2.tgz#8e27cce799b1b1d0054a88b135dddf7fbf1bdc78"
|
||||||
integrity sha512-sJp8DRsU2iSF67hlOQHFdRkbJHncspoOywHEsqcjh2KFl8gRY12rQL0ORG6J2THUt0LVBWSy48iwph9s4rkwsA==
|
integrity sha512-sJp8DRsU2iSF67hlOQHFdRkbJHncspoOywHEsqcjh2KFl8gRY12rQL0ORG6J2THUt0LVBWSy48iwph9s4rkwsA==
|
||||||
|
|
||||||
|
"@spectrum-css/treeview@^3.0.2":
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@spectrum-css/treeview/-/treeview-3.0.2.tgz#d54d8f17290babb1c885f5d9355e225421beb0d2"
|
||||||
|
integrity sha512-foO7UBJv1JMFaKgDPVt8jBghZSVbqhXR8TaGaxHSnMubv7ygmKkc1AITrWC2STILCn84ju2vchOohMZfW6sYwg==
|
||||||
|
|
||||||
"@spectrum-css/typography@^3.0.1":
|
"@spectrum-css/typography@^3.0.1":
|
||||||
version "3.0.2"
|
version "3.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
|
resolved "https://registry.yarnpkg.com/@spectrum-css/typography/-/typography-3.0.2.tgz#ea3ca0a60e18064527819d48c8c4364cab4fcd38"
|
||||||
|
|
|
@ -1,362 +0,0 @@
|
||||||
import { writable, get } from "svelte/store"
|
|
||||||
import { cloneDeep } from "lodash/fp"
|
|
||||||
import api from "../api"
|
|
||||||
|
|
||||||
const INITIAL_BACKEND_UI_STATE = {
|
|
||||||
tables: [],
|
|
||||||
views: [],
|
|
||||||
users: [],
|
|
||||||
roles: [],
|
|
||||||
datasources: [],
|
|
||||||
queries: [],
|
|
||||||
integrations: {},
|
|
||||||
selectedDatabase: {},
|
|
||||||
selectedTable: {},
|
|
||||||
draftTable: {},
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getBackendUiStore = () => {
|
|
||||||
const store = writable({ ...INITIAL_BACKEND_UI_STATE })
|
|
||||||
|
|
||||||
store.actions = {
|
|
||||||
reset: () => store.set({ ...INITIAL_BACKEND_UI_STATE }),
|
|
||||||
database: {
|
|
||||||
select: async db => {
|
|
||||||
const tablesResponse = await api.get(`/api/tables`)
|
|
||||||
const tables = await tablesResponse.json()
|
|
||||||
const datasourcesResponse = await api.get(`/api/datasources`)
|
|
||||||
const datasources = await datasourcesResponse.json()
|
|
||||||
const queriesResponse = await api.get(`/api/queries`)
|
|
||||||
const queries = await queriesResponse.json()
|
|
||||||
const integrationsResponse = await api.get("/api/integrations")
|
|
||||||
const integrations = await integrationsResponse.json()
|
|
||||||
const permissionLevels = await store.actions.permissions.fetchLevels()
|
|
||||||
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedDatabase = db
|
|
||||||
state.tables = tables
|
|
||||||
state.datasources = datasources
|
|
||||||
state.queries = queries
|
|
||||||
state.integrations = integrations
|
|
||||||
state.permissionLevels = permissionLevels
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rows: {
|
|
||||||
save: () =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedView = state.selectedView
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
delete: () =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedView = state.selectedView
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
select: row =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedRow = row
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
datasources: {
|
|
||||||
fetch: async () => {
|
|
||||||
const response = await api.get(`/api/datasources`)
|
|
||||||
const json = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
state.datasources = json
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
select: async datasourceId => {
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedDatasourceId = datasourceId
|
|
||||||
state.selectedQueryId = null
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
save: async datasource => {
|
|
||||||
const response = await api.post("/api/datasources", datasource)
|
|
||||||
const json = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
const currentIdx = state.datasources.findIndex(
|
|
||||||
ds => ds._id === json._id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (currentIdx >= 0) {
|
|
||||||
state.datasources.splice(currentIdx, 1, json)
|
|
||||||
} else {
|
|
||||||
state.datasources.push(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.datasources = state.datasources
|
|
||||||
state.selectedDatasourceId = json._id
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
delete: async datasource => {
|
|
||||||
await api.delete(
|
|
||||||
`/api/datasources/${datasource._id}/${datasource._rev}`
|
|
||||||
)
|
|
||||||
store.update(state => {
|
|
||||||
state.datasources = state.datasources.filter(
|
|
||||||
existing => existing._id !== datasource._id
|
|
||||||
)
|
|
||||||
if (datasource._id === state.selectedDatasourceId) {
|
|
||||||
state.selectedDatasourceId = null
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
queries: {
|
|
||||||
fetch: async () => {
|
|
||||||
const response = await api.get(`/api/queries`)
|
|
||||||
const json = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
state.queries = json
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
save: async (datasourceId, query) => {
|
|
||||||
const integrations = get(store).integrations
|
|
||||||
const dataSource = get(store).datasources.filter(
|
|
||||||
ds => ds._id === datasourceId
|
|
||||||
)
|
|
||||||
// check if readable attribute is found
|
|
||||||
if (dataSource.length !== 0) {
|
|
||||||
const integration = integrations[dataSource[0].source]
|
|
||||||
const readable = integration.query[query.queryVerb].readable
|
|
||||||
if (readable) {
|
|
||||||
query.readable = readable
|
|
||||||
}
|
|
||||||
}
|
|
||||||
query.datasourceId = datasourceId
|
|
||||||
const response = await api.post(`/api/queries`, query)
|
|
||||||
if (response.status !== 200) {
|
|
||||||
throw new Error("Failed saving query.")
|
|
||||||
}
|
|
||||||
const json = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
const currentIdx = state.queries.findIndex(
|
|
||||||
query => query._id === json._id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (currentIdx >= 0) {
|
|
||||||
state.queries.splice(currentIdx, 1, json)
|
|
||||||
} else {
|
|
||||||
state.queries.push(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
state.queries = state.queries
|
|
||||||
state.selectedQueryId = json._id
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
select: query =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedDatasourceId = query.datasourceId
|
|
||||||
state.selectedQueryId = query._id
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
delete: async query => {
|
|
||||||
await api.delete(`/api/queries/${query._id}/${query._rev}`)
|
|
||||||
store.update(state => {
|
|
||||||
state.queries = state.queries.filter(
|
|
||||||
existing => existing._id !== query._id
|
|
||||||
)
|
|
||||||
if (state.selectedQueryId === query._id) {
|
|
||||||
state.selectedQueryId = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tables: {
|
|
||||||
fetch: async () => {
|
|
||||||
const tablesResponse = await api.get(`/api/tables`)
|
|
||||||
const tables = await tablesResponse.json()
|
|
||||||
store.update(state => {
|
|
||||||
state.tables = tables
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
select: table =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedTable = table
|
|
||||||
state.draftTable = cloneDeep(table)
|
|
||||||
state.selectedView = { name: `all_${table._id}` }
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
save: async table => {
|
|
||||||
const updatedTable = cloneDeep(table)
|
|
||||||
const oldTable = get(store).tables.filter(t => t._id === table._id)[0]
|
|
||||||
|
|
||||||
const fieldNames = []
|
|
||||||
// update any renamed schema keys to reflect their names
|
|
||||||
for (let key of Object.keys(updatedTable.schema)) {
|
|
||||||
// if field name has been seen before remove it
|
|
||||||
if (fieldNames.indexOf(key.toLowerCase()) !== -1) {
|
|
||||||
delete updatedTable.schema[key]
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const field = updatedTable.schema[key]
|
|
||||||
const oldField = oldTable?.schema[key]
|
|
||||||
// if the type has changed then revert back to the old field
|
|
||||||
if (oldField != null && oldField.type !== field.type) {
|
|
||||||
updatedTable.schema[key] = oldField
|
|
||||||
}
|
|
||||||
// field has been renamed
|
|
||||||
if (field.name && field.name !== key) {
|
|
||||||
updatedTable.schema[field.name] = field
|
|
||||||
updatedTable._rename = { old: key, updated: field.name }
|
|
||||||
delete updatedTable.schema[key]
|
|
||||||
}
|
|
||||||
// finally record this field has been used
|
|
||||||
fieldNames.push(key.toLowerCase())
|
|
||||||
}
|
|
||||||
|
|
||||||
const SAVE_TABLE_URL = `/api/tables`
|
|
||||||
const response = await api.post(SAVE_TABLE_URL, updatedTable)
|
|
||||||
const savedTable = await response.json()
|
|
||||||
await store.actions.tables.fetch()
|
|
||||||
store.actions.tables.select(savedTable)
|
|
||||||
return savedTable
|
|
||||||
},
|
|
||||||
delete: async table => {
|
|
||||||
await api.delete(`/api/tables/${table._id}/${table._rev}`)
|
|
||||||
store.update(state => {
|
|
||||||
state.tables = state.tables.filter(
|
|
||||||
existing => existing._id !== table._id
|
|
||||||
)
|
|
||||||
if (table._id === state.selectedTable._id) {
|
|
||||||
state.selectedTable = {}
|
|
||||||
}
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
saveField: ({ originalName, field, primaryDisplay = false, indexes }) => {
|
|
||||||
store.update(state => {
|
|
||||||
// delete the original if renaming
|
|
||||||
// need to handle if the column had no name, empty string
|
|
||||||
if (originalName || originalName === "") {
|
|
||||||
delete state.draftTable.schema[originalName]
|
|
||||||
state.draftTable._rename = {
|
|
||||||
old: originalName,
|
|
||||||
updated: field.name,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optionally set display column
|
|
||||||
if (primaryDisplay) {
|
|
||||||
state.draftTable.primaryDisplay = field.name
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexes) {
|
|
||||||
state.draftTable.indexes = indexes
|
|
||||||
}
|
|
||||||
|
|
||||||
state.draftTable.schema[field.name] = cloneDeep(field)
|
|
||||||
store.actions.tables.save(state.draftTable)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
deleteField: field => {
|
|
||||||
store.update(state => {
|
|
||||||
delete state.draftTable.schema[field.name]
|
|
||||||
store.actions.tables.save(state.draftTable)
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
views: {
|
|
||||||
select: view =>
|
|
||||||
store.update(state => {
|
|
||||||
state.selectedView = view
|
|
||||||
state.selectedTable = {}
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
delete: async view => {
|
|
||||||
await api.delete(`/api/views/${view}`)
|
|
||||||
await store.actions.tables.fetch()
|
|
||||||
},
|
|
||||||
save: async view => {
|
|
||||||
const response = await api.post(`/api/views`, view)
|
|
||||||
const json = await response.json()
|
|
||||||
|
|
||||||
const viewMeta = {
|
|
||||||
name: view.name,
|
|
||||||
...json,
|
|
||||||
}
|
|
||||||
|
|
||||||
store.update(state => {
|
|
||||||
const viewTable = state.tables.find(
|
|
||||||
table => table._id === view.tableId
|
|
||||||
)
|
|
||||||
|
|
||||||
if (view.originalName) delete viewTable.views[view.originalName]
|
|
||||||
viewTable.views[view.name] = viewMeta
|
|
||||||
|
|
||||||
state.tables = state.tables
|
|
||||||
state.selectedView = viewMeta
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
},
|
|
||||||
users: {
|
|
||||||
create: user =>
|
|
||||||
store.update(state => {
|
|
||||||
state.users.push(user)
|
|
||||||
state.users = state.users
|
|
||||||
return state
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
roles: {
|
|
||||||
fetch: async () => {
|
|
||||||
const response = await api.get("/api/roles")
|
|
||||||
const roles = await response.json()
|
|
||||||
store.update(state => {
|
|
||||||
state.roles = roles
|
|
||||||
return state
|
|
||||||
})
|
|
||||||
},
|
|
||||||
delete: async role => {
|
|
||||||
const response = await api.delete(`/api/roles/${role._id}/${role._rev}`)
|
|
||||||
await store.actions.roles.fetch()
|
|
||||||
return response
|
|
||||||
},
|
|
||||||
save: async role => {
|
|
||||||
const response = await api.post("/api/roles", role)
|
|
||||||
await store.actions.roles.fetch()
|
|
||||||
return response
|
|
||||||
},
|
|
||||||
},
|
|
||||||
permissions: {
|
|
||||||
fetchLevels: async () => {
|
|
||||||
const response = await api.get("/api/permission/levels")
|
|
||||||
const json = await response.json()
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
forResource: async resourceId => {
|
|
||||||
const response = await api.get(`/api/permission/${resourceId}`)
|
|
||||||
const json = await response.json()
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
save: async ({ role, resource, level }) => {
|
|
||||||
const response = await api.post(
|
|
||||||
`/api/permission/${role}/${resource}/${level}`
|
|
||||||
)
|
|
||||||
const json = await response.json()
|
|
||||||
return json
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return store
|
|
||||||
}
|
|
Loading…
Reference in New Issue