Remove the Raphael.js dependency by switching to SVG API (#43845).

Patch by Mizuki ISHIKAWA (user:ishikawa999).


git-svn-id: https://svn.redmine.org/redmine/trunk@24469 e93f8b46-1217-0410-a6f0-8f06a7374b81
This commit is contained in:
Go MAEDA
2026-03-01 06:58:28 +00:00
parent 5d4a738692
commit df2257eff0
6 changed files with 250 additions and 89 deletions

View File

@@ -1,6 +1,7 @@
import { Controller } from "@hotwired/stimulus"
const RELATION_STROKE_WIDTH = 2
const SVG_NS = "http://www.w3.org/2000/svg"
export default class extends Controller {
static targets = ["ganttArea", "drawArea", "subjectsContainer"]
@@ -16,10 +17,10 @@ export default class extends Controller {
#drawRight = 0
#drawLeft = 0
#drawPaper = null
#drawPaperGroup = null
initialize() {
this.$ = window.jQuery
this.Raphael = window.Raphael
}
connect() {
@@ -35,6 +36,7 @@ export default class extends Controller {
if (this.#drawPaper) {
this.#drawPaper.remove()
this.#drawPaper = null
this.#drawPaperGroup = null
}
}
@@ -73,13 +75,9 @@ export default class extends Controller {
}
#drawProgressLineAndRelations() {
if (this.#drawPaper) {
this.#drawPaper.clear()
} else {
this.#drawPaper = this.Raphael(this.drawAreaTarget)
}
this.#setupDrawArea()
this.#setupDrawPaper()
this.#drawPaperGroup?.replaceChildren(); // Clear previous drawings
if (this.showProgressValue) {
this.#drawGanttProgressLines()
@@ -91,6 +89,42 @@ export default class extends Controller {
}
#setupDrawPaper() {
const width = Math.ceil(this.$(this.drawAreaTarget).width() || 0)
const height = Math.ceil(this.$(this.drawAreaTarget).height() || 0)
if (!this.#drawPaper) {
this.#drawPaper = document.createElementNS(SVG_NS, "svg")
this.#drawPaper.setAttribute("aria-hidden", "true")
this.#drawPaper.style.position = "absolute"
this.#drawPaper.style.inset = "0"
this.#drawPaper.style.pointerEvents = "none"
this.#drawPaperGroup = document.createElementNS(SVG_NS, "g")
this.#drawPaper.appendChild(this.#drawPaperGroup)
this.drawAreaTarget.appendChild(this.#drawPaper)
}
const safeWidth = Math.max(width, 1)
const safeHeight = Math.max(height, 1)
this.#drawPaper.setAttribute("width", String(safeWidth))
this.#drawPaper.setAttribute("height", String(safeHeight))
this.#drawPaper.setAttribute("viewBox", `0 0 ${safeWidth} ${safeHeight}`)
}
#drawPath(pathData, attributes = {}) {
if (!this.#drawPaperGroup) return
const path = document.createElementNS(SVG_NS, "path")
path.setAttribute("d", pathData.map((item) => String(item)).join(" "))
Object.entries(attributes).forEach(([name, value]) => {
path.setAttribute(name, String(value))
})
this.#drawPaperGroup.appendChild(path)
}
#setupDrawArea() {
const $drawArea = this.$(this.drawAreaTarget)
const $ganttArea = this.hasGanttAreaTarget ? this.$(this.ganttAreaTarget) : null
@@ -168,83 +202,90 @@ export default class extends Controller {
const issueFromRightRel = issueFromRight + landscapeMargin
const issueToLeftRel = issueToLeft - landscapeMargin
this.#drawPaper
.path([
this.#drawPath(
[
"M",
issueFromRight + this.#drawLeft,
issueFromTop,
"L",
issueFromRightRel + this.#drawLeft,
issueFromTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
if (issueFromRightRel < issueToLeftRel) {
this.#drawPaper
.path([
this.#drawPath(
[
"M",
issueFromRightRel + this.#drawLeft,
issueFromTop,
"L",
issueFromRightRel + this.#drawLeft,
issueToTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
this.#drawPaper
.path([
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
this.#drawPath(
[
"M",
issueFromRightRel + this.#drawLeft,
issueToTop,
"L",
issueToLeft + this.#drawLeft,
issueToTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
} else {
const issueMiddleTop = issueToTop + issueHeight * (issueFromTop > issueToTop ? 1 : -1)
this.#drawPaper
.path([
this.#drawPath(
[
"M",
issueFromRightRel + this.#drawLeft,
issueFromTop,
"L",
issueFromRightRel + this.#drawLeft,
issueMiddleTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
this.#drawPaper
.path([
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
this.#drawPath(
[
"M",
issueFromRightRel + this.#drawLeft,
issueMiddleTop,
"L",
issueToLeftRel + this.#drawLeft,
issueMiddleTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
this.#drawPaper
.path([
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
this.#drawPath(
[
"M",
issueToLeftRel + this.#drawLeft,
issueMiddleTop,
"L",
issueToLeftRel + this.#drawLeft,
issueToTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
this.#drawPaper
.path([
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
this.#drawPath(
[
"M",
issueToLeftRel + this.#drawLeft,
issueToTop,
"L",
issueToLeft + this.#drawLeft,
issueToTop
])
.attr({ stroke: color, "stroke-width": RELATION_STROKE_WIDTH })
],
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
)
}
this.#drawPaper
.path([
this.#drawPath(
[
"M",
issueToLeft + this.#drawLeft,
issueToTop,
@@ -255,13 +296,12 @@ export default class extends Controller {
0,
4 * RELATION_STROKE_WIDTH,
"z"
])
.attr({
],
{
stroke: "none",
fill: color,
"stroke-linecap": "butt",
"stroke-linejoin": "miter"
})
fill: color
}
)
})
}
@@ -344,9 +384,11 @@ export default class extends Controller {
const x1 = previous.left === 0 ? 0 : previous.left + this.#drawLeft
const x2 = current.left === 0 ? 0 : current.left + this.#drawLeft
this.#drawPaper
.path(["M", x1, previous.top, "L", x2, current.top])
.attr({ stroke: color, "stroke-width": 2 })
this.#drawPath(["M", x1, previous.top, "L", x2, current.top], {
stroke: color,
"stroke-width": 2,
fill: "none"
})
}
}
}