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 { SvelteFlow, Background, BackgroundVariant } from "@xyflow/svelte"
|
||||||
import "@xyflow/svelte/dist/style.css"
|
import "@xyflow/svelte/dist/style.css"
|
||||||
import RoleNode from "./RoleNode.svelte"
|
import RoleNode from "./RoleNode.svelte"
|
||||||
|
import RoleEdge from "./RoleEdge.svelte"
|
||||||
import { rolesToNodes, autoLayout } from "./layout"
|
import { rolesToNodes, autoLayout } from "./layout"
|
||||||
import { onMount, setContext } from "svelte"
|
import { onMount, setContext } from "svelte"
|
||||||
import Controls from "./Controls.svelte"
|
import Controls from "./Controls.svelte"
|
||||||
|
|
||||||
const nodes = writable([])
|
const nodes = writable([])
|
||||||
const edges = writable([])
|
const edges = writable([])
|
||||||
|
const dragging = writable(false)
|
||||||
|
|
||||||
setContext("flow", { nodes, edges })
|
setContext("flow", { nodes, edges, dragging })
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
const layout = autoLayout(rolesToNodes())
|
const layout = autoLayout(rolesToNodes())
|
||||||
|
@ -33,9 +35,12 @@
|
||||||
{edges}
|
{edges}
|
||||||
snapGrid={[25, 25]}
|
snapGrid={[25, 25]}
|
||||||
nodeTypes={{ role: RoleNode }}
|
nodeTypes={{ role: RoleNode }}
|
||||||
|
edgeTypes={{ role: RoleEdge }}
|
||||||
proOptions={{ hideAttribution: true }}
|
proOptions={{ hideAttribution: true }}
|
||||||
fitViewOptions={{ maxZoom: 1 }}
|
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} />
|
<Background variant={BackgroundVariant.Dots} />
|
||||||
<Controls />
|
<Controls />
|
||||||
|
@ -81,6 +86,6 @@
|
||||||
|
|
||||||
/* Edges */
|
/* Edges */
|
||||||
--xy-edge-stroke: var(--edge-color);
|
--xy-edge-stroke: var(--edge-color);
|
||||||
--xy-edge-stroke-selected: var(--selected-color);
|
--xy-edge-stroke-selected: var(--edge-color);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -16,10 +16,9 @@
|
||||||
import { roles } from "stores/builder"
|
import { roles } from "stores/builder"
|
||||||
|
|
||||||
export let data
|
export let data
|
||||||
export let isConnectable
|
|
||||||
export let id
|
export let id
|
||||||
|
|
||||||
const { nodes, edges } = getContext("flow")
|
const { nodes, edges, dragging } = getContext("flow")
|
||||||
const flow = useSvelteFlow()
|
const flow = useSvelteFlow()
|
||||||
|
|
||||||
let anchor
|
let anchor
|
||||||
|
@ -31,6 +30,7 @@
|
||||||
$: nameError = validateName(tempDisplayName, $roles)
|
$: nameError = validateName(tempDisplayName, $roles)
|
||||||
$: descriptionError = validateDescription(tempDescription)
|
$: descriptionError = validateDescription(tempDescription)
|
||||||
$: invalid = nameError || descriptionError
|
$: invalid = nameError || descriptionError
|
||||||
|
$: targetClasses = `target${$dragging ? "" : " hidden"}`
|
||||||
|
|
||||||
const validateName = (name, roles) => {
|
const validateName = (name, roles) => {
|
||||||
if (!name?.length) {
|
if (!name?.length) {
|
||||||
|
@ -109,10 +109,15 @@
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if id !== Roles.BASIC}
|
{#if id !== Roles.BASIC}
|
||||||
<Handle type="target" position={Position.Left} {isConnectable} />
|
<Handle
|
||||||
|
type="target"
|
||||||
|
position={Position.Left}
|
||||||
|
class={targetClasses}
|
||||||
|
isConnectable={$dragging}
|
||||||
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
{#if id !== Roles.ADMIN}
|
{#if id !== Roles.ADMIN}
|
||||||
<Handle type="source" position={Position.Right} {isConnectable} />
|
<Handle type="source" position={Position.Right} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -207,4 +212,11 @@
|
||||||
.node:hover .buttons {
|
.node:hover .buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
.node :global(.svelte-flow__handle.target) {
|
||||||
|
background: var(--background-color);
|
||||||
|
}
|
||||||
|
.node :global(.svelte-flow__handle.hidden) {
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -62,7 +62,6 @@ export const rolesToNodes = () => {
|
||||||
id: `${sourceRole}-${role._id}`,
|
id: `${sourceRole}-${role._id}`,
|
||||||
source: sourceRole,
|
source: sourceRole,
|
||||||
target: role._id,
|
target: role._id,
|
||||||
animated: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +78,7 @@ const dagreLayout = ({ nodes, edges }) => {
|
||||||
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
dagreGraph.setDefaultEdgeLabel(() => ({}))
|
||||||
dagreGraph.setGraph({
|
dagreGraph.setGraph({
|
||||||
rankdir: "LR",
|
rankdir: "LR",
|
||||||
ranksep: 100,
|
ranksep: 200,
|
||||||
nodesep: 100,
|
nodesep: 100,
|
||||||
})
|
})
|
||||||
nodes.forEach(node => {
|
nodes.forEach(node => {
|
||||||
|
@ -122,7 +121,6 @@ const sanitiseLayout = ({ nodes, edges }) => {
|
||||||
id: Helpers.uuid(),
|
id: Helpers.uuid(),
|
||||||
source: node.id,
|
source: node.id,
|
||||||
target: Roles.ADMIN,
|
target: Roles.ADMIN,
|
||||||
animated: true,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue