Add initial new UI for views
This commit is contained in:
parent
0109fce51d
commit
fa80d99139
|
@ -57,64 +57,52 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<Grid
|
||||
{API}
|
||||
{darkMode}
|
||||
datasource={gridDatasource}
|
||||
canAddRows={!isUsersTable}
|
||||
canDeleteRows={!isUsersTable}
|
||||
canEditRows={!isUsersTable || !$appStore.features.disableUserMetadata}
|
||||
canEditColumns={!isUsersTable || !$appStore.features.disableUserMetadata}
|
||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||
showAvatars={false}
|
||||
on:updatedatasource={handleGridTableUpdate}
|
||||
isCloud={$admin.cloud}
|
||||
>
|
||||
<svelte:fragment slot="filter">
|
||||
{#if isUsersTable && $appStore.features.disableUserMetadata}
|
||||
<GridUsersTableButton />
|
||||
{/if}
|
||||
<GridFilterButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="controls">
|
||||
{#if !isUsersTable}
|
||||
<GridCreateViewButton />
|
||||
{/if}
|
||||
<GridManageAccessButton />
|
||||
{#if !isUsersTable}
|
||||
<GridCreateAutomationButton />
|
||||
{/if}
|
||||
{#if relationshipsEnabled}
|
||||
<GridRelationshipButton />
|
||||
{/if}
|
||||
{#if isUsersTable}
|
||||
<EditRolesButton />
|
||||
{:else}
|
||||
<GridImportButton />
|
||||
{/if}
|
||||
<GridExportButton />
|
||||
{#if isUsersTable}
|
||||
<GridEditUserModal />
|
||||
{:else}
|
||||
<GridCreateEditRowModal />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="edit-column">
|
||||
<GridEditColumnModal />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="add-column">
|
||||
<GridAddColumnModal />
|
||||
</svelte:fragment>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1 1 auto;
|
||||
margin: -28px -40px -40px -40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
<Grid
|
||||
{API}
|
||||
{darkMode}
|
||||
datasource={gridDatasource}
|
||||
canAddRows={!isUsersTable}
|
||||
canDeleteRows={!isUsersTable}
|
||||
canEditRows={!isUsersTable || !$appStore.features.disableUserMetadata}
|
||||
canEditColumns={!isUsersTable || !$appStore.features.disableUserMetadata}
|
||||
schemaOverrides={isUsersTable ? userSchemaOverrides : null}
|
||||
showAvatars={false}
|
||||
on:updatedatasource={handleGridTableUpdate}
|
||||
isCloud={$admin.cloud}
|
||||
>
|
||||
<svelte:fragment slot="filter">
|
||||
{#if isUsersTable && $appStore.features.disableUserMetadata}
|
||||
<GridUsersTableButton />
|
||||
{/if}
|
||||
<GridFilterButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="controls">
|
||||
{#if !isUsersTable}
|
||||
<GridCreateViewButton />
|
||||
{/if}
|
||||
<GridManageAccessButton />
|
||||
{#if !isUsersTable}
|
||||
<GridCreateAutomationButton />
|
||||
{/if}
|
||||
{#if relationshipsEnabled}
|
||||
<GridRelationshipButton />
|
||||
{/if}
|
||||
{#if isUsersTable}
|
||||
<EditRolesButton />
|
||||
{:else}
|
||||
<GridImportButton />
|
||||
{/if}
|
||||
<GridExportButton />
|
||||
{#if isUsersTable}
|
||||
<GridEditUserModal />
|
||||
{:else}
|
||||
<GridCreateEditRowModal />
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="edit-column">
|
||||
<GridEditColumnModal />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="add-column">
|
||||
<GridAddColumnModal />
|
||||
</svelte:fragment>
|
||||
</Grid>
|
||||
|
|
|
@ -19,34 +19,21 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<Grid
|
||||
{API}
|
||||
{datasource}
|
||||
allowAddRows
|
||||
allowDeleteRows
|
||||
showAvatars={false}
|
||||
on:updatedatasource={handleGridViewUpdate}
|
||||
isCloud={$admin.cloud}
|
||||
allowViewReadonlyColumns={$licensing.isViewReadonlyColumnsEnabled}
|
||||
>
|
||||
<svelte:fragment slot="filter">
|
||||
<GridFilterButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="controls">
|
||||
<GridCreateEditRowModal />
|
||||
<GridManageAccessButton />
|
||||
</svelte:fragment>
|
||||
</Grid>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1 1 auto;
|
||||
margin: -28px -40px -40px -40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background);
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<Grid
|
||||
{API}
|
||||
{datasource}
|
||||
allowAddRows
|
||||
allowDeleteRows
|
||||
showAvatars={false}
|
||||
on:updatedatasource={handleGridViewUpdate}
|
||||
isCloud={$admin.cloud}
|
||||
allowViewReadonlyColumns={$licensing.isViewReadonlyColumnsEnabled}
|
||||
>
|
||||
<svelte:fragment slot="filter">
|
||||
<GridFilterButton />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="controls">
|
||||
<GridCreateEditRowModal />
|
||||
<GridManageAccessButton />
|
||||
</svelte:fragment>
|
||||
</Grid>
|
||||
|
|
|
@ -104,7 +104,7 @@
|
|||
</InlineAlert>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="fourthWarning">Please enter the app name below to confirm.</p>
|
||||
<p class="fourthWarning">Please enter the table name below to confirm.</p>
|
||||
<Input bind:value={deleteTableName} placeholder={table.name} />
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
|
|
|
@ -20,14 +20,6 @@
|
|||
|
||||
const getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteConfirmationModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
|
@ -36,6 +28,14 @@
|
|||
disabled: false,
|
||||
callback: editModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteConfirmationModal.show,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import TableNavItem from "./TableNavItem/TableNavItem.svelte"
|
||||
import ViewNavItem from "./ViewNavItem/ViewNavItem.svelte"
|
||||
|
||||
export let tables
|
||||
export let selectTable
|
||||
|
@ -16,18 +14,5 @@
|
|||
<div class="hierarchy-items-container">
|
||||
{#each sortedTables as table, idx}
|
||||
<TableNavItem {table} {idx} on:click={() => selectTable(table._id)} />
|
||||
{#each [...Object.entries(table.views || {})].sort() as [name, view], idx (idx)}
|
||||
<ViewNavItem
|
||||
{view}
|
||||
{name}
|
||||
on:click={() => {
|
||||
if (view.version === 2) {
|
||||
$goto(`./view/v2/${encodeURIComponent(view.id)}`)
|
||||
} else {
|
||||
$goto(`./view/v1/${encodeURIComponent(name)}`)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -1,71 +0,0 @@
|
|||
<script>
|
||||
import {
|
||||
contextMenuStore,
|
||||
views,
|
||||
viewsV2,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { isActive } from "@roxi/routify"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import EditViewModal from "./EditViewModal.svelte"
|
||||
import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte"
|
||||
|
||||
export let view
|
||||
export let name
|
||||
|
||||
let editModal
|
||||
let deleteConfirmationModal
|
||||
|
||||
const getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteConfirmationModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: editModal.show,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getContextMenuItems()
|
||||
contextMenuStore.open(view.id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
|
||||
const isViewActive = (view, isActive, views, viewsV2) => {
|
||||
return (
|
||||
(isActive("./view/v1") && views.selected?.name === view.name) ||
|
||||
(isActive("./view/v2") && viewsV2.selected?.id === view.id)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
on:contextmenu={openContextMenu}
|
||||
indentLevel={2}
|
||||
icon="Remove"
|
||||
text={name}
|
||||
selected={isViewActive(view, $isActive, $views, $viewsV2)}
|
||||
hovering={view.id === $contextMenuStore.id}
|
||||
on:click
|
||||
selectedBy={$userSelectedResourceMap[name] ||
|
||||
$userSelectedResourceMap[view.id]}
|
||||
>
|
||||
<Icon on:click={openContextMenu} s hoverable name="MoreSmallList" />
|
||||
</NavItem>
|
||||
<EditViewModal {view} bind:this={editModal} />
|
||||
<DeleteConfirmationModal {view} bind:this={deleteConfirmationModal} />
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { viewsV2, builderStore } from "stores/builder"
|
||||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
$: id = $viewsV2.selectedViewId
|
||||
$: builderStore.selectResource(id)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "viewId",
|
||||
stateKey: "selectedViewId",
|
||||
validate: id => $viewsV2.list?.some(view => view.id === id),
|
||||
update: viewsV2.select,
|
||||
fallbackUrl: "../",
|
||||
store: viewsV2,
|
||||
routify,
|
||||
decode: decodeURIComponent,
|
||||
})
|
||||
|
||||
onDestroy(stopSyncing)
|
||||
</script>
|
||||
|
||||
<slot />
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import ViewV2DataTable from "components/backend/DataTable/ViewV2DataTable.svelte"
|
||||
</script>
|
||||
|
||||
<ViewV2DataTable />
|
|
@ -0,0 +1,200 @@
|
|||
<script>
|
||||
import {
|
||||
tables,
|
||||
datasources,
|
||||
userSelectedResourceMap,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { params, url } from "@roxi/routify"
|
||||
import EditViewModal from "./EditViewModal.svelte"
|
||||
import DeleteViewModal from "./DeleteViewModal.svelte"
|
||||
import EditTableModal from "components/backend/TableNavigator/TableNavItem/EditModal.svelte"
|
||||
import DeleteTableModal from "components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte"
|
||||
import { UserAvatars } from "@budibase/frontend-core"
|
||||
import { tick } from "svelte"
|
||||
import { DB_TYPE_EXTERNAL } from "constants/backend"
|
||||
import { TableNames } from "constants"
|
||||
|
||||
// Editing table
|
||||
let editTableModal
|
||||
let deleteTableModal
|
||||
|
||||
// Editing views
|
||||
let editableView
|
||||
let editViewModal
|
||||
let deleteViewModal
|
||||
|
||||
$: tableId = $params.tableId
|
||||
$: table = $tables.list.find(x => x._id === tableId)
|
||||
$: datasource = $datasources.list.find(x => x._id === table?.sourceId)
|
||||
$: tableSelectedBy = $userSelectedResourceMap[table?._id]
|
||||
$: tableEditable = table?._id !== TableNames.USERS
|
||||
$: activeId = $params.viewId ?? $params.tableId
|
||||
$: views = Object.values(table?.views || {})
|
||||
.filter(x => x.version === 2)
|
||||
.slice()
|
||||
.sort(alphabetical)
|
||||
|
||||
const openTableContextMenu = e => {
|
||||
if (!tableEditable) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
contextMenuStore.open(
|
||||
table._id,
|
||||
[
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: table?.sourceType !== DB_TYPE_EXTERNAL,
|
||||
disabled: false,
|
||||
callback: editTableModal?.show,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteTableModal?.show,
|
||||
},
|
||||
],
|
||||
{
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const openViewContextMenu = async (e, view) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
editableView = view
|
||||
await tick()
|
||||
contextMenuStore.open(
|
||||
view.id,
|
||||
[
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: editViewModal?.show,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteViewModal?.show,
|
||||
},
|
||||
],
|
||||
{
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
const alphabetical = (a, b) => {
|
||||
return a.name < b.name ? -1 : 1
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="view-nav-bar">
|
||||
<IntegrationIcon
|
||||
integrationType={datasource.source}
|
||||
schema={datasource.schema}
|
||||
size="24"
|
||||
/>
|
||||
<a
|
||||
href={$url(`../${tableId}`)}
|
||||
class="nav-bar-item"
|
||||
class:active={tableId === activeId}
|
||||
on:contextmenu={openTableContextMenu}
|
||||
>
|
||||
<div class="title">
|
||||
{table.name}
|
||||
</div>
|
||||
{#if tableSelectedBy}
|
||||
<UserAvatars size="XS" users={tableSelectedBy} />
|
||||
{/if}
|
||||
{#if tableEditable}
|
||||
<Icon on:click={openTableContextMenu} s hoverable name="MoreSmallList" />
|
||||
{/if}
|
||||
</a>
|
||||
{#each views as view (view.id)}
|
||||
{@const selectedBy = $userSelectedResourceMap[view.id]}
|
||||
<a
|
||||
href={$url(`../${tableId}/${encodeURIComponent(view.id)}`)}
|
||||
class="nav-bar-item"
|
||||
class:active={view.id === activeId}
|
||||
on:contextmenu={e => openViewContextMenu(e, view)}
|
||||
>
|
||||
<div class="title">
|
||||
{view.name}
|
||||
</div>
|
||||
{#if selectedBy}
|
||||
<UserAvatars size="XS" users={selectedBy} />
|
||||
{/if}
|
||||
<Icon
|
||||
on:click={e => openViewContextMenu(e, view)}
|
||||
s
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
/>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
{#if table}
|
||||
<EditTableModal {table} bind:this={editTableModal} />
|
||||
<DeleteTableModal {table} bind:this={deleteTableModal} />
|
||||
{/if}
|
||||
|
||||
{#if editableView}
|
||||
<EditViewModal view={editableView} bind:this={editViewModal} />
|
||||
<DeleteViewModal view={editableView} bind:this={deleteViewModal} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.view-nav-bar {
|
||||
height: 50px;
|
||||
border-bottom: var(--border-light);
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding: 0 var(--spacing-xl);
|
||||
gap: var(--spacing-m);
|
||||
}
|
||||
.nav-bar-item {
|
||||
padding: 6px 8px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: var(--spacing-m);
|
||||
transition: background 130ms ease-out, color 130ms ease-out;
|
||||
color: var(--spectrum-global-color-gray-700);
|
||||
}
|
||||
.title {
|
||||
font-size: 16px;
|
||||
}
|
||||
.nav-bar-item.active,
|
||||
.nav-bar-item:hover {
|
||||
color: var(--spectrum-global-color-gray-900);
|
||||
background: var(--spectrum-global-color-gray-300);
|
||||
cursor: pointer;
|
||||
}
|
||||
.nav-bar-item:not(.active) :global(.icon) {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
|
@ -3,6 +3,7 @@
|
|||
import { tables, builderStore } from "stores/builder"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
import ViewNavBar from "./_components/ViewNavBar.svelte"
|
||||
|
||||
$: tableId = $tables.selectedTableId
|
||||
$: builderStore.selectResource(tableId)
|
||||
|
@ -20,4 +21,17 @@
|
|||
onDestroy(stopSyncing)
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
<div class="wrapper">
|
||||
<ViewNavBar />
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.wrapper {
|
||||
flex: 1 1 auto;
|
||||
margin: -28px -40px -40px -40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: var(--background);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import { views, viewsV2 } from "stores/builder"
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
onMount(async () => {
|
||||
if ($viewsV2.selected) {
|
||||
$redirect(`./v2/${$viewsV2.selected.id}`)
|
||||
} else if ($viewsV2.list?.length) {
|
||||
$redirect(`./v2/${$viewsV2.list[0].id}`)
|
||||
} else if ($views.selected) {
|
||||
$redirect(`./${encodeURIComponent($views.selected?.name)}`)
|
||||
} else if ($views.list?.length) {
|
||||
$redirect(`./${encodeURIComponent($views.list[0].name)}`)
|
||||
} else {
|
||||
$redirect("../")
|
||||
}
|
||||
})
|
||||
</script>
|
|
@ -0,0 +1,24 @@
|
|||
<script>
|
||||
import { views, builderStore } from "stores/builder"
|
||||
import { syncURLToState } from "helpers/urlStateSync"
|
||||
import * as routify from "@roxi/routify"
|
||||
import { onDestroy } from "svelte"
|
||||
|
||||
$: name = $views.selectedViewName
|
||||
$: builderStore.selectResource(name)
|
||||
|
||||
const stopSyncing = syncURLToState({
|
||||
urlParam: "viewName",
|
||||
stateKey: "selectedViewName",
|
||||
validate: name => $views.list?.some(view => view.name === name),
|
||||
update: views.select,
|
||||
fallbackUrl: "../../",
|
||||
store: views,
|
||||
routify,
|
||||
decode: decodeURIComponent,
|
||||
})
|
||||
|
||||
onDestroy(stopSyncing)
|
||||
</script>
|
||||
|
||||
<slot />
|
|
@ -0,0 +1,18 @@
|
|||
<script>
|
||||
import ViewDataTable from "components/backend/DataTable/ViewDataTable.svelte"
|
||||
import { views } from "stores/builder"
|
||||
|
||||
$: selectedView = $views.selected
|
||||
</script>
|
||||
|
||||
{#if selectedView}
|
||||
<ViewDataTable view={selectedView} />
|
||||
{:else}<i>Create your first table to start building</i>{/if}
|
||||
|
||||
<style>
|
||||
i {
|
||||
font-size: var(--font-size-m);
|
||||
color: var(--grey-4);
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,5 @@
|
|||
<script>
|
||||
import { redirect } from "@roxi/routify"
|
||||
|
||||
$redirect("../")
|
||||
</script>
|
Loading…
Reference in New Issue