Add confirmation for ejecting blocks and component target when using context menu

This commit is contained in:
Andrew Kingston 2022-08-30 12:57:29 +01:00
parent b86bd5bf03
commit bce2ee2107
2 changed files with 108 additions and 67 deletions

View File

@ -9,14 +9,15 @@
$: isBlock = definition?.block === true $: isBlock = definition?.block === true
const keyboardEvent = (key, ctrlKey = false) => { const keyboardEvent = (key, ctrlKey = false) => {
// Ensure this component is selected first document.dispatchEvent(
if (component._id !== $store.selectedComponentId) { new CustomEvent("component-menu", {
store.update(state => { detail: {
state.selectedComponentId = component._id key,
return state ctrlKey,
id: component?._id,
},
}) })
} )
document.dispatchEvent(new KeyboardEvent("keydown", { key, ctrlKey }))
} }
</script> </script>

View File

@ -7,14 +7,66 @@
import NavItem from "components/common/NavItem.svelte" import NavItem from "components/common/NavItem.svelte"
import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte" import ScreenslotDropdownMenu from "./ScreenslotDropdownMenu.svelte"
import { setContext, onMount } from "svelte" import { setContext, onMount } from "svelte"
import { get } from "svelte/store"
import DNDPositionIndicator from "./DNDPositionIndicator.svelte" import DNDPositionIndicator from "./DNDPositionIndicator.svelte"
import { DropPosition } from "./dndStore" import { DropPosition } from "./dndStore"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { notifications, Button } from "@budibase/bbui" import { notifications, Button } from "@budibase/bbui"
import { findComponent } from "builderStore/componentUtils"
let scrollRef let scrollRef
let confirmDeleteDialog let confirmDeleteDialog
let confirmEjectDialog
let componentToDelete
let componentToEject
const keyHandlers = {
["^ArrowUp"]: async component => {
await store.actions.components.moveUp(component)
},
["^ArrowDown"]: async component => {
await store.actions.components.moveDown(component)
},
["^c"]: component => {
store.actions.components.copy(component, false)
},
["^x"]: component => {
store.actions.components.copy(component, true)
},
["^v"]: async component => {
await store.actions.components.paste(component, "inside")
},
["^d"]: async component => {
store.actions.components.copy(component)
await store.actions.components.paste(component, "below")
},
["^e"]: component => {
componentToEject = component
confirmEjectDialog.show()
},
["^Enter"]: () => {
$goto("./new")
},
["Delete"]: component => {
// Don't show confirmation for the screen itself
if (component?._id === $selectedScreen.props._id) {
return false
}
componentToDelete = component
confirmDeleteDialog.show()
},
["ArrowUp"]: () => {
store.actions.components.selectPrevious()
},
["ArrowDown"]: () => {
store.actions.components.selectNext()
},
["Escape"]: () => {
if (!$isActive("/new")) {
return false
}
$goto("./")
},
}
const scrollTo = bounds => { const scrollTo = bounds => {
if (!bounds) { if (!bounds) {
@ -59,6 +111,11 @@
}) })
} }
// Set scroll context so components can invoke scrolling when selected
setContext("scroll", {
scrollTo,
})
const onDrop = async () => { const onDrop = async () => {
try { try {
await dndStore.actions.drop() await dndStore.actions.drop()
@ -68,13 +125,28 @@
} }
} }
// Set scroll context so components can invoke scrolling when selected const handleKeyAction = async (component, key, ctrlKey = false) => {
setContext("scroll", { if (!component || !key) {
scrollTo, return false
}) }
try {
const deleteComponent = async () => { // Delete and backspace are the same
await store.actions.components.delete(get(selectedComponent)) if (key === "Backspace") {
key = "Delete"
}
// Prefix key with a caret for ctrl modifier
if (ctrlKey) {
key = "^" + key
}
const handler = keyHandlers[key]
if (!handler) {
return false
}
return handler(component)
} catch (error) {
console.log(error)
notifications.error("Error handling key press")
}
} }
const handleKeyPress = async e => { const handleKeyPress = async e => {
@ -87,62 +159,23 @@
if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") { if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") {
return return
} }
const component = get(selectedComponent) // Key events are always for the selected component
try { return handleKeyAction($selectedComponent, e.key, e.ctrlKey || e.metaKey)
if (e.ctrlKey || e.metaKey) { }
if (e.key === "ArrowUp") {
e.preventDefault() const handleComponentMenu = async e => {
await store.actions.components.moveUp(component) // Menu events can be for any component
} else if (e.key === "ArrowDown") { const { id, key, ctrlKey } = e.detail
e.preventDefault() const component = findComponent($selectedScreen.props, id)
await store.actions.components.moveDown(component) return await handleKeyAction(component, key, ctrlKey)
} else if (e.key === "c") {
e.preventDefault()
await store.actions.components.copy(component, false)
} else if (e.key === "x") {
e.preventDefault()
store.actions.components.copy(component, true)
} else if (e.key === "v") {
e.preventDefault()
await store.actions.components.paste(component, "inside")
} else if (e.key === "d") {
e.preventDefault()
await store.actions.components.copy(component)
await store.actions.components.paste(component, "below")
} else if (e.key === "Enter") {
e.preventDefault()
$goto("./new")
} else if (e.key === "e") {
e.preventDefault()
await store.actions.components.requestEjectBlock(component?._id)
}
} else if (e.key === "Backspace" || e.key === "Delete") {
// Don't show confirmation for the screen itself
if (component._id === get(selectedScreen).props._id) {
return
}
e.preventDefault()
confirmDeleteDialog.show()
} else if (e.key === "ArrowUp") {
e.preventDefault()
await store.actions.components.selectPrevious()
} else if (e.key === "ArrowDown") {
e.preventDefault()
await store.actions.components.selectNext()
} else if (e.key === "Escape" && $isActive("./new")) {
e.preventDefault()
$goto("./")
}
} catch (error) {
console.log(error)
notifications.error("Error handling key press")
}
} }
onMount(() => { onMount(() => {
document.addEventListener("keydown", handleKeyPress) document.addEventListener("keydown", handleKeyPress)
document.addEventListener("component-menu", handleComponentMenu)
return () => { return () => {
document.removeEventListener("keydown", handleKeyPress) document.removeEventListener("keydown", handleKeyPress)
document.removeEventListener("component-menu", handleComponentMenu)
} }
}) })
</script> </script>
@ -195,9 +228,16 @@
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
title="Confirm Deletion" title="Confirm Deletion"
body={`Are you sure you want to delete "${$selectedComponent?._instanceName}"?`} body={`Are you sure you want to delete "${componentToDelete?._instanceName}"?`}
okText="Delete Component" okText="Delete Component"
onOk={deleteComponent} onOk={() => store.actions.components.delete(componentToDelete)}
/>
<ConfirmDialog
bind:this={confirmEjectDialog}
title="Eject block"
body={`Ejecting a block breaks it down into multiple components. Are you sure you want to eject "${componentToEject?._instanceName}"?`}
onOk={() => store.actions.components.requestEjectBlock(componentToEject?._id)}
okText="Eject block"
/> />
<style> <style>