Skip to content

Commit

Permalink
feat: Deletion of connetions
Browse files Browse the repository at this point in the history
  • Loading branch information
Xeyos88 committed Feb 14, 2025
1 parent 9611312 commit 31ea8f5
Show file tree
Hide file tree
Showing 12 changed files with 213 additions and 56 deletions.
9 changes: 9 additions & 0 deletions docs/.vitepress/theme/components/AdvancedGanttDemo.vue
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const enableRowDragAndDrop = ref(true)
const maxRows = ref(5)
const defaultProgressResizable = ref(true)
const enableConnectionCreation = ref(true)
const enableConnectionDeletion = ref(true)
const multiColumnOptions = ['Label','StartDate','EndDate','Id','Duration', 'Progress']
const columnsSelected = ref(["Label"])
Expand Down Expand Up @@ -685,6 +686,12 @@ const formattedEventLog = computed(() => {
<input type="checkbox" v-model="enableConnectionCreation">
</label>
</div>
<div class="setting-item">
<label>
Enable connection deletion:
<input type="checkbox" v-model="enableConnectionDeletion">
</label>
</div>
</div>
</div>
</div>
Expand Down Expand Up @@ -736,6 +743,7 @@ const formattedEventLog = computed(() => {
:showLabel="showLabel"
:milestones="milestones"
:enableConnectionCreation="enableConnectionCreation"
:enableConnectionDeletion="enableConnectionDeletion"
@click-bar="handleEvent($event, 'Bar Click')"
@drag-bar="handleEvent($event, 'Bar Drag')"
@sort="handleEvent($event, 'Sort Change')"
Expand All @@ -745,6 +753,7 @@ const formattedEventLog = computed(() => {
@progress-drag-end="handleEvent($event, 'Progress Bar End')"
@connection-start="handleEvent($event, 'Connection Start')"
@connection-complete="handleEvent($event, 'Connection Complete')"
@connection-delete="handleEvent($event, 'Connection Deleted')"
>
<g-gantt-row
v-for="row in sampleData"
Expand Down
5 changes: 4 additions & 1 deletion docs/components/g-gantt-chart.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,9 @@ Here's a minimal example of using the GGanttChart component:
| showLabel| `boolean` | `true` | Enable the visualization of bar label |
| showProgress| `boolean` | `true` | Enable the visualization of percentage bar progress |
| defaultProgressResizable | `boolean` | `true` | Enable progress adjustment through dragging |
| enableConnectionCreation | `boolean` | `true` | Enable the possibility to draw connections |
| enableConnectionCreation | `boolean` | `false` | Enable the possibility to draw connections |
| enableConnectionDelete | `boolean` | `false` | Enable the possibility to delete connections |


### Events

Expand All @@ -100,6 +102,7 @@ Here's a minimal example of using the GGanttChart component:
| connection-drag | `{ sourceBar: GanttBarObject, connectionPoint: ConnectionPoint, currentX: number, currentY: number, e: MouseEvent }` | During connection dragging |
| connection-complete | `{ sourceBar: GanttBarObject, targetBar: GanttBarObject, sourcePoint: ConnectionPoint, targetPoint: ConnectionPoint, e: MouseEvent }` | Completes the creation of a connection |
| connection-cancel | `{ sourceBar: GanttBarObject, connectionPoint: ConnectionPoint, e: MouseEvent }` | Cancels the creation of a connection |
| connection-delete | `{ sourceBar: GanttBarObject, targetBar: GanttBarObject, e: MouseEvent }` | Delete a connection |


### Slots
Expand Down
23 changes: 19 additions & 4 deletions src/components/GGanttChart.vue
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ const props = withDefaults(defineProps<GGanttChartProps>(), {
showLabel: true,
showProgress: true,
defaultProgressResizable: true,
enableConnectionCreation: true
enableConnectionCreation: false,
enableConnectionDeletion: false
})
// Events
Expand Down Expand Up @@ -199,8 +200,16 @@ const rowManager = useRows(
provide("useRows", rowManager)
// Connections Management
const { connections, barPositions, getConnectorProps, initializeConnections, updateBarPositions } =
useConnections(rowManager, props, id)
const {
connections,
barPositions,
getConnectorProps,
initializeConnections,
updateBarPositions,
handleConnectionClick,
selectedConnection,
deleteSelectedConnection
} = useConnections(rowManager, props, id, emit)
// Tooltip Management
const { showTooltip, tooltipBar, initTooltip, clearTooltip } = useTooltip()
Expand Down Expand Up @@ -249,7 +258,12 @@ const { handleKeyDown } = useKeyboardNavigation(
handleZoomUpdate
},
ganttWrapper,
ganttContainer
ganttContainer,
{
selectedConnection,
deleteSelectedConnection
},
toRef(props.enableConnectionDeletion)
)
// Size Management
Expand Down Expand Up @@ -901,6 +915,7 @@ provide(GANTT_ID_KEY, id.value)
v-if="barPositions.get(conn.sourceId) && barPositions.get(conn.targetId)"
v-bind="getConnectorProps(conn)!"
:marker="markerConnection"
@click="handleConnectionClick(conn)"
/>
</template>
</template>
Expand Down
58 changes: 53 additions & 5 deletions src/components/GGanttConnector.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<script setup lang="ts">
import provideConfig from "../provider/provideConfig"
import type { BarPosition, ConnectionType, MarkerConnection } from "../types"
import { computed, ref } from "vue"
Expand All @@ -12,6 +13,7 @@ interface Props {
animated?: boolean
animationSpeed?: "slow" | "normal" | "fast"
marker: MarkerConnection
isSelected?: boolean
}
const props = withDefaults(defineProps<Props>(), {
Expand All @@ -20,9 +22,12 @@ const props = withDefaults(defineProps<Props>(), {
strokeWidth: 2,
pattern: "solid",
animated: false,
animationSpeed: "normal"
animationSpeed: "normal",
isSelected: false
})
const { enableConnectionDeletion } = provideConfig()
const pathRef = ref<SVGPathElement | null>(null)
const animationClass = computed(() => {
Expand Down Expand Up @@ -87,6 +92,13 @@ const nonAnimatedDashArray = computed(() => {
return ""
}
})
const getStrokeWidth = computed(() => {
if (props.isSelected && enableConnectionDeletion.value) {
return props.strokeWidth * 1.5
}
return props.strokeWidth
})
</script>

<template>
Expand All @@ -98,7 +110,6 @@ const nonAnimatedDashArray = computed(() => {
left: 0,
width: '100%',
height: '100%',
pointerEvents: 'none',
zIndex: 1001,
overflow: 'visible'
}"
Expand Down Expand Up @@ -152,26 +163,52 @@ const nonAnimatedDashArray = computed(() => {
:stroke="
animated && pattern === 'solid' ? `url(#gradient-${sourceBar.id}-${targetBar.id})` : color
"
:stroke-width="strokeWidth"
:stroke-width="getStrokeWidth"
:stroke-dasharray="nonAnimatedDashArray"
:class="['connector-path', animationClass]"
:class="[
'connector-path',
animationClass,
{ selected: isSelected && enableConnectionDeletion }
]"
:style="{
markerStart: hasMarkerStart ? `url(#${markerId})` : 'none',
markerEnd: hasMarkerEnd ? `url(#${markerId})` : 'none'
markerEnd: hasMarkerEnd ? `url(#${markerId})` : 'none',
cursor: enableConnectionDeletion ? 'pointer' : 'inherit'
}"
/>
<template v-if="isSelected && enableConnectionDeletion">
<circle
:cx="sourceBar.x + sourceBar.width"
:cy="sourceBar.y + sourceBar.height / 2"
r="6"
fill="white"
class="connection-endpoint"
/>
<circle
:cx="targetBar.x"
:cy="targetBar.y + targetBar.height / 2"
r="6"
fill="white"
class="connection-endpoint"
/>
</template>
</svg>
</template>

<style scoped>
.gantt-connector {
overflow: visible;
pointer-events: all;
}
.connector-path {
transition: d 0.3s ease;
}
.connector-path.selected {
filter: drop-shadow(0 0 5px rgba(33, 150, 243, 0.6));
}
/* Animazione per pattern dash */
.connector-animated-dash-slow {
animation: dashFlow 4s linear infinite;
Expand Down Expand Up @@ -212,6 +249,17 @@ const nonAnimatedDashArray = computed(() => {
marker-start 0.3s ease;
}
.connection-endpoint {
filter: drop-shadow(0 0 2px rgba(0, 0, 0, 0.3));
transition: all 0.3s ease;
border: 1px solid black;
border-radius: 100%;
}
.connection-endpoint:hover {
r: 8;
}
@keyframes dashFlow {
0% {
stroke-dasharray: 10, 10;
Expand Down
76 changes: 64 additions & 12 deletions src/composables/useConnections.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {
BarConnection,
BarPosition,
ChartRow,
ConnectionDeleteEvent,
GanttBarObject,
GGanttChartProps
} from "../types"
Expand All @@ -19,10 +20,14 @@ import type { UseRowsReturn } from "./useRows"
export function useConnections(
rowManager: UseRowsReturn,
props: GGanttChartProps,
id: Ref<string>
id: Ref<string>,
emit: {
(e: "connection-delete", value: ConnectionDeleteEvent): void
}
) {
const connections = ref<BarConnection[]>([])
const barPositions = ref<Map<string, BarPosition>>(new Map())
const selectedConnection = ref<BarConnection | null>(null)

/**
* Computed property that generates connector properties for rendering
Expand All @@ -41,7 +46,10 @@ export function useConnections(
color: conn.color ?? props.defaultConnectionColor,
pattern: conn.pattern ?? props.defaultConnectionPattern,
animated: conn.animated ?? props.defaultConnectionAnimated,
animationSpeed: conn.animationSpeed ?? props.defaultConnectionAnimationSpeed
animationSpeed: conn.animationSpeed ?? props.defaultConnectionAnimationSpeed,
isSelected:
selectedConnection.value?.sourceId === conn.sourceId &&
selectedConnection.value?.targetId === conn.targetId
}

return {
Expand All @@ -51,22 +59,63 @@ export function useConnections(
}
})

const handleConnectionClick = (connection: BarConnection) => {
if (
selectedConnection.value?.sourceId === connection.sourceId &&
selectedConnection.value?.targetId === connection.targetId
) {
selectedConnection.value = null
} else {
selectedConnection.value = connection
}
}

const deleteSelectedConnection = () => {
if (!selectedConnection.value) return

const allBars = getAllBars(rowManager.rows.value)
const sourceBar = allBars.find(
(bar) => bar.ganttBarConfig.id === selectedConnection.value?.sourceId
)

if (sourceBar && sourceBar.ganttBarConfig.connections) {
sourceBar.ganttBarConfig.connections = sourceBar.ganttBarConfig.connections.filter(
(conn) => conn.targetId !== selectedConnection.value?.targetId
)

const targetBar = allBars.find(
(bar) => bar.ganttBarConfig.id === selectedConnection.value?.targetId
)!

emit("connection-delete", {
sourceBar,
targetBar,
e: new MouseEvent("mouseup")
})

selectedConnection.value = null
initializeConnections()
rowManager.onBarMove()
}
}

const getAllBars = (rows: ChartRow[]): GanttBarObject[] => {
return rows.flatMap((row) => {
const bars = [...row.bars]
if (row.children?.length) {
return [...bars, ...getAllBars(row.children)]
}
return bars
})
}

/**
* Initializes connections by processing all bars and their connection configurations
* Extracts and normalizes connection data from bar configurations
*/
const initializeConnections = () => {
connections.value = []

const getAllBars = (rows: ChartRow[]): GanttBarObject[] => {
return rows.flatMap((row) => {
const bars = [...row.bars]
if (row.children?.length) {
return [...bars, ...getAllBars(row.children)]
}
return bars
})
}
const allBars = getAllBars(rowManager.rows.value)

allBars.forEach((el) => {
Expand Down Expand Up @@ -133,6 +182,9 @@ export function useConnections(
barPositions,
getConnectorProps,
initializeConnections,
updateBarPositions
updateBarPositions,
handleConnectionClick,
selectedConnection,
deleteSelectedConnection
}
}
22 changes: 21 additions & 1 deletion src/composables/useKeyboardNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { BarConnection } from "../types"
import { type Ref } from "vue"

/**
Expand All @@ -10,6 +11,11 @@ interface NavigationControls {
handleZoomUpdate: (increase: boolean) => void
}

interface ConnectionControls {
selectedConnection: Ref<BarConnection | null>
deleteSelectedConnection: () => void
}

/**
* A composable that provides keyboard navigation functionality for the Gantt chart
* Allows users to navigate and control the chart using keyboard shortcuts
Expand All @@ -22,9 +28,12 @@ interface NavigationControls {
export function useKeyboardNavigation(
chartNavigation: NavigationControls,
wrapperRef: Ref<HTMLElement | null>,
ganttContainerRef: Ref<HTMLElement | null>
ganttContainerRef: Ref<HTMLElement | null>,
connectionControls: ConnectionControls,
enableConnectionDeletion: Ref<boolean>
) {
const { handleStep, handleZoomUpdate, scrollPosition } = chartNavigation
const { selectedConnection, deleteSelectedConnection } = connectionControls

/**
* Handles keyboard events for chart navigation
Expand All @@ -43,6 +52,17 @@ export function useKeyboardNavigation(
return
}

if (selectedConnection.value && enableConnectionDeletion.value) {
switch (event.key) {
case "Delete":
deleteSelectedConnection()
return
case "Escape":
selectedConnection.value = null
return
}
}

switch (event.key) {
case "ArrowLeft":
if (wrapperRef.value && scrollPosition.value > 0) {
Expand Down
Loading

0 comments on commit 31ea8f5

Please sign in to comment.