Add custom RBAC edges with inline deletion icon
This commit is contained in:
parent
35bdc998ca
commit
6f9175168b
|
@ -0,0 +1,114 @@
|
|||
<script>
|
||||
import {
|
||||
getBezierPath,
|
||||
BaseEdge,
|
||||
EdgeLabelRenderer,
|
||||
useSvelteFlow,
|
||||
} from "@xyflow/svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
export let sourceX
|
||||
export let sourceY
|
||||
export let sourcePosition
|
||||
export let targetX
|
||||
export let targetY
|
||||
export let targetPosition
|
||||
export let id
|
||||
|
||||
const flow = useSvelteFlow()
|
||||
|
||||
let edgeHovered = false
|
||||
let labelHovered = false
|
||||
|
||||
$: hovered = edgeHovered || labelHovered
|
||||
$: edgeClasses = getEdgeClasses(hovered, labelHovered)
|
||||
$: [edgePath, labelX, labelY] = getBezierPath({
|
||||
sourceX,
|
||||
sourceY,
|
||||
sourcePosition,
|
||||
targetX,
|
||||
targetY,
|
||||
targetPosition,
|
||||
})
|
||||
|
||||
const getEdgeClasses = (hovered, labelHovered) => {
|
||||
let classes = ""
|
||||
if (hovered) classes += `hovered `
|
||||
if (labelHovered) classes += `delete `
|
||||
return classes
|
||||
}
|
||||
|
||||
const onEdgeMouseOver = e => {
|
||||
edgeHovered = true
|
||||
}
|
||||
|
||||
const onEdgeMouseOut = e => {
|
||||
edgeHovered = false
|
||||
}
|
||||
|
||||
const deleteEdge = () => {
|
||||
flow.deleteElements({
|
||||
edges: [{ id }],
|
||||
})
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const edge = document.querySelector(`.svelte-flow__edge[data-id="${id}"]`)
|
||||
if (edge) {
|
||||
edge.addEventListener("mouseover", onEdgeMouseOver)
|
||||
edge.addEventListener("mouseout", onEdgeMouseOut)
|
||||
}
|
||||
return () => {
|
||||
if (edge) {
|
||||
edge.removeEventListener("mouseover", onEdgeMouseOver)
|
||||
edge.removeEventListener("mouseout", onEdgeMouseOut)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<BaseEdge path={edgePath} class={edgeClasses} />
|
||||
<EdgeLabelRenderer>
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
||||
<div
|
||||
style:transform="translate(-50%, -50%) translate({labelX}px,{labelY}px)"
|
||||
class="edge-label nodrag nopan"
|
||||
class:hovered
|
||||
on:click={deleteEdge}
|
||||
on:mouseover={() => (labelHovered = true)}
|
||||
on:mouseout={() => (labelHovered = false)}
|
||||
>
|
||||
<Icon name="Delete" />
|
||||
</div>
|
||||
</EdgeLabelRenderer>
|
||||
|
||||
<style>
|
||||
.edge-label {
|
||||
position: absolute;
|
||||
padding: 8px;
|
||||
opacity: 0;
|
||||
pointer-events: all;
|
||||
}
|
||||
.edge-label:hover,
|
||||
.edge-label.hovered {
|
||||
opacity: 1;
|
||||
cursor: pointer;
|
||||
}
|
||||
.edge-label:hover :global(.spectrum-Icon) {
|
||||
color: var(--spectrum-global-color-red-400);
|
||||
}
|
||||
.edge-label :global(.spectrum-Icon) {
|
||||
background: var(--background-color);
|
||||
color: var(--spectrum-global-color-gray-600);
|
||||
}
|
||||
:global(.svelte-flow__edge:hover .svelte-flow__edge-path),
|
||||
:global(.svelte-flow__edge-path.hovered) {
|
||||
stroke: var(--spectrum-global-color-blue-400);
|
||||
}
|
||||
:global(.svelte-flow__edge-path.hovered.delete) {
|
||||
stroke: var(--spectrum-global-color-red-400);
|
||||
}
|
||||
</style>
|
|
@ -4,14 +4,16 @@
|
|||
import { SvelteFlow, Background, BackgroundVariant } from "@xyflow/svelte"
|
||||
import "@xyflow/svelte/dist/style.css"
|
||||
import RoleNode from "./RoleNode.svelte"
|
||||
import RoleEdge from "./RoleEdge.svelte"
|
||||
import { rolesToNodes, autoLayout } from "./layout"
|
||||
import { onMount, setContext } from "svelte"
|
||||
import Controls from "./Controls.svelte"
|
||||
|
||||
const nodes = writable([])
|
||||
const edges = writable([])
|
||||
const dragging = writable(false)
|
||||
|
||||
setContext("flow", { nodes, edges })
|
||||
setContext("flow", { nodes, edges, dragging })
|
||||
|
||||
onMount(() => {
|
||||
const layout = autoLayout(rolesToNodes())
|
||||
|
@ -33,9 +35,12 @@
|
|||
{edges}
|
||||
snapGrid={[25, 25]}
|
||||
nodeTypes={{ role: RoleNode }}
|
||||
edgeTypes={{ role: RoleEdge }}
|
||||
proOptions={{ hideAttribution: true }}
|
||||
fitViewOptions={{ maxZoom: 1 }}
|
||||
defaultEdgeOptions={{ type: "bezier", animated: true }}
|
||||
defaultEdgeOptions={{ type: "role", animated: true, selectable: false }}
|
||||
onconnectstart={() => dragging.set(true)}
|
||||
onconnectend={() => dragging.set(false)}
|
||||
>
|
||||
<Background variant={BackgroundVariant.Dots} />
|
||||
<Controls />
|
||||
|
@ -81,6 +86,6 @@
|
|||
|
||||
/* Edges */
|
||||
--xy-edge-stroke: var(--edge-color);
|
||||
--xy-edge-stroke-selected: var(--selected-color);
|
||||
--xy-edge-stroke-selected: var(--edge-color);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
import { roles } from "stores/builder"
|
||||
|
||||
export let data
|
||||
export let isConnectable
|
||||
export let id
|
||||
|
||||
const { nodes, edges } = getContext("flow")
|
||||
const { nodes, edges, dragging } = getContext("flow")
|
||||
const flow = useSvelteFlow()
|
||||
|
||||
let anchor
|
||||
|
@ -31,6 +30,7 @@
|
|||
$: nameError = validateName(tempDisplayName, $roles)
|
||||
$: descriptionError = validateDescription(tempDescription)
|
||||
$: invalid = nameError || descriptionError
|
||||
$: targetClasses = `target${$dragging ? "" : " hidden"}`
|
||||
|
||||
const validateName = (name, roles) => {
|
||||
if (!name?.length) {
|
||||
|
@ -109,10 +109,15 @@
|
|||
{/if}
|
||||
</div>
|
||||
{#if id !== Roles.BASIC}
|
||||
<Handle type="target" position={Position.Left} {isConnectable} />
|
||||
<Handle
|
||||
type="target"
|
||||
position={Position.Left}
|
||||
class={targetClasses}
|
||||
isConnectable={$dragging}
|
||||
/>
|
||||
{/if}
|
||||
{#if id !== Roles.ADMIN}
|
||||
<Handle type="source" position={Position.Right} {isConnectable} />
|
||||
<Handle type="source" position={Position.Right} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -207,4 +212,11 @@
|
|||
.node:hover .buttons {
|
||||
display: flex;
|
||||
}
|
||||
.node :global(.svelte-flow__handle.target) {
|
||||
background: var(--background-color);
|
||||
}
|
||||
.node :global(.svelte-flow__handle.hidden) {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -62,7 +62,6 @@ export const rolesToNodes = () => {
|
|||
id: `${sourceRole}-${role._id}`,
|
||||
source: sourceRole,
|
||||
target: role._id,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +78,7 @@ const dagreLayout = ({ nodes, edges }) => {
|
|||
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
||||
dagreGraph.setGraph({
|
||||
rankdir: "LR",
|
||||
ranksep: 100,
|
||||
ranksep: 200,
|
||||
nodesep: 100,
|
||||
})
|
||||
nodes.forEach(node => {
|
||||
|
@ -122,7 +121,6 @@ const sanitiseLayout = ({ nodes, edges }) => {
|
|||
id: Helpers.uuid(),
|
||||
source: node.id,
|
||||
target: Roles.ADMIN,
|
||||
animated: true,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue