Merge branch 'master' of github.com:Budibase/budibase into conditional-table-colours
This commit is contained in:
commit
a14a737921
|
@ -29,6 +29,7 @@
|
|||
>
|
||||
<div class="icon" class:newStyles>
|
||||
<svg
|
||||
on:contextmenu
|
||||
on:click
|
||||
class:hoverable
|
||||
class:disabled
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<script>
|
||||
import { contextMenuStore } from "stores/builder"
|
||||
import { Popover, Menu, MenuItem } from "@budibase/bbui"
|
||||
|
||||
let dropdown
|
||||
let anchor
|
||||
|
||||
const handleKeyDown = () => {
|
||||
if ($contextMenuStore.visible) {
|
||||
contextMenuStore.close()
|
||||
}
|
||||
}
|
||||
|
||||
const handleItemClick = async itemCallback => {
|
||||
await itemCallback()
|
||||
contextMenuStore.close()
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={handleKeyDown} />
|
||||
{#key $contextMenuStore.position}
|
||||
<div
|
||||
bind:this={anchor}
|
||||
class="anchor"
|
||||
style:top={`${$contextMenuStore.position.y}px`}
|
||||
style:left={`${$contextMenuStore.position.x}px`}
|
||||
/>
|
||||
{/key}
|
||||
|
||||
<Popover
|
||||
open={$contextMenuStore.visible}
|
||||
animate={false}
|
||||
bind:this={dropdown}
|
||||
{anchor}
|
||||
resizable={false}
|
||||
align="left"
|
||||
on:close={contextMenuStore.close}
|
||||
>
|
||||
<Menu>
|
||||
{#each $contextMenuStore.items as item}
|
||||
{#if item.visible}
|
||||
<MenuItem
|
||||
icon={item.icon}
|
||||
keyBind={item.keyBind}
|
||||
on:click={() => handleItemClick(item.callback)}
|
||||
disabled={item.disabled}
|
||||
>
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
{/if}
|
||||
{/each}
|
||||
</Menu>
|
||||
</Popover>
|
||||
|
||||
<style>
|
||||
.anchor {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,48 +0,0 @@
|
|||
<script>
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
automationStore,
|
||||
selectedAutomation,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
$: selectedAutomationId = $selectedAutomation?._id
|
||||
|
||||
onMount(async () => {
|
||||
try {
|
||||
await automationStore.actions.fetch()
|
||||
} catch (error) {
|
||||
notifications.error("Error getting automations list")
|
||||
}
|
||||
})
|
||||
|
||||
function selectAutomation(id) {
|
||||
automationStore.actions.select(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="automations-list">
|
||||
{#each $automationStore.automations.sort(aut => aut.name) as automation}
|
||||
<NavItem
|
||||
icon="ShareAndroid"
|
||||
text={automation.name}
|
||||
selected={automation._id === selectedAutomationId}
|
||||
on:click={() => selectAutomation(automation._id)}
|
||||
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||
>
|
||||
<EditAutomationPopover {automation} />
|
||||
</NavItem>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.automations-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: stretch;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
<script>
|
||||
import {
|
||||
selectedAutomation,
|
||||
userSelectedResourceMap,
|
||||
automationStore,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import { notifications, Icon } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import UpdateAutomationModal from "components/automation/AutomationPanel/UpdateAutomationModal.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
|
||||
export let automation
|
||||
export let icon
|
||||
|
||||
let confirmDeleteDialog
|
||||
let updateAutomationDialog
|
||||
|
||||
async function deleteAutomation() {
|
||||
try {
|
||||
await automationStore.actions.delete(automation)
|
||||
notifications.success("Automation deleted successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting automation")
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateAutomation() {
|
||||
try {
|
||||
await automationStore.actions.duplicate(automation)
|
||||
notifications.success("Automation has been duplicated successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error duplicating automation")
|
||||
}
|
||||
}
|
||||
|
||||
const getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: confirmDeleteDialog.show,
|
||||
},
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: updateAutomationDialog.show,
|
||||
},
|
||||
{
|
||||
icon: "Duplicate",
|
||||
name: "Duplicate",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: automation.definition.trigger.name === "Webhook",
|
||||
callback: duplicateAutomation,
|
||||
},
|
||||
{
|
||||
icon: automation.disabled ? "CheckmarkCircle" : "Cancel",
|
||||
name: automation.disabled ? "Activate" : "Pause",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => {
|
||||
automationStore.actions.toggleDisabled(
|
||||
automation._id,
|
||||
automation.disabled
|
||||
)
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getContextMenuItems()
|
||||
contextMenuStore.open(automation._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
on:contextmenu={openContextMenu}
|
||||
{icon}
|
||||
iconColor={"var(--spectrum-global-color-gray-900)"}
|
||||
text={automation.name}
|
||||
selected={automation._id === $selectedAutomation?._id}
|
||||
hovering={automation._id === $contextMenuStore.id}
|
||||
on:click={() => automationStore.actions.select(automation._id)}
|
||||
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||
disabled={automation.disabled}
|
||||
>
|
||||
<div class="icon">
|
||||
<Icon on:click={openContextMenu} size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
</NavItem>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Automation"
|
||||
onOk={deleteAutomation}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete the automation
|
||||
<i>{automation.name}?</i>
|
||||
This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
<UpdateAutomationModal {automation} bind:this={updateAutomationDialog} />
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -3,20 +3,13 @@
|
|||
import { Modal, notifications, Layout } from "@budibase/bbui"
|
||||
import NavHeader from "components/common/NavHeader.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import {
|
||||
automationStore,
|
||||
selectedAutomation,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import EditAutomationPopover from "./EditAutomationPopover.svelte"
|
||||
import { automationStore } from "stores/builder"
|
||||
import AutomationNavItem from "./AutomationNavItem.svelte"
|
||||
|
||||
export let modal
|
||||
export let webhookModal
|
||||
let searchString
|
||||
|
||||
$: selectedAutomationId = $selectedAutomation?._id
|
||||
|
||||
$: filteredAutomations = $automationStore.automations
|
||||
.filter(automation => {
|
||||
return (
|
||||
|
@ -49,10 +42,6 @@
|
|||
notifications.error("Error getting automations list")
|
||||
}
|
||||
})
|
||||
|
||||
function selectAutomation(id) {
|
||||
automationStore.actions.select(id)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="side-bar">
|
||||
|
@ -71,17 +60,7 @@
|
|||
{triggerGroup?.name}
|
||||
</div>
|
||||
{#each triggerGroup.entries as automation}
|
||||
<NavItem
|
||||
icon={triggerGroup.icon}
|
||||
iconColor={"var(--spectrum-global-color-gray-900)"}
|
||||
text={automation.name}
|
||||
selected={automation._id === selectedAutomationId}
|
||||
on:click={() => selectAutomation(automation._id)}
|
||||
selectedBy={$userSelectedResourceMap[automation._id]}
|
||||
disabled={automation.disabled}
|
||||
>
|
||||
<EditAutomationPopover {automation} />
|
||||
</NavItem>
|
||||
<AutomationNavItem {automation} icon={triggerGroup.icon} />
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
<script>
|
||||
import { automationStore } from "stores/builder"
|
||||
import { ActionMenu, MenuItem, notifications, Icon } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import UpdateAutomationModal from "components/automation/AutomationPanel/UpdateAutomationModal.svelte"
|
||||
|
||||
export let automation
|
||||
|
||||
let confirmDeleteDialog
|
||||
let updateAutomationDialog
|
||||
|
||||
async function deleteAutomation() {
|
||||
try {
|
||||
await automationStore.actions.delete(automation)
|
||||
notifications.success("Automation deleted successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting automation")
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateAutomation() {
|
||||
try {
|
||||
await automationStore.actions.duplicate(automation)
|
||||
notifications.success("Automation has been duplicated successfully")
|
||||
} catch (error) {
|
||||
notifications.error("Error duplicating automation")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem
|
||||
icon="Duplicate"
|
||||
on:click={duplicateAutomation}
|
||||
disabled={automation.definition.trigger.name === "Webhook"}
|
||||
>Duplicate</MenuItem
|
||||
>
|
||||
<MenuItem icon="Edit" on:click={updateAutomationDialog.show}>Edit</MenuItem>
|
||||
<MenuItem
|
||||
icon={automation.disabled ? "CheckmarkCircle" : "Cancel"}
|
||||
on:click={automationStore.actions.toggleDisabled(
|
||||
automation._id,
|
||||
automation.disabled
|
||||
)}
|
||||
>
|
||||
{automation.disabled ? "Activate" : "Pause"}
|
||||
</MenuItem>
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Automation"
|
||||
onOk={deleteAutomation}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete the automation
|
||||
<i>{automation.name}?</i>
|
||||
This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
<UpdateAutomationModal {automation} bind:this={updateAutomationDialog} />
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -79,18 +79,18 @@
|
|||
|
||||
table = $tables.list.find(table => table._id === tableId)
|
||||
|
||||
schemaFields = Object.entries(table?.schema ?? {})
|
||||
.filter(entry => {
|
||||
const [, field] = entry
|
||||
return field.type !== "formula" && !field.autocolumn
|
||||
})
|
||||
.sort(([nameA], [nameB]) => {
|
||||
return nameA < nameB ? -1 : 1
|
||||
})
|
||||
|
||||
if (table) {
|
||||
editableRow["tableId"] = tableId
|
||||
|
||||
schemaFields = Object.entries(table?.schema ?? {})
|
||||
.filter(entry => {
|
||||
const [, field] = entry
|
||||
return field.type !== "formula" && !field.autocolumn
|
||||
})
|
||||
.sort(([nameA], [nameB]) => {
|
||||
return nameA < nameB ? -1 : 1
|
||||
})
|
||||
|
||||
// Parse out any data not in the schema.
|
||||
for (const column in editableFields) {
|
||||
if (!Object.hasOwn(table?.schema, column)) {
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
<script>
|
||||
import { isActive } from "@roxi/routify"
|
||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||
import { contextMenuStore, userSelectedResourceMap } from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
|
||||
import IntegrationIcon from "components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import UpdateDatasourceModal from "components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte"
|
||||
import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte"
|
||||
|
||||
export let datasource
|
||||
|
||||
let editModal
|
||||
let deleteConfirmationModal
|
||||
|
||||
const getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
icon: "Edit",
|
||||
name: "Edit",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: editModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: deleteConfirmationModal.show,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
if (datasource._id === BUDIBASE_INTERNAL_DB_ID) {
|
||||
return
|
||||
}
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getContextMenuItems()
|
||||
contextMenuStore.open(datasource._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
on:contextmenu={openContextMenu}
|
||||
border
|
||||
text={datasource.name}
|
||||
opened={datasource.open}
|
||||
selected={$isActive("./datasource") && datasource.selected}
|
||||
hovering={datasource._id === $contextMenuStore.id}
|
||||
withArrow={true}
|
||||
on:click
|
||||
on:iconClick
|
||||
selectedBy={$userSelectedResourceMap[datasource._id]}
|
||||
>
|
||||
<div class="datasource-icon" slot="icon">
|
||||
<IntegrationIcon
|
||||
integrationType={datasource.source}
|
||||
schema={datasource.schema}
|
||||
size="18"
|
||||
/>
|
||||
</div>
|
||||
{#if datasource._id !== BUDIBASE_INTERNAL_DB_ID}
|
||||
<Icon on:click={openContextMenu} size="S" hoverable name="MoreSmallList" />
|
||||
{/if}
|
||||
</NavItem>
|
||||
<UpdateDatasourceModal {datasource} bind:this={editModal} />
|
||||
<DeleteConfirmationModal {datasource} bind:this={deleteConfirmationModal} />
|
||||
|
||||
<style>
|
||||
.datasource-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
</style>
|
|
@ -1,15 +1,16 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { datasources } from "stores/builder"
|
||||
import { notifications, ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import UpdateDatasourceModal from "components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte"
|
||||
import { BUDIBASE_DATASOURCE_TYPE } from "constants/backend"
|
||||
|
||||
export let datasource
|
||||
|
||||
let confirmDeleteDialog
|
||||
let updateDatasourceDialog
|
||||
|
||||
export const show = () => {
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteDatasource() {
|
||||
try {
|
||||
|
@ -25,16 +26,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
{#if datasource.type !== BUDIBASE_DATASOURCE_TYPE}
|
||||
<MenuItem icon="Edit" on:click={updateDatasourceDialog.show}>Edit</MenuItem>
|
||||
{/if}
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Datasource"
|
||||
|
@ -45,13 +36,3 @@
|
|||
<i>{datasource.name}?</i>
|
||||
This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
<UpdateDatasourceModal {datasource} bind:this={updateDatasourceDialog} />
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import { goto, isActive, params } from "@roxi/routify"
|
||||
import { Layout } from "@budibase/bbui"
|
||||
import { BUDIBASE_INTERNAL_DB_ID } from "constants/backend"
|
||||
import {
|
||||
datasources,
|
||||
queries,
|
||||
|
@ -10,16 +9,10 @@
|
|||
viewsV2,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import EditDatasourcePopover from "./popovers/EditDatasourcePopover.svelte"
|
||||
import EditQueryPopover from "./popovers/EditQueryPopover.svelte"
|
||||
import QueryNavItem from "./QueryNavItem.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import TableNavigator from "components/backend/TableNavigator/TableNavigator.svelte"
|
||||
import {
|
||||
customQueryIconText,
|
||||
customQueryIconColor,
|
||||
customQueryText,
|
||||
} from "helpers/data/utils"
|
||||
import IntegrationIcon from "./IntegrationIcon.svelte"
|
||||
import DatasourceNavItem from "./DatasourceNavItem/DatasourceNavItem.svelte"
|
||||
import { TableNames } from "constants"
|
||||
import { enrichDatasources } from "./datasourceUtils"
|
||||
import { onMount } from "svelte"
|
||||
|
@ -86,44 +79,15 @@
|
|||
/>
|
||||
{/if}
|
||||
{#each enrichedDataSources.filter(ds => ds.show) as datasource}
|
||||
<NavItem
|
||||
border
|
||||
text={datasource.name}
|
||||
opened={datasource.open}
|
||||
selected={$isActive("./datasource") && datasource.selected}
|
||||
withArrow={true}
|
||||
<DatasourceNavItem
|
||||
{datasource}
|
||||
on:click={() => selectDatasource(datasource)}
|
||||
on:iconClick={() => toggleNode(datasource)}
|
||||
selectedBy={$userSelectedResourceMap[datasource._id]}
|
||||
>
|
||||
<div class="datasource-icon" slot="icon">
|
||||
<IntegrationIcon
|
||||
integrationType={datasource.source}
|
||||
schema={datasource.schema}
|
||||
size="18"
|
||||
/>
|
||||
</div>
|
||||
{#if datasource._id !== BUDIBASE_INTERNAL_DB_ID}
|
||||
<EditDatasourcePopover {datasource} />
|
||||
{/if}
|
||||
</NavItem>
|
||||
|
||||
/>
|
||||
{#if datasource.open}
|
||||
<TableNavigator tables={datasource.tables} {selectTable} />
|
||||
{#each datasource.queries as query}
|
||||
<NavItem
|
||||
indentLevel={1}
|
||||
icon="SQLQuery"
|
||||
iconText={customQueryIconText(datasource, query)}
|
||||
iconColor={customQueryIconColor(datasource, query)}
|
||||
text={customQueryText(datasource, query)}
|
||||
selected={$isActive("./query/:queryId") &&
|
||||
$queries.selectedQueryId === query._id}
|
||||
on:click={() => $goto(`./query/${query._id}`)}
|
||||
selectedBy={$userSelectedResourceMap[query._id]}
|
||||
>
|
||||
<EditQueryPopover {query} />
|
||||
</NavItem>
|
||||
<QueryNavItem {datasource} {query} />
|
||||
{/each}
|
||||
{/if}
|
||||
{/each}
|
||||
|
@ -140,11 +104,6 @@
|
|||
.hierarchy-items-container {
|
||||
margin: 0 calc(-1 * var(--spacing-l));
|
||||
}
|
||||
.datasource-icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: 0 0 24px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
<script>
|
||||
import {
|
||||
customQueryIconText,
|
||||
customQueryIconColor,
|
||||
customQueryText,
|
||||
} from "helpers/data/utils"
|
||||
import { goto as gotoStore, isActive } from "@roxi/routify"
|
||||
import {
|
||||
datasources,
|
||||
queries,
|
||||
userSelectedResourceMap,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { notifications, Icon } from "@budibase/bbui"
|
||||
|
||||
export let datasource
|
||||
export let query
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
// goto won't work in the context menu callback if the store is called directly
|
||||
$: goto = $gotoStore
|
||||
|
||||
const getContextMenuItems = () => {
|
||||
return [
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: confirmDeleteDialog.show,
|
||||
},
|
||||
{
|
||||
icon: "Duplicate",
|
||||
name: "Duplicate",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: async () => {
|
||||
try {
|
||||
const newQuery = await queries.duplicate(query)
|
||||
goto(`./query/${newQuery._id}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error duplicating query")
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async function deleteQuery() {
|
||||
try {
|
||||
// Go back to the datasource if we are deleting the active query
|
||||
if ($queries.selectedQueryId === query._id) {
|
||||
goto(`./datasource/${query.datasourceId}`)
|
||||
}
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
notifications.success("Query deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting query")
|
||||
}
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getContextMenuItems()
|
||||
contextMenuStore.open(query._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
on:contextmenu={openContextMenu}
|
||||
indentLevel={1}
|
||||
icon="SQLQuery"
|
||||
iconText={customQueryIconText(datasource, query)}
|
||||
iconColor={customQueryIconColor(datasource, query)}
|
||||
text={customQueryText(datasource, query)}
|
||||
selected={$isActive("./query/:queryId") &&
|
||||
$queries.selectedQueryId === query._id}
|
||||
hovering={query._id === $contextMenuStore.id}
|
||||
on:click={() => goto(`./query/${query._id}`)}
|
||||
selectedBy={$userSelectedResourceMap[query._id]}
|
||||
>
|
||||
<Icon size="S" hoverable name="MoreSmallList" on:click={openContextMenu} />
|
||||
</NavItem>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Query"
|
||||
onOk={deleteQuery}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete this query? This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -1,59 +0,0 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { datasources, queries } from "stores/builder"
|
||||
|
||||
export let query
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
async function deleteQuery() {
|
||||
try {
|
||||
// Go back to the datasource if we are deleting the active query
|
||||
if ($queries.selectedQueryId === query._id) {
|
||||
$goto(`./datasource/${query.datasourceId}`)
|
||||
}
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
notifications.success("Query deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting query")
|
||||
}
|
||||
}
|
||||
|
||||
async function duplicateQuery() {
|
||||
try {
|
||||
const newQuery = await queries.duplicate(query)
|
||||
$goto(`./query/${newQuery._id}`)
|
||||
} catch (error) {
|
||||
notifications.error("Error duplicating query")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
<MenuItem icon="Duplicate" on:click={duplicateQuery}>Duplicate</MenuItem>
|
||||
</ActionMenu>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Query"
|
||||
onOk={deleteQuery}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete this query? This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -1,35 +1,15 @@
|
|||
<script>
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { tables, datasources, screenStore } from "stores/builder"
|
||||
import {
|
||||
ActionMenu,
|
||||
Icon,
|
||||
Input,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalContent,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { Input, notifications } from "@budibase/bbui"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { DB_TYPE_EXTERNAL } from "constants/backend"
|
||||
|
||||
export let table
|
||||
|
||||
let editorModal, editTableNameModal
|
||||
let confirmDeleteDialog
|
||||
let error = ""
|
||||
|
||||
let originalName
|
||||
let updatedName
|
||||
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
let deleteTableName
|
||||
|
||||
$: externalTable = table?.sourceType === DB_TYPE_EXTERNAL
|
||||
|
||||
function showDeleteModal() {
|
||||
export const show = () => {
|
||||
templateScreens = $screenStore.screens.filter(
|
||||
screen => screen.autoTableId === table._id
|
||||
)
|
||||
|
@ -39,6 +19,10 @@
|
|||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
let templateScreens
|
||||
let willBeDeleted
|
||||
let deleteTableName
|
||||
|
||||
async function deleteTable() {
|
||||
const isSelected = $params.tableId === table._id
|
||||
try {
|
||||
|
@ -62,58 +46,8 @@
|
|||
function hideDeleteDialog() {
|
||||
deleteTableName = ""
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const updatedTable = cloneDeep(table)
|
||||
updatedTable.name = updatedName
|
||||
await tables.save(updatedTable)
|
||||
await datasources.fetch()
|
||||
notifications.success("Table renamed successfully")
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
const tableName = evt.target.value
|
||||
error =
|
||||
originalName === tableName
|
||||
? `Table with name ${tableName} already exists. Please choose another name.`
|
||||
: ""
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
originalName = table.name + ""
|
||||
updatedName = table.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
{#if !externalTable}
|
||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
{/if}
|
||||
<MenuItem icon="Delete" on:click={showDeleteModal}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent
|
||||
bind:this={editTableNameModal}
|
||||
title="Edit Table"
|
||||
confirmText="Save"
|
||||
onConfirm={save}
|
||||
disabled={updatedName === originalName || error}
|
||||
>
|
||||
<form on:submit|preventDefault={() => editTableNameModal.confirm()}>
|
||||
<Input
|
||||
label="Table Name"
|
||||
thin
|
||||
bind:value={updatedName}
|
||||
on:input={checkValid}
|
||||
{error}
|
||||
/>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Table"
|
||||
|
@ -142,13 +76,6 @@
|
|||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
div.icon {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
div.delete-items {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
|
@ -0,0 +1,58 @@
|
|||
<script>
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { tables, datasources } from "stores/builder"
|
||||
import { Input, Modal, ModalContent, notifications } from "@budibase/bbui"
|
||||
|
||||
export let table
|
||||
|
||||
export const show = () => {
|
||||
editorModal.show()
|
||||
}
|
||||
|
||||
let editorModal, editTableNameModal
|
||||
let error = ""
|
||||
|
||||
let originalName
|
||||
let updatedName
|
||||
|
||||
async function save() {
|
||||
const updatedTable = cloneDeep(table)
|
||||
updatedTable.name = updatedName
|
||||
await tables.save(updatedTable)
|
||||
await datasources.fetch()
|
||||
notifications.success("Table renamed successfully")
|
||||
}
|
||||
|
||||
function checkValid(evt) {
|
||||
const tableName = evt.target.value
|
||||
error =
|
||||
originalName === tableName
|
||||
? `Table with name ${tableName} already exists. Please choose another name.`
|
||||
: ""
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
originalName = table.name + ""
|
||||
updatedName = table.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent
|
||||
bind:this={editTableNameModal}
|
||||
title="Edit Table"
|
||||
confirmText="Save"
|
||||
onConfirm={save}
|
||||
disabled={updatedName === originalName || error}
|
||||
>
|
||||
<form on:submit|preventDefault={() => editTableNameModal.confirm()}>
|
||||
<Input
|
||||
label="Table Name"
|
||||
thin
|
||||
bind:value={updatedName}
|
||||
on:input={checkValid}
|
||||
{error}
|
||||
/>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
|
@ -0,0 +1,68 @@
|
|||
<script>
|
||||
import {
|
||||
tables as tablesStore,
|
||||
userSelectedResourceMap,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import { TableNames } from "constants"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { isActive } from "@roxi/routify"
|
||||
import EditModal from "./EditModal.svelte"
|
||||
import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { DB_TYPE_EXTERNAL } from "constants/backend"
|
||||
|
||||
export let table
|
||||
export let idx
|
||||
|
||||
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: table?.sourceType !== DB_TYPE_EXTERNAL,
|
||||
disabled: false,
|
||||
callback: editModal.show,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getContextMenuItems()
|
||||
contextMenuStore.open(table._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<NavItem
|
||||
on:contextmenu={openContextMenu}
|
||||
indentLevel={1}
|
||||
border={idx > 0}
|
||||
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
||||
text={table.name}
|
||||
hovering={table._id === $contextMenuStore.id}
|
||||
selected={$isActive("./table/:tableId") &&
|
||||
$tablesStore.selected?._id === table._id}
|
||||
selectedBy={$userSelectedResourceMap[table._id]}
|
||||
on:click
|
||||
>
|
||||
{#if table._id !== TableNames.USERS}
|
||||
<Icon s on:click={openContextMenu} hoverable name="MoreSmallList" />
|
||||
{/if}
|
||||
</NavItem>
|
||||
<EditModal {table} bind:this={editModal} />
|
||||
<DeleteConfirmationModal {table} bind:this={deleteConfirmationModal} />
|
|
@ -1,15 +1,7 @@
|
|||
<script>
|
||||
import {
|
||||
tables as tablesStore,
|
||||
views,
|
||||
viewsV2,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import { TableNames } from "constants"
|
||||
import EditTablePopover from "./popovers/EditTablePopover.svelte"
|
||||
import EditViewPopover from "./popovers/EditViewPopover.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { goto, isActive } from "@roxi/routify"
|
||||
import { goto } from "@roxi/routify"
|
||||
import TableNavItem from "./TableNavItem/TableNavItem.svelte"
|
||||
import ViewNavItem from "./ViewNavItem/ViewNavItem.svelte"
|
||||
|
||||
export let tables
|
||||
export let selectTable
|
||||
|
@ -19,37 +11,15 @@
|
|||
const alphabetical = (a, b) => {
|
||||
return a.name?.toLowerCase() > b.name?.toLowerCase() ? 1 : -1
|
||||
}
|
||||
|
||||
const isViewActive = (view, isActive, views, viewsV2) => {
|
||||
return (
|
||||
(isActive("./view/v1") && views.selected?.name === view.name) ||
|
||||
(isActive("./view/v2") && viewsV2.selected?.id === view.id)
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="hierarchy-items-container">
|
||||
{#each sortedTables as table, idx}
|
||||
<NavItem
|
||||
indentLevel={1}
|
||||
border={idx > 0}
|
||||
icon={table._id === TableNames.USERS ? "UserGroup" : "Table"}
|
||||
text={table.name}
|
||||
selected={$isActive("./table/:tableId") &&
|
||||
$tablesStore.selected?._id === table._id}
|
||||
on:click={() => selectTable(table._id)}
|
||||
selectedBy={$userSelectedResourceMap[table._id]}
|
||||
>
|
||||
{#if table._id !== TableNames.USERS}
|
||||
<EditTablePopover {table} />
|
||||
{/if}
|
||||
</NavItem>
|
||||
<TableNavItem {table} {idx} on:click={() => selectTable(table._id)} />
|
||||
{#each [...Object.entries(table.views || {})].sort() as [name, view], idx (idx)}
|
||||
<NavItem
|
||||
indentLevel={2}
|
||||
icon="Remove"
|
||||
text={name}
|
||||
selected={isViewActive(view, $isActive, $views, $viewsV2)}
|
||||
<ViewNavItem
|
||||
{view}
|
||||
{name}
|
||||
on:click={() => {
|
||||
if (view.version === 2) {
|
||||
$goto(`./view/v2/${encodeURIComponent(view.id)}`)
|
||||
|
@ -57,11 +27,7 @@
|
|||
$goto(`./view/v1/${encodeURIComponent(name)}`)
|
||||
}
|
||||
}}
|
||||
selectedBy={$userSelectedResourceMap[name] ||
|
||||
$userSelectedResourceMap[view.id]}
|
||||
>
|
||||
<EditViewPopover {view} />
|
||||
</NavItem>
|
||||
/>
|
||||
{/each}
|
||||
{/each}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<script>
|
||||
import { views, viewsV2 } from "stores/builder"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let view
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
export const show = () => {
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteView() {
|
||||
try {
|
||||
if (view.version === 2) {
|
||||
await viewsV2.delete(view)
|
||||
} else {
|
||||
await views.delete(view)
|
||||
}
|
||||
notifications.success("View deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete the view '${view.name}'? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete View"
|
||||
onOk={deleteView}
|
||||
title="Confirm Deletion"
|
||||
/>
|
|
@ -0,0 +1,45 @@
|
|||
<script>
|
||||
import { views, viewsV2 } from "stores/builder"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import { notifications, Input, Modal, ModalContent } from "@budibase/bbui"
|
||||
|
||||
export let view
|
||||
|
||||
let editorModal
|
||||
let originalName
|
||||
let updatedName
|
||||
|
||||
export const show = () => {
|
||||
editorModal.show()
|
||||
}
|
||||
|
||||
async function save() {
|
||||
const updatedView = cloneDeep(view)
|
||||
updatedView.name = updatedName
|
||||
|
||||
if (view.version === 2) {
|
||||
await viewsV2.save({
|
||||
originalName,
|
||||
...updatedView,
|
||||
})
|
||||
} else {
|
||||
await views.save({
|
||||
originalName,
|
||||
...updatedView,
|
||||
})
|
||||
}
|
||||
|
||||
notifications.success("View renamed successfully")
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
updatedName = view.name + ""
|
||||
originalName = view.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent title="Edit View" onConfirm={save} confirmText="Save">
|
||||
<Input label="View Name" thin bind:value={updatedName} />
|
||||
</ModalContent>
|
||||
</Modal>
|
|
@ -0,0 +1,71 @@
|
|||
<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} />
|
|
@ -1,78 +0,0 @@
|
|||
<script>
|
||||
import { views, viewsV2 } from "stores/builder"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import {
|
||||
notifications,
|
||||
Icon,
|
||||
Input,
|
||||
ActionMenu,
|
||||
MenuItem,
|
||||
Modal,
|
||||
ModalContent,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
export let view
|
||||
|
||||
let editorModal
|
||||
let originalName
|
||||
let updatedName
|
||||
let confirmDeleteDialog
|
||||
|
||||
async function save() {
|
||||
const updatedView = cloneDeep(view)
|
||||
updatedView.name = updatedName
|
||||
|
||||
if (view.version === 2) {
|
||||
await viewsV2.save({
|
||||
originalName,
|
||||
...updatedView,
|
||||
})
|
||||
} else {
|
||||
await views.save({
|
||||
originalName,
|
||||
...updatedView,
|
||||
})
|
||||
}
|
||||
|
||||
notifications.success("View renamed successfully")
|
||||
}
|
||||
|
||||
async function deleteView() {
|
||||
try {
|
||||
if (view.version === 2) {
|
||||
await viewsV2.delete(view)
|
||||
} else {
|
||||
await views.delete(view)
|
||||
}
|
||||
notifications.success("View deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
|
||||
const initForm = () => {
|
||||
updatedName = view.name + ""
|
||||
originalName = view.name + ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon open-popover">
|
||||
<Icon s hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem icon="Edit" on:click={editorModal.show}>Edit</MenuItem>
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
<Modal bind:this={editorModal} on:show={initForm}>
|
||||
<ModalContent title="Edit View" onConfirm={save} confirmText="Save">
|
||||
<Input label="View Name" thin bind:value={updatedName} />
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete the view '${view.name}'? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete View"
|
||||
onOk={deleteView}
|
||||
title="Confirm Deletion"
|
||||
/>
|
|
@ -83,6 +83,7 @@
|
|||
on:mouseenter
|
||||
on:mouseleave
|
||||
on:click={onClick}
|
||||
on:contextmenu
|
||||
ondragover="return false"
|
||||
ondragenter="return false"
|
||||
{id}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<script>
|
||||
import { AbsTooltip, Icon, Body } from "@budibase/bbui"
|
||||
import { Icon, Body, TooltipPosition, TooltipType } from "@budibase/bbui"
|
||||
|
||||
export let title
|
||||
export let icon
|
||||
|
@ -18,6 +18,7 @@
|
|||
export let closeButtonIcon = "Close"
|
||||
export let noHeaderBorder = false
|
||||
export let titleCSS = true
|
||||
|
||||
$: customHeaderContent = $$slots["panel-header-content"]
|
||||
$: customTitleContent = $$slots["panel-title-content"]
|
||||
</script>
|
||||
|
@ -41,9 +42,12 @@
|
|||
<Icon name="ArrowLeft" hoverable on:click={onClickBackButton} />
|
||||
{/if}
|
||||
{#if icon}
|
||||
<AbsTooltip type="info" text={iconTooltip}>
|
||||
<Icon name={icon} />
|
||||
</AbsTooltip>
|
||||
<Icon
|
||||
name={icon}
|
||||
tooltipType={TooltipType.Info}
|
||||
tooltip={iconTooltip}
|
||||
tooltipPosition={TooltipPosition.Top}
|
||||
/>
|
||||
{/if}
|
||||
<div class:title={titleCSS}>
|
||||
{#if customTitleContent}
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
<script>
|
||||
import { Modal } from "@budibase/bbui"
|
||||
import DeleteModal from "components/deploy/DeleteModal.svelte"
|
||||
import ExportAppModal from "./ExportAppModal.svelte"
|
||||
import DuplicateAppModal from "./DuplicateAppModal.svelte"
|
||||
import { licensing } from "stores/portal"
|
||||
|
||||
export let app
|
||||
|
||||
let exportPublishedVersion = false
|
||||
|
||||
let deleteModal
|
||||
let exportModal
|
||||
let duplicateModal
|
||||
|
||||
export const showDuplicateModal = () => {
|
||||
duplicateModal.show()
|
||||
}
|
||||
|
||||
export const showExportDevModal = () => {
|
||||
exportPublishedVersion = false
|
||||
exportModal.show()
|
||||
}
|
||||
|
||||
export const showExportProdModal = () => {
|
||||
exportPublishedVersion = true
|
||||
exportModal.show()
|
||||
}
|
||||
|
||||
export const showDeleteModal = () => {
|
||||
deleteModal.show()
|
||||
}
|
||||
</script>
|
||||
|
||||
<DeleteModal
|
||||
bind:this={deleteModal}
|
||||
appId={app?.devId}
|
||||
appName={app?.name}
|
||||
onDeleteSuccess={async () => {
|
||||
await licensing.init()
|
||||
}}
|
||||
/>
|
||||
|
||||
<Modal bind:this={exportModal} padding={false}>
|
||||
<ExportAppModal {app} published={exportPublishedVersion} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={duplicateModal} padding={false}>
|
||||
<DuplicateAppModal
|
||||
appId={app?.devId}
|
||||
appName={app?.name}
|
||||
onDuplicateSuccess={async () => {
|
||||
await licensing.init()
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
|
@ -5,14 +5,17 @@
|
|||
import { goto } from "@roxi/routify"
|
||||
import { UserAvatars } from "@budibase/frontend-core"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import AppRowContext from "./AppRowContext.svelte"
|
||||
import AppContextMenuModals from "./AppContextMenuModals.svelte"
|
||||
import getAppContextMenuItems from "./getAppContextMenuItems.js"
|
||||
import FavouriteAppButton from "pages/builder/portal/apps/FavouriteAppButton.svelte"
|
||||
import { contextMenuStore } from "stores/builder"
|
||||
|
||||
export let app
|
||||
export let lockedAction
|
||||
|
||||
let actionsOpen = false
|
||||
let appContextMenuModals
|
||||
|
||||
$: contextMenuOpen = `${app.appId}-index` === $contextMenuStore.id
|
||||
$: editing = app.sessions?.length
|
||||
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
||||
$: unclickable = !isBuilder && !app.deployed
|
||||
|
@ -40,16 +43,35 @@
|
|||
window.open(`/app${app.url}`, "_blank")
|
||||
}
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getAppContextMenuItems({
|
||||
app,
|
||||
onDuplicate: appContextMenuModals?.showDuplicateModal,
|
||||
onExportDev: appContextMenuModals?.showExportDevModal,
|
||||
onExportProd: appContextMenuModals?.showExportProdModal,
|
||||
onDelete: appContextMenuModals?.showDeleteModal,
|
||||
})
|
||||
|
||||
contextMenuStore.open(`${app.appId}-index`, items, {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div
|
||||
class:contextMenuOpen
|
||||
class="app-row"
|
||||
class:unclickable
|
||||
class:actionsOpen
|
||||
class:favourite={app.favourite}
|
||||
on:click={lockedAction || handleDefaultClick}
|
||||
on:contextmenu={openContextMenu}
|
||||
>
|
||||
<div class="title">
|
||||
<div class="app-icon">
|
||||
|
@ -89,14 +111,11 @@
|
|||
</Button>
|
||||
</div>
|
||||
<div class="row-action">
|
||||
<AppRowContext
|
||||
{app}
|
||||
on:open={() => {
|
||||
actionsOpen = true
|
||||
}}
|
||||
on:close={() => {
|
||||
actionsOpen = false
|
||||
}}
|
||||
<Icon
|
||||
on:click={openContextMenu}
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
/>
|
||||
</div>
|
||||
{:else}
|
||||
|
@ -109,6 +128,7 @@
|
|||
<FavouriteAppButton {app} noWrap />
|
||||
</div>
|
||||
</div>
|
||||
<AppContextMenuModals {app} bind:this={appContextMenuModals} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
@ -123,7 +143,8 @@
|
|||
transition: border 130ms ease-out;
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
.app-row:not(.unclickable):hover {
|
||||
.app-row:not(.unclickable):hover,
|
||||
.contextMenuOpen {
|
||||
cursor: pointer;
|
||||
border-color: var(--spectrum-global-color-gray-300);
|
||||
}
|
||||
|
@ -132,9 +153,9 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.app-row.contextMenuOpen .favourite-icon,
|
||||
.app-row:hover .favourite-icon,
|
||||
.app-row.favourite .favourite-icon,
|
||||
.app-row.actionsOpen .favourite-icon {
|
||||
.app-row.favourite .favourite-icon {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
|
@ -176,8 +197,8 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.app-row:hover .app-row-actions,
|
||||
.app-row.actionsOpen .app-row-actions {
|
||||
.app-row.contextMenuOpen .app-row-actions,
|
||||
.app-row:hover .app-row-actions {
|
||||
gap: var(--spacing-m);
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
|
|
|
@ -1,108 +0,0 @@
|
|||
<script>
|
||||
import { ActionMenu, MenuItem, Icon, Modal } from "@budibase/bbui"
|
||||
import DeleteModal from "components/deploy/DeleteModal.svelte"
|
||||
import AppLimitModal from "components/portal/licensing/AppLimitModal.svelte"
|
||||
import ExportAppModal from "./ExportAppModal.svelte"
|
||||
import DuplicateAppModal from "./DuplicateAppModal.svelte"
|
||||
import { onMount } from "svelte"
|
||||
import { licensing } from "stores/portal"
|
||||
|
||||
export let app
|
||||
export let align = "right"
|
||||
export let options
|
||||
|
||||
let deleteModal
|
||||
let exportModal
|
||||
let duplicateModal
|
||||
let exportPublishedVersion = false
|
||||
let loaded = false
|
||||
|
||||
const getActions = app => {
|
||||
if (!loaded) {
|
||||
return []
|
||||
}
|
||||
return [
|
||||
{
|
||||
id: "duplicate",
|
||||
icon: "Copy",
|
||||
onClick: duplicateModal.show,
|
||||
body: "Duplicate",
|
||||
},
|
||||
{
|
||||
id: "exportDev",
|
||||
icon: "Export",
|
||||
onClick: () => {
|
||||
exportPublishedVersion = false
|
||||
exportModal.show()
|
||||
},
|
||||
body: "Export latest edited app",
|
||||
},
|
||||
{
|
||||
id: "exportProd",
|
||||
icon: "Export",
|
||||
onClick: () => {
|
||||
exportPublishedVersion = true
|
||||
exportModal.show()
|
||||
},
|
||||
body: "Export latest published app",
|
||||
},
|
||||
{
|
||||
id: "delete",
|
||||
icon: "Delete",
|
||||
onClick: deleteModal.show,
|
||||
body: "Delete",
|
||||
},
|
||||
].filter(action => {
|
||||
if (action.id === "exportProd" && app.deployed !== true) {
|
||||
return false
|
||||
} else if (Array.isArray(options) && !options.includes(action.id)) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
$: actions = getActions(app, loaded)
|
||||
|
||||
onMount(() => {
|
||||
loaded = true
|
||||
})
|
||||
let appLimitModal
|
||||
</script>
|
||||
|
||||
<DeleteModal
|
||||
bind:this={deleteModal}
|
||||
appId={app.devId}
|
||||
appName={app.name}
|
||||
onDeleteSuccess={async () => {
|
||||
await licensing.init()
|
||||
}}
|
||||
/>
|
||||
|
||||
<AppLimitModal bind:this={appLimitModal} />
|
||||
|
||||
<Modal bind:this={exportModal} padding={false}>
|
||||
<ExportAppModal {app} published={exportPublishedVersion} />
|
||||
</Modal>
|
||||
|
||||
<Modal bind:this={duplicateModal} padding={false}>
|
||||
<DuplicateAppModal
|
||||
appId={app.devId}
|
||||
appName={app.name}
|
||||
onDuplicateSuccess={async () => {
|
||||
await licensing.init()
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
<ActionMenu {align} on:open on:close>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
|
||||
{#each actions as action}
|
||||
<MenuItem icon={action.icon} on:click={action.onClick}>
|
||||
{action.body}
|
||||
</MenuItem>
|
||||
{/each}
|
||||
</ActionMenu>
|
|
@ -0,0 +1,44 @@
|
|||
const getAppContextMenuItems = ({
|
||||
app,
|
||||
onDuplicate,
|
||||
onExportDev,
|
||||
onExportProd,
|
||||
onDelete,
|
||||
}) => {
|
||||
return [
|
||||
{
|
||||
icon: "Copy",
|
||||
name: "Duplicate",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: onDuplicate,
|
||||
},
|
||||
{
|
||||
icon: "Export",
|
||||
name: "Export latest edited app",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: onExportDev,
|
||||
},
|
||||
{
|
||||
icon: "Export",
|
||||
name: "Export latest published app",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: !app.deployed,
|
||||
callback: onExportProd,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: onDelete,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default getAppContextMenuItems
|
|
@ -299,15 +299,6 @@ export const PaginationLocations = [
|
|||
{ label: "Request body", value: "body" },
|
||||
]
|
||||
|
||||
export const BannedSearchTypes = [
|
||||
FieldType.LINK,
|
||||
FieldType.ATTACHMENTS,
|
||||
FieldType.FORMULA,
|
||||
FieldType.JSON,
|
||||
"jsonarray",
|
||||
"queryarray",
|
||||
]
|
||||
|
||||
export const DatasourceTypes = {
|
||||
RELATIONAL: "Relational",
|
||||
NON_RELATIONAL: "Non-relational",
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
import { CookieUtils, Constants } from "@budibase/frontend-core"
|
||||
import { API } from "api"
|
||||
import Branding from "./Branding.svelte"
|
||||
import ContextMenu from "components/ContextMenu.svelte"
|
||||
|
||||
let loaded = false
|
||||
|
||||
|
@ -160,6 +161,7 @@
|
|||
|
||||
<!--Portal branding overrides -->
|
||||
<Branding />
|
||||
<ContextMenu />
|
||||
|
||||
{#if loaded}
|
||||
<slot />
|
||||
|
|
|
@ -1,129 +0,0 @@
|
|||
<script>
|
||||
import { componentStore } from "stores/builder"
|
||||
import { ActionMenu, MenuItem, Icon } from "@budibase/bbui"
|
||||
|
||||
export let component
|
||||
export let opened
|
||||
|
||||
$: definition = componentStore.getDefinition(component?._component)
|
||||
$: noPaste = !$componentStore.componentToPaste
|
||||
$: isBlock = definition?.block === true
|
||||
$: canEject = !(definition?.ejectable === false)
|
||||
|
||||
const keyboardEvent = (key, ctrlKey = false) => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("component-menu", {
|
||||
detail: {
|
||||
key,
|
||||
ctrlKey,
|
||||
id: component?._id,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem
|
||||
icon="Delete"
|
||||
keyBind="!BackAndroid"
|
||||
on:click={() => keyboardEvent("Delete")}
|
||||
>
|
||||
Delete
|
||||
</MenuItem>
|
||||
{#if isBlock && canEject}
|
||||
<MenuItem
|
||||
icon="Export"
|
||||
keyBind="Ctrl+E"
|
||||
on:click={() => keyboardEvent("e", true)}
|
||||
>
|
||||
Eject block
|
||||
</MenuItem>
|
||||
{/if}
|
||||
<MenuItem
|
||||
icon="ChevronUp"
|
||||
keyBind="Ctrl+!ArrowUp"
|
||||
on:click={() => keyboardEvent("ArrowUp", true)}
|
||||
>
|
||||
Move up
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="ChevronDown"
|
||||
keyBind="Ctrl+!ArrowDown"
|
||||
on:click={() => keyboardEvent("ArrowDown", true)}
|
||||
>
|
||||
Move down
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Duplicate"
|
||||
keyBind="Ctrl+D"
|
||||
on:click={() => keyboardEvent("d", true)}
|
||||
>
|
||||
Duplicate
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Cut"
|
||||
keyBind="Ctrl+X"
|
||||
on:click={() => keyboardEvent("x", true)}
|
||||
>
|
||||
Cut
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="Copy"
|
||||
keyBind="Ctrl+C"
|
||||
on:click={() => keyboardEvent("c", true)}
|
||||
>
|
||||
Copy
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="LayersSendToBack"
|
||||
keyBind="Ctrl+V"
|
||||
on:click={() => keyboardEvent("v", true)}
|
||||
disabled={noPaste}
|
||||
>
|
||||
Paste
|
||||
</MenuItem>
|
||||
|
||||
{#if component?._children?.length}
|
||||
<MenuItem
|
||||
icon="TreeExpand"
|
||||
keyBind="!ArrowRight"
|
||||
on:click={() => keyboardEvent("ArrowRight", false)}
|
||||
disabled={opened}
|
||||
>
|
||||
Expand
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="TreeCollapse"
|
||||
keyBind="!ArrowLeft"
|
||||
on:click={() => keyboardEvent("ArrowLeft", false)}
|
||||
disabled={!opened}
|
||||
>
|
||||
Collapse
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="TreeExpandAll"
|
||||
keyBind="Ctrl+!ArrowRight"
|
||||
on:click={() => keyboardEvent("ArrowRight", true)}
|
||||
>
|
||||
Expand All
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="TreeCollapseAll"
|
||||
keyBind="Ctrl+!ArrowLeft"
|
||||
on:click={() => keyboardEvent("ArrowLeft", true)}
|
||||
>
|
||||
Collapse All
|
||||
</MenuItem>
|
||||
{/if}
|
||||
</ActionMenu>
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
|
@ -1,7 +1,6 @@
|
|||
<script>
|
||||
import ComponentDropdownMenu from "./ComponentDropdownMenu.svelte"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Icon, notifications } from "@budibase/bbui"
|
||||
import {
|
||||
selectedScreen,
|
||||
componentStore,
|
||||
|
@ -9,6 +8,7 @@
|
|||
selectedComponent,
|
||||
hoverStore,
|
||||
componentTreeNodesStore,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import {
|
||||
findComponentPath,
|
||||
|
@ -17,6 +17,7 @@
|
|||
} from "helpers/components"
|
||||
import { get } from "svelte/store"
|
||||
import { dndStore } from "./dndStore"
|
||||
import getComponentContextMenuItems from "./getComponentContextMenuItems"
|
||||
|
||||
export let components = []
|
||||
export let level = 0
|
||||
|
@ -85,6 +86,18 @@
|
|||
}
|
||||
|
||||
const hover = hoverStore.hover
|
||||
|
||||
const openContextMenu = (e, component, opened) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getComponentContextMenuItems(
|
||||
component,
|
||||
!opened,
|
||||
componentStore
|
||||
)
|
||||
contextMenuStore.open(component._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-noninteractive-element-interactions-->
|
||||
|
@ -93,6 +106,7 @@
|
|||
{#each filteredComponents || [] as component, index (component._id)}
|
||||
{@const opened = isOpen(component, openNodes)}
|
||||
<li
|
||||
on:contextmenu={e => openContextMenu(e, component, opened)}
|
||||
on:click|stopPropagation={() => {
|
||||
componentStore.select(component._id)
|
||||
}}
|
||||
|
@ -107,7 +121,8 @@
|
|||
on:dragover={dragover(component, index)}
|
||||
on:iconClick={() => handleIconClick(component._id)}
|
||||
on:drop={onDrop}
|
||||
hovering={$hoverStore.componentId === component._id}
|
||||
hovering={$hoverStore.componentId === component._id ||
|
||||
component._id === $contextMenuStore.id}
|
||||
on:mouseenter={() => hover(component._id)}
|
||||
on:mouseleave={() => hover(null)}
|
||||
text={getComponentText(component)}
|
||||
|
@ -120,7 +135,12 @@
|
|||
highlighted={isChildOfSelectedComponent(component)}
|
||||
selectedBy={$userSelectedResourceMap[component._id]}
|
||||
>
|
||||
<ComponentDropdownMenu {opened} {component} />
|
||||
<Icon
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
on:click={e => openContextMenu(e, component, opened)}
|
||||
/>
|
||||
</NavItem>
|
||||
|
||||
{#if opened}
|
||||
|
|
|
@ -1,57 +0,0 @@
|
|||
<script>
|
||||
import { componentStore } from "stores/builder"
|
||||
import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui"
|
||||
|
||||
export let component
|
||||
|
||||
$: definition = componentStore.getDefinition(component?._component)
|
||||
$: noPaste = !$componentStore.componentToPaste
|
||||
|
||||
// "editable" has been repurposed for inline text editing.
|
||||
// It remains here for legacy compatibility.
|
||||
// Future components should define "static": true for indicate they should
|
||||
// not show a context menu.
|
||||
$: showMenu = definition?.editable !== false && definition?.static !== true
|
||||
|
||||
const storeComponentForCopy = (cut = false) => {
|
||||
componentStore.copy(component, cut)
|
||||
}
|
||||
|
||||
const pasteComponent = mode => {
|
||||
try {
|
||||
componentStore.paste(component, mode)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if showMenu}
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
</div>
|
||||
<MenuItem
|
||||
icon="Copy"
|
||||
keyBind="Ctrl+C"
|
||||
on:click={() => storeComponentForCopy(false)}
|
||||
>
|
||||
Copy
|
||||
</MenuItem>
|
||||
<MenuItem
|
||||
icon="LayersSendToBack"
|
||||
keyBind="Ctrl+V"
|
||||
on:click={() => pasteComponent("inside")}
|
||||
disabled={noPaste}
|
||||
>
|
||||
Paste
|
||||
</MenuItem>
|
||||
</ActionMenu>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,123 @@
|
|||
import { get } from "svelte/store"
|
||||
import { componentStore } from "stores/builder"
|
||||
|
||||
const getContextMenuItems = (component, componentCollapsed) => {
|
||||
const definition = componentStore.getDefinition(component?._component)
|
||||
const noPaste = !get(componentStore).componentToPaste
|
||||
const isBlock = definition?.block === true
|
||||
const canEject = !(definition?.ejectable === false)
|
||||
const hasChildren = component?._children?.length
|
||||
|
||||
const keyboardEvent = (key, ctrlKey = false) => {
|
||||
document.dispatchEvent(
|
||||
new CustomEvent("component-menu", {
|
||||
detail: {
|
||||
key,
|
||||
ctrlKey,
|
||||
id: component?._id,
|
||||
},
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: "!BackAndroid",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("Delete"),
|
||||
},
|
||||
{
|
||||
icon: "ChevronUp",
|
||||
name: "Move up",
|
||||
keyBind: "Ctrl+!ArrowUp",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("ArrowUp", true),
|
||||
},
|
||||
{
|
||||
icon: "ChevronDown",
|
||||
name: "Move down",
|
||||
keyBind: "Ctrl+!ArrowDown",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("ArrowDown", true),
|
||||
},
|
||||
{
|
||||
icon: "Duplicate",
|
||||
name: "Duplicate",
|
||||
keyBind: "Ctrl+D",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("d", true),
|
||||
},
|
||||
{
|
||||
icon: "Cut",
|
||||
name: "Cut",
|
||||
keyBind: "Ctrl+X",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("x", true),
|
||||
},
|
||||
{
|
||||
icon: "Copy",
|
||||
name: "Copy",
|
||||
keyBind: "Ctrl+C",
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("c", true),
|
||||
},
|
||||
{
|
||||
icon: "LayersSendToBack",
|
||||
name: "Paste",
|
||||
keyBind: "Ctrl+V",
|
||||
visible: true,
|
||||
disabled: noPaste,
|
||||
callback: () => keyboardEvent("v", true),
|
||||
},
|
||||
{
|
||||
icon: "Export",
|
||||
name: "Eject block",
|
||||
keyBind: "Ctrl+E",
|
||||
visible: isBlock && canEject,
|
||||
disabled: false,
|
||||
callback: () => keyboardEvent("e", true),
|
||||
},
|
||||
{
|
||||
icon: "TreeExpand",
|
||||
name: "Expand",
|
||||
keyBind: "!ArrowRight",
|
||||
visible: hasChildren,
|
||||
disabled: !componentCollapsed,
|
||||
callback: () => keyboardEvent("ArrowRight", false),
|
||||
},
|
||||
{
|
||||
icon: "TreeExpandAll",
|
||||
name: "Expand All",
|
||||
keyBind: "Ctrl+!ArrowRight",
|
||||
visible: hasChildren,
|
||||
disabled: !componentCollapsed,
|
||||
callback: () => keyboardEvent("ArrowRight", true),
|
||||
},
|
||||
{
|
||||
icon: "TreeCollapse",
|
||||
name: "Collapse",
|
||||
keyBind: "!ArrowLeft",
|
||||
visible: hasChildren,
|
||||
disabled: componentCollapsed,
|
||||
callback: () => keyboardEvent("ArrowLeft", false),
|
||||
},
|
||||
{
|
||||
icon: "TreeCollapseAll",
|
||||
name: "Collapse All",
|
||||
keyBind: "Ctrl+!ArrowLeft",
|
||||
visible: hasChildren,
|
||||
disabled: componentCollapsed,
|
||||
callback: () => keyboardEvent("ArrowLeft", true),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default getContextMenuItems
|
|
@ -0,0 +1,40 @@
|
|||
import { get } from "svelte/store"
|
||||
import { componentStore } from "stores/builder"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
const getContextMenuItems = (component, showCopy) => {
|
||||
const noPaste = !get(componentStore).componentToPaste
|
||||
|
||||
const storeComponentForCopy = (cut = false) => {
|
||||
componentStore.copy(component, cut)
|
||||
}
|
||||
|
||||
const pasteComponent = mode => {
|
||||
try {
|
||||
componentStore.paste(component, mode)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
{
|
||||
icon: "Copy",
|
||||
name: "Copy",
|
||||
keyBind: "Ctrl+C",
|
||||
visible: showCopy,
|
||||
disabled: false,
|
||||
callback: () => storeComponentForCopy(false),
|
||||
},
|
||||
{
|
||||
icon: "LayersSendToBack",
|
||||
name: "Paste",
|
||||
keyBind: "Ctrl+V",
|
||||
visible: true,
|
||||
disabled: noPaste,
|
||||
callback: () => pasteComponent("inside"),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
export default getContextMenuItems
|
|
@ -7,14 +7,15 @@
|
|||
componentStore,
|
||||
userSelectedResourceMap,
|
||||
hoverStore,
|
||||
contextMenuStore,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import ComponentTree from "./ComponentTree.svelte"
|
||||
import { dndStore, DropPosition } from "./dndStore.js"
|
||||
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
|
||||
import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
|
||||
import ComponentKeyHandler from "./ComponentKeyHandler.svelte"
|
||||
import ComponentScrollWrapper from "./ComponentScrollWrapper.svelte"
|
||||
import getScreenContextMenuItems from "./getScreenContextMenuItems"
|
||||
|
||||
let scrolling = false
|
||||
|
||||
|
@ -43,6 +44,32 @@
|
|||
}
|
||||
|
||||
const hover = hoverStore.hover
|
||||
|
||||
// showCopy is used to hide the copy button when the user right-clicks the empty
|
||||
// background of their component tree. Pasting in the empty space makes sense,
|
||||
// but copying it doesn't
|
||||
const openScreenContextMenu = (e, showCopy) => {
|
||||
const screenComponent = $selectedScreen?.props
|
||||
const definition = componentStore.getDefinition(screenComponent?._component)
|
||||
// "editable" has been repurposed for inline text editing.
|
||||
// It remains here for legacy compatibility.
|
||||
// Future components should define "static": true for indicate they should
|
||||
// not show a context menu.
|
||||
if (definition?.editable !== false && definition?.static !== true) {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getScreenContextMenuItems(screenComponent, showCopy)
|
||||
contextMenuStore.open(
|
||||
`${showCopy ? "background-" : ""}screenComponent._id`,
|
||||
items,
|
||||
{
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
|
@ -56,8 +83,11 @@
|
|||
</div>
|
||||
<div class="list-panel">
|
||||
<ComponentScrollWrapper on:scroll={handleScroll}>
|
||||
<ul>
|
||||
<li>
|
||||
<ul
|
||||
class="componentTree"
|
||||
on:contextmenu={e => openScreenContextMenu(e, false)}
|
||||
>
|
||||
<li on:contextmenu={e => openScreenContextMenu(e, true)}>
|
||||
<NavItem
|
||||
text="Screen"
|
||||
indentLevel={0}
|
||||
|
@ -70,14 +100,22 @@
|
|||
on:click={() => {
|
||||
componentStore.select(`${$screenStore.selectedScreenId}-screen`)
|
||||
}}
|
||||
hovering={$hoverStore.componentId === screenComponentId}
|
||||
hovering={$hoverStore.componentId === screenComponentId ||
|
||||
$selectedScreen?.props._id === $contextMenuStore.id}
|
||||
on:mouseenter={() => hover(screenComponentId)}
|
||||
on:mouseleave={() => hover(null)}
|
||||
id="component-screen"
|
||||
selectedBy={$userSelectedResourceMap[screenComponentId]}
|
||||
>
|
||||
<ScreenslotDropdownMenu component={$selectedScreen?.props} />
|
||||
<Icon
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
on:click={e => openScreenContextMenu(e, $selectedScreen?.props)}
|
||||
/>
|
||||
</NavItem>
|
||||
</li>
|
||||
<li on:contextmenu|stopPropagation>
|
||||
<NavItem
|
||||
text="Navigation"
|
||||
indentLevel={0}
|
||||
|
@ -165,6 +203,10 @@
|
|||
flex: 1;
|
||||
}
|
||||
|
||||
.componentTree {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
|
|
|
@ -1,39 +1,25 @@
|
|||
<script>
|
||||
import { screenStore, componentStore, navigationStore } from "stores/builder"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import { Modal, Helpers, notifications, Icon } from "@budibase/bbui"
|
||||
import {
|
||||
ActionMenu,
|
||||
MenuItem,
|
||||
Icon,
|
||||
Modal,
|
||||
Helpers,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
navigationStore,
|
||||
screenStore,
|
||||
userSelectedResourceMap,
|
||||
contextMenuStore,
|
||||
componentStore,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import RoleIndicator from "./RoleIndicator.svelte"
|
||||
import ScreenDetailsModal from "components/design/ScreenDetailsModal.svelte"
|
||||
import sanitizeUrl from "helpers/sanitizeUrl"
|
||||
import { makeComponentUnique } from "helpers/components"
|
||||
import { capitalise } from "helpers"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
|
||||
export let screenId
|
||||
export let screen
|
||||
|
||||
let confirmDeleteDialog
|
||||
let screenDetailsModal
|
||||
|
||||
$: screen = $screenStore.screens.find(screen => screen._id === screenId)
|
||||
$: noPaste = !$componentStore.componentToPaste
|
||||
|
||||
const pasteComponent = mode => {
|
||||
try {
|
||||
componentStore.paste(screen.props, mode, screen)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
|
||||
const duplicateScreen = () => {
|
||||
screenDetailsModal.show()
|
||||
}
|
||||
|
||||
const createDuplicateScreen = async ({ screenName, screenUrl }) => {
|
||||
// Create a dupe and ensure it is unique
|
||||
let duplicateScreen = Helpers.cloneDeep(screen)
|
||||
|
@ -69,22 +55,75 @@
|
|||
notifications.error("Error deleting screen")
|
||||
}
|
||||
}
|
||||
|
||||
$: noPaste = !$componentStore.componentToPaste
|
||||
|
||||
const pasteComponent = mode => {
|
||||
try {
|
||||
componentStore.paste(screen.props, mode, screen)
|
||||
} catch (error) {
|
||||
notifications.error("Error saving component")
|
||||
}
|
||||
}
|
||||
|
||||
const openContextMenu = (e, screen) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = [
|
||||
{
|
||||
icon: "ShowOneLayer",
|
||||
name: "Paste inside",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: noPaste,
|
||||
callback: () => pasteComponent("inside"),
|
||||
},
|
||||
{
|
||||
icon: "Duplicate",
|
||||
name: "Duplicate",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: screenDetailsModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Delete",
|
||||
name: "Delete",
|
||||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: confirmDeleteDialog.show,
|
||||
},
|
||||
]
|
||||
|
||||
contextMenuStore.open(screen._id, items, { x: e.clientX, y: e.clientY })
|
||||
}
|
||||
</script>
|
||||
|
||||
<ActionMenu>
|
||||
<div slot="control" class="icon">
|
||||
<Icon size="S" hoverable name="MoreSmallList" />
|
||||
<NavItem
|
||||
on:contextmenu={e => openContextMenu(e, screen)}
|
||||
scrollable
|
||||
icon={screen.routing.homeScreen ? "Home" : null}
|
||||
indentLevel={0}
|
||||
selected={$screenStore.selectedScreenId === screen._id}
|
||||
hovering={screen._id === $contextMenuStore.id}
|
||||
text={screen.routing.route}
|
||||
on:click={() => screenStore.select(screen._id)}
|
||||
rightAlignIcon
|
||||
showTooltip
|
||||
selectedBy={$userSelectedResourceMap[screen._id]}
|
||||
>
|
||||
<Icon
|
||||
on:click={e => openContextMenu(e, screen)}
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
/>
|
||||
<div slot="icon" class="icon">
|
||||
<RoleIndicator roleId={screen.routing.roleId} />
|
||||
</div>
|
||||
<MenuItem
|
||||
icon="ShowOneLayer"
|
||||
on:click={() => pasteComponent("inside")}
|
||||
disabled={noPaste}
|
||||
>
|
||||
Paste inside
|
||||
</MenuItem>
|
||||
<MenuItem icon="Duplicate" on:click={duplicateScreen}>Duplicate</MenuItem>
|
||||
<MenuItem icon="Delete" on:click={confirmDeleteDialog.show}>Delete</MenuItem>
|
||||
</ActionMenu>
|
||||
</NavItem>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
|
@ -105,7 +144,7 @@
|
|||
|
||||
<style>
|
||||
.icon {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
</style>
|
|
@ -1,13 +1,7 @@
|
|||
<script>
|
||||
import { Layout } from "@budibase/bbui"
|
||||
import {
|
||||
screenStore,
|
||||
sortedScreens,
|
||||
userSelectedResourceMap,
|
||||
} from "stores/builder"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import RoleIndicator from "./RoleIndicator.svelte"
|
||||
import DropdownMenu from "./DropdownMenu.svelte"
|
||||
import { sortedScreens } from "stores/builder"
|
||||
import ScreenNavItem from "./ScreenNavItem.svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
import { getVerticalResizeActions } from "components/common/resizable"
|
||||
import NavHeader from "components/common/NavHeader.svelte"
|
||||
|
@ -55,22 +49,7 @@
|
|||
<div on:scroll={handleScroll} bind:this={screensContainer} class="content">
|
||||
{#if filteredScreens?.length}
|
||||
{#each filteredScreens as screen (screen._id)}
|
||||
<NavItem
|
||||
scrollable
|
||||
icon={screen.routing.homeScreen ? "Home" : null}
|
||||
indentLevel={0}
|
||||
selected={$screenStore.selectedScreenId === screen._id}
|
||||
text={screen.routing.route}
|
||||
on:click={() => screenStore.select(screen._id)}
|
||||
rightAlignIcon
|
||||
showTooltip
|
||||
selectedBy={$userSelectedResourceMap[screen._id]}
|
||||
>
|
||||
<DropdownMenu screenId={screen._id} />
|
||||
<div slot="icon" class="icon">
|
||||
<RoleIndicator roleId={screen.routing.roleId} />
|
||||
</div>
|
||||
</NavItem>
|
||||
<ScreenNavItem {screen} />
|
||||
{/each}
|
||||
{:else}
|
||||
<Layout paddingY="none" paddingX="L">
|
||||
|
@ -129,11 +108,6 @@
|
|||
padding-right: 8px !important;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
|
|
|
@ -7,7 +7,8 @@
|
|||
sideBarCollapsed,
|
||||
enrichedApps,
|
||||
} from "stores/portal"
|
||||
import AppRowContext from "components/start/AppRowContext.svelte"
|
||||
import AppContextMenuModals from "components/start/AppContextMenuModals.svelte"
|
||||
import getAppContextMenuItems from "components/start/getAppContextMenuItems.js"
|
||||
import FavouriteAppButton from "../FavouriteAppButton.svelte"
|
||||
import {
|
||||
Link,
|
||||
|
@ -21,12 +22,14 @@
|
|||
import { API } from "api"
|
||||
import ErrorSVG from "./ErrorSVG.svelte"
|
||||
import { getBaseTheme, ClientAppSkeleton } from "@budibase/frontend-core"
|
||||
import { contextMenuStore } from "stores/builder"
|
||||
|
||||
$: app = $enrichedApps.find(app => app.appId === $params.appId)
|
||||
$: iframeUrl = getIframeURL(app)
|
||||
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
||||
|
||||
let loading = true
|
||||
let appContextMenuModals
|
||||
|
||||
const getIframeURL = app => {
|
||||
loading = true
|
||||
|
@ -62,6 +65,24 @@
|
|||
onDestroy(() => {
|
||||
window.removeEventListener("message", receiveMessage)
|
||||
})
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getAppContextMenuItems({
|
||||
app,
|
||||
onDuplicate: appContextMenuModals.showDuplicateModal,
|
||||
onExportDev: appContextMenuModals.showExportDevModal,
|
||||
onExportProd: appContextMenuModals.showExportProdModal,
|
||||
onDelete: appContextMenuModals.showDeleteModal,
|
||||
})
|
||||
|
||||
contextMenuStore.open(`${app.appId}-view`, items, {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
|
@ -116,10 +137,15 @@
|
|||
size="S"
|
||||
/>
|
||||
</div>
|
||||
<AppRowContext
|
||||
{app}
|
||||
options={["duplicate", "delete", "exportDev", "exportProd"]}
|
||||
align="left"
|
||||
<Icon
|
||||
color={`${app.appId}-view` === $contextMenuStore.id
|
||||
? "var(--hover-color)"
|
||||
: null}
|
||||
on:contextmenu={openContextMenu}
|
||||
on:click={openContextMenu}
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
/>
|
||||
</div>
|
||||
{#if noScreens}
|
||||
|
@ -155,6 +181,7 @@
|
|||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<AppContextMenuModals {app} bind:this={appContextMenuModals} />
|
||||
|
||||
<style>
|
||||
.headerButton {
|
||||
|
|
|
@ -0,0 +1,91 @@
|
|||
<script>
|
||||
import { auth } from "stores/portal"
|
||||
import { params, goto } from "@roxi/routify"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import AppContextMenuModals from "components/start/AppContextMenuModals.svelte"
|
||||
import getAppContextMenuItems from "components/start/getAppContextMenuItems.js"
|
||||
import FavouriteAppButton from "../FavouriteAppButton.svelte"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { contextMenuStore } from "stores/builder"
|
||||
|
||||
export let app
|
||||
|
||||
let opened
|
||||
let appContextMenuModals
|
||||
|
||||
$: contextMenuOpen = `${app.appId}-sideBar` === $contextMenuStore.id
|
||||
|
||||
const openContextMenu = (e, app) => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
const items = getAppContextMenuItems({
|
||||
app,
|
||||
onDuplicate: appContextMenuModals.showDuplicateModal,
|
||||
onExportDev: appContextMenuModals.showExportDevModal,
|
||||
onExportProd: appContextMenuModals.showExportProdModal,
|
||||
onDelete: appContextMenuModals.showDeleteModal,
|
||||
})
|
||||
|
||||
contextMenuStore.open(`${app.appId}-sideBar`, items, {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<span
|
||||
class="side-bar-app-entry"
|
||||
class:favourite={app.favourite}
|
||||
class:actionsOpen={opened == app.appId || contextMenuOpen}
|
||||
>
|
||||
<NavItem
|
||||
on:contextmenu={e => openContextMenu(e, app)}
|
||||
text={app.name}
|
||||
icon={app.icon?.name || "Apps"}
|
||||
iconColor={app.icon?.color}
|
||||
selected={$params.appId === app.appId}
|
||||
hovering={contextMenuOpen}
|
||||
highlighted={opened == app.appId}
|
||||
on:click={() => $goto(`./${app.appId}`)}
|
||||
withActions
|
||||
showActions
|
||||
>
|
||||
<div class="app-entry-actions">
|
||||
{#if sdk.users.isBuilder($auth.user, app?.devId)}
|
||||
<Icon
|
||||
on:click={e => openContextMenu(e, app)}
|
||||
size="S"
|
||||
hoverable
|
||||
name="MoreSmallList"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="favourite-icon">
|
||||
<FavouriteAppButton {app} size="XS" />
|
||||
</div>
|
||||
</NavItem>
|
||||
</span>
|
||||
<AppContextMenuModals {app} bind:this={appContextMenuModals} />
|
||||
|
||||
<style>
|
||||
.side-bar-app-entry :global(.nav-item-content .actions) {
|
||||
width: auto;
|
||||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.side-bar-app-entry:hover .app-entry-actions,
|
||||
.side-bar-app-entry:hover .favourite-icon,
|
||||
.side-bar-app-entry.favourite .favourite-icon,
|
||||
.side-bar-app-entry.actionsOpen .app-entry-actions,
|
||||
.side-bar-app-entry.actionsOpen .favourite-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.side-bar-app-entry .app-entry-actions,
|
||||
.side-bar-app-entry .favourite-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
|
@ -1,11 +1,9 @@
|
|||
<script>
|
||||
import { sideBarCollapsed, enrichedApps, auth } from "stores/portal"
|
||||
import { sideBarCollapsed, enrichedApps } from "stores/portal"
|
||||
import { params, goto } from "@roxi/routify"
|
||||
import NavItem from "components/common/NavItem.svelte"
|
||||
import NavHeader from "components/common/NavHeader.svelte"
|
||||
import AppRowContext from "components/start/AppRowContext.svelte"
|
||||
import FavouriteAppButton from "../FavouriteAppButton.svelte"
|
||||
import { sdk } from "@budibase/shared-core"
|
||||
import AppNavItem from "./AppNavItem.svelte"
|
||||
|
||||
let searchString
|
||||
let opened
|
||||
|
@ -40,34 +38,7 @@
|
|||
class:favourite={app.favourite}
|
||||
class:actionsOpen={opened == app.appId}
|
||||
>
|
||||
<NavItem
|
||||
text={app.name}
|
||||
icon={app.icon?.name || "Apps"}
|
||||
iconColor={app.icon?.color}
|
||||
selected={$params.appId === app.appId}
|
||||
highlighted={opened == app.appId}
|
||||
on:click={() => $goto(`./${app.appId}`)}
|
||||
withActions
|
||||
showActions
|
||||
>
|
||||
<div class="app-entry-actions">
|
||||
{#if sdk.users.isBuilder($auth.user, app?.devId)}
|
||||
<AppRowContext
|
||||
{app}
|
||||
align="left"
|
||||
on:open={() => {
|
||||
opened = app.appId
|
||||
}}
|
||||
on:close={() => {
|
||||
opened = null
|
||||
}}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="favourite-icon">
|
||||
<FavouriteAppButton {app} size="XS" />
|
||||
</div>
|
||||
</NavItem>
|
||||
<AppNavItem {app} />
|
||||
</span>
|
||||
{/each}
|
||||
</div>
|
||||
|
@ -117,17 +88,4 @@
|
|||
display: flex;
|
||||
gap: var(--spacing-s);
|
||||
}
|
||||
|
||||
.side-bar-app-entry:hover .app-entry-actions,
|
||||
.side-bar-app-entry:hover .favourite-icon,
|
||||
.side-bar-app-entry.favourite .favourite-icon,
|
||||
.side-bar-app-entry.actionsOpen .app-entry-actions,
|
||||
.side-bar-app-entry.actionsOpen .favourite-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.side-bar-app-entry .app-entry-actions,
|
||||
.side-bar-app-entry .favourite-icon {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import { writable } from "svelte/store"
|
||||
|
||||
export const INITIAL_CONTEXT_MENU_STATE = {
|
||||
id: null,
|
||||
items: [],
|
||||
position: { x: 0, y: 0 },
|
||||
visible: false,
|
||||
}
|
||||
|
||||
export function createViewsStore() {
|
||||
const store = writable({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
|
||||
const open = (id, items, position) => {
|
||||
store.set({ id, items, position, visible: true })
|
||||
}
|
||||
|
||||
const close = () => {
|
||||
store.set({ ...INITIAL_CONTEXT_MENU_STATE })
|
||||
}
|
||||
|
||||
return {
|
||||
subscribe: store.subscribe,
|
||||
open,
|
||||
close,
|
||||
}
|
||||
}
|
||||
|
||||
export const contextMenuStore = createViewsStore()
|
|
@ -14,6 +14,7 @@ import {
|
|||
} from "./automations.js"
|
||||
import { userStore, userSelectedResourceMap, isOnlyUser } from "./users.js"
|
||||
import { deploymentStore } from "./deployments.js"
|
||||
import { contextMenuStore } from "./contextMenu.js"
|
||||
import { snippets } from "./snippets"
|
||||
|
||||
// Backend
|
||||
|
@ -48,6 +49,7 @@ export {
|
|||
userStore,
|
||||
isOnlyUser,
|
||||
deploymentStore,
|
||||
contextMenuStore,
|
||||
selectedComponent,
|
||||
tables,
|
||||
views,
|
||||
|
|
|
@ -28,11 +28,9 @@ export class ScreenStore extends BudiStore {
|
|||
this.reset = this.reset.bind(this)
|
||||
this.syncAppScreens = this.syncAppScreens.bind(this)
|
||||
this.validate = this.validate.bind(this)
|
||||
this.save = this.save.bind(this)
|
||||
this.patch = this.patch.bind(this)
|
||||
this.replace = this.replace.bind(this)
|
||||
this.saveScreen = this.saveScreen.bind(this)
|
||||
this.delete = this.delete.bind(this)
|
||||
this.deleteScreen = this.deleteScreen.bind(this)
|
||||
this.syncScreenData = this.syncScreenData.bind(this)
|
||||
this.updateSetting = this.updateSetting.bind(this)
|
||||
|
@ -52,6 +50,9 @@ export class ScreenStore extends BudiStore {
|
|||
}
|
||||
},
|
||||
})
|
||||
|
||||
this.delete = this.history.wrapDeleteDoc(this.deleteScreen)
|
||||
this.save = this.history.wrapSaveDoc(this.saveScreen)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -382,25 +383,6 @@ export class ScreenStore extends BudiStore {
|
|||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link deleteScreen} wrapped to enable history tracking
|
||||
* @param {object | array} screen
|
||||
*
|
||||
*/
|
||||
async delete(screens_x) {
|
||||
const wrappedFn = this.history.wrapDeleteDoc(this.deleteScreen)
|
||||
return wrappedFn(screens_x)
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link saveScreen} wrapped to enable history tracking
|
||||
* @param {object} screen
|
||||
*/
|
||||
async save(screen) {
|
||||
const wrappedFn = this.history.wrapSaveDoc(this.saveScreen)
|
||||
return wrappedFn(screen)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a screen by deep setting a property value by name
|
||||
*
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
import { getContext, onDestroy } from "svelte"
|
||||
import { ModalContent, Modal } from "@budibase/bbui"
|
||||
import FilterModal from "./FilterModal.svelte"
|
||||
import { QueryUtils } from "@budibase/frontend-core"
|
||||
import { QueryUtils, Constants } from "@budibase/frontend-core"
|
||||
import Button from "../Button.svelte"
|
||||
|
||||
export let dataProvider
|
||||
|
@ -22,6 +22,7 @@
|
|||
|
||||
$: dataProviderId = dataProvider?.id
|
||||
$: datasource = dataProvider?.datasource
|
||||
$: isDSPlus = ["table", "link", "viewV2"].includes(datasource?.type)
|
||||
$: addExtension = getAction(
|
||||
dataProviderId,
|
||||
ActionTypes.AddDataProviderQueryExtension
|
||||
|
@ -69,7 +70,10 @@
|
|||
}
|
||||
})
|
||||
}
|
||||
|
||||
return Object.values(clonedSchema || {})
|
||||
.filter(field => !Constants.BannedSearchTypes.includes(field.type))
|
||||
.concat(isDSPlus ? [{ name: "_id", type: "string" }] : [])
|
||||
}
|
||||
|
||||
const openEditor = () => {
|
||||
|
@ -94,7 +98,7 @@
|
|||
onClick={openEditor}
|
||||
icon="ri-filter-3-line"
|
||||
text="Filter"
|
||||
size="XL"
|
||||
{size}
|
||||
type="secondary"
|
||||
quiet
|
||||
active={filters?.length > 0}
|
||||
|
|
|
@ -36,11 +36,7 @@
|
|||
) &&
|
||||
!schemaFields.some(field => field.name === "_id")
|
||||
) {
|
||||
schemaFields = [
|
||||
...schemaFields,
|
||||
{ name: "_id", type: "string" },
|
||||
{ name: "_rev", type: "string" },
|
||||
]
|
||||
schemaFields = [...schemaFields, { name: "_id", type: "string" }]
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ export const BannedSearchTypes = [
|
|||
FieldType.ATTACHMENTS,
|
||||
FieldType.FORMULA,
|
||||
FieldType.JSON,
|
||||
FieldType.ATTACHMENT_SINGLE,
|
||||
FieldType.SIGNATURE_SINGLE,
|
||||
"jsonarray",
|
||||
"queryarray",
|
||||
|
|
Loading…
Reference in New Issue