Patch #44567 » 0001-Refactor-and-modernize-Gantt-chart-layout.patch
| app/assets/stylesheets/gantt.css | ||
|---|---|---|
| 19 | 19 |
} |
| 20 | 20 | |
| 21 | 21 |
/***** Gantt chart *****/ |
| 22 |
table.gantt-table {
|
|
| 22 |
.gantt-chart {
|
|
| 23 |
display: flex; |
|
| 24 |
align-items: flex-start; |
|
| 23 | 25 |
inline-size: 100%; |
| 24 |
border-collapse: collapse; |
|
| 26 |
margin-block-end: 1em; |
|
| 27 |
overflow: hidden; |
|
| 25 | 28 |
/* Force LTR for Gantt even in RTL locales because column resizing, relation lines, and the progress line don't work in RTL yet. */ |
| 26 | 29 |
direction: ltr; |
| 27 | 30 |
} |
| 28 | 31 | |
| 29 |
table.gantt-table td {
|
|
| 30 |
padding: 0;
|
|
| 32 |
.gantt-chart [hidden] {
|
|
| 33 |
display: none !important;
|
|
| 31 | 34 |
} |
| 32 | 35 | |
| 33 |
.gantt_hdr {
|
|
| 34 |
position: absolute; |
|
| 35 |
inset-block-start: 0; |
|
| 36 |
block-size: 16px; |
|
| 36 |
.gantt_subjects_column, |
|
| 37 |
.gantt_selected_column {
|
|
| 38 |
position: relative; |
|
| 39 |
flex: 0 0 var(--gantt-column-width); |
|
| 40 |
inline-size: var(--gantt-column-width); |
|
| 41 |
min-inline-size: 0; |
|
| 42 |
} |
|
| 43 | ||
| 44 |
.gantt-chart .ui-resizable-e {
|
|
| 45 |
cursor: ew-resize; |
|
| 46 |
} |
|
| 47 | ||
| 48 |
.gantt-pane {
|
|
| 49 |
block-size: calc(var(--gantt-pane-height) + 2px); |
|
| 37 | 50 |
border-block: 1px solid var(--oc-gray-4); |
| 51 |
border-inline-end: 1px solid var(--oc-gray-4); |
|
| 52 |
box-sizing: border-box; |
|
| 53 |
overflow: hidden; |
|
| 54 |
} |
|
| 55 | ||
| 56 |
.gantt_subjects_container {
|
|
| 38 | 57 |
border-inline-start: 1px solid var(--oc-gray-4); |
| 39 |
text-align: center; |
|
| 58 |
} |
|
| 59 | ||
| 60 |
.gantt-pane-header {
|
|
| 61 |
position: relative; |
|
| 62 |
z-index: 2; |
|
| 63 |
display: flex; |
|
| 64 |
align-items: center; |
|
| 65 |
justify-content: center; |
|
| 66 |
block-size: var(--gantt-headers-height); |
|
| 67 |
border-block-end: 1px solid var(--oc-gray-4); |
|
| 68 |
box-sizing: border-box; |
|
| 69 |
background: var(--oc-gray-1); |
|
| 70 |
font-size: 0.8em; |
|
| 40 | 71 |
overflow: hidden; |
| 41 | 72 |
} |
| 42 | 73 | |
| 43 |
#gantt_area .gantt_hdr {
|
|
| 44 |
border-inline-start: 0; |
|
| 74 |
.gantt-selected-column-header {
|
|
| 75 |
padding-inline: 3px; |
|
| 76 |
text-overflow: ellipsis; |
|
| 77 |
white-space: nowrap; |
|
| 78 |
} |
|
| 79 | ||
| 80 |
.gantt-pane-body {
|
|
| 81 |
position: relative; |
|
| 82 |
block-size: var(--gantt-content-height); |
|
| 83 |
} |
|
| 84 | ||
| 85 |
.gantt-timeline {
|
|
| 86 |
position: relative; |
|
| 87 |
flex: 1 1 auto; |
|
| 88 |
min-inline-size: 0; |
|
| 89 |
/* Keep the canvas border inside the overflow clipping edge. */ |
|
| 90 |
padding-block-end: 1px; |
|
| 91 |
overflow-x: auto; |
|
| 92 |
overflow-y: hidden; |
|
| 93 |
} |
|
| 94 | ||
| 95 |
.gantt-timeline-canvas {
|
|
| 96 |
position: relative; |
|
| 97 |
inline-size: calc(var(--gantt-chart-width) + 1px); |
|
| 98 |
block-size: calc(var(--gantt-pane-height) + 2px); |
|
| 99 |
border-block: 1px solid var(--oc-gray-4); |
|
| 45 | 100 |
border-inline-end: 1px solid var(--oc-gray-4); |
| 101 |
box-sizing: border-box; |
|
| 102 |
} |
|
| 103 | ||
| 104 |
.gantt-timeline-header {
|
|
| 105 |
position: relative; |
|
| 106 |
z-index: 1; |
|
| 107 |
block-size: var(--gantt-headers-height); |
|
| 108 |
border-block-end: 1px solid var(--oc-gray-4); |
|
| 109 |
box-sizing: border-box; |
|
| 110 |
background: var(--oc-gray-1); |
|
| 46 | 111 |
} |
| 47 | 112 | |
| 48 |
.gantt_subjects_container:not(.draw_selected_columns) .gantt_hdr, |
|
| 49 |
.last_gantt_selected_column .gantt_hdr {
|
|
| 113 |
.gantt-header-row {
|
|
| 114 |
display: flex; |
|
| 115 |
block-size: var(--gantt-header-height); |
|
| 116 |
} |
|
| 117 | ||
| 118 |
.gantt-period {
|
|
| 119 |
position: relative; |
|
| 120 |
flex: 0 0 var(--gantt-period-width); |
|
| 121 |
inline-size: var(--gantt-period-width); |
|
| 122 |
min-inline-size: 0; |
|
| 123 |
block-size: 100%; |
|
| 50 | 124 |
border-inline-end: 1px solid var(--oc-gray-4); |
| 125 |
border-block-end: 1px solid var(--oc-gray-4); |
|
| 126 |
text-align: center; |
|
| 127 |
box-sizing: border-box; |
|
| 128 |
overflow: hidden; |
|
| 129 |
white-space: nowrap; |
|
| 130 |
pointer-events: none; |
|
| 51 | 131 |
} |
| 52 | 132 | |
| 53 |
.last_gantt_selected_column .gantt_selected_column_container, |
|
| 54 |
.gantt_subjects_container .gantt_subjects * {
|
|
| 55 |
z-index: 10; |
|
| 133 |
.gantt-header-row:last-child .gantt-period {
|
|
| 134 |
border-block-end: 0; |
|
| 56 | 135 |
} |
| 57 | 136 | |
| 58 |
.gantt_subjects_column + td {
|
|
| 59 |
padding: 0; |
|
| 137 |
.gantt-period-last {
|
|
| 138 |
border-inline-end: 0; |
|
| 139 |
} |
|
| 140 | ||
| 141 |
.gantt-period-spans-body {
|
|
| 142 |
block-size: calc(100% + var(--gantt-content-height)); |
|
| 143 |
border-block-end: 0; |
|
| 144 |
} |
|
| 145 | ||
| 146 |
.gantt-period a {
|
|
| 147 |
position: relative; |
|
| 148 |
z-index: 1; |
|
| 149 |
pointer-events: auto; |
|
| 150 |
} |
|
| 151 | ||
| 152 |
.gantt-period-day, |
|
| 153 |
.gantt-period-day-number {
|
|
| 154 |
font-size: 0.7em; |
|
| 60 | 155 |
} |
| 61 | 156 | |
| 62 |
.gantt_hdr.nwday {
|
|
| 157 |
.gantt-period-non-working {
|
|
| 63 | 158 |
background-color: var(--oc-gray-1); |
| 64 | 159 |
color: var(--oc-gray-6); |
| 65 | 160 |
} |
| 66 | 161 | |
| 162 |
.gantt-timeline-body {
|
|
| 163 |
position: absolute; |
|
| 164 |
z-index: 2; |
|
| 165 |
inset-block-start: var(--gantt-headers-height); |
|
| 166 |
inset-inline-start: 0; |
|
| 167 |
inline-size: var(--gantt-chart-width); |
|
| 168 |
block-size: var(--gantt-content-height); |
|
| 169 |
} |
|
| 170 | ||
| 67 | 171 |
.gantt_subjects, |
| 68 |
.gantt_selected_column_content.gantt_hdr {
|
|
| 172 |
.gantt_selected_column_content {
|
|
| 173 |
position: relative; |
|
| 174 |
block-size: 100%; |
|
| 69 | 175 |
font-size: 0.8em; |
| 176 |
} |
|
| 177 | ||
| 178 |
.gantt_subjects form, |
|
| 179 |
.gantt_selected_column_content, |
|
| 180 |
.gantt-timeline-body form {
|
|
| 70 | 181 |
position: relative; |
| 71 |
z-index: 1; |
|
| 182 |
display: block; |
|
| 183 |
inline-size: 100%; |
|
| 184 |
block-size: 100%; |
|
| 72 | 185 |
} |
| 73 | 186 | |
| 74 |
.gantt_subjects div, |
|
| 75 |
.gantt_selected_column_content div {
|
|
| 187 |
.gantt-row {
|
|
| 188 |
position: absolute; |
|
| 189 |
inset-block-start: var(--gantt-row-top); |
|
| 190 |
block-size: 20px; |
|
| 191 |
box-sizing: border-box; |
|
| 192 |
} |
|
| 193 | ||
| 194 |
.gantt-subject-row {
|
|
| 195 |
inset-inline-start: var(--gantt-row-indent); |
|
| 196 |
inline-size: var(--gantt-row-width); |
|
| 76 | 197 |
line-height: 16px; |
| 77 |
block-size: 16px; |
|
| 78 | 198 |
overflow: hidden; |
| 79 | 199 |
white-space: nowrap; |
| 80 |
text-overflow: clip; |
|
| 200 |
} |
|
| 201 | ||
| 202 |
.gantt-column-row {
|
|
| 203 |
inset-inline-start: 0; |
|
| 81 | 204 |
inline-size: 100%; |
| 205 |
padding-inline-start: 3px; |
|
| 206 |
line-height: 16px; |
|
| 207 |
overflow: hidden; |
|
| 208 |
white-space: nowrap; |
|
| 82 | 209 |
} |
| 83 | 210 | |
| 84 | 211 |
.gantt_subjects div.issue-subject:hover {
|
| 85 | 212 |
background-color: var(--oc-yellow-0); |
| 86 | 213 |
} |
| 87 | 214 | |
| 88 |
.gantt_selected_column_content > div {
|
|
| 89 |
padding-inline-start: 3px;
|
|
| 90 |
box-sizing: border-box;
|
|
| 215 |
.gantt-timeline-row {
|
|
| 216 |
inset-inline-start: 0;
|
|
| 217 |
inline-size: var(--gantt-chart-width);
|
|
| 91 | 218 |
} |
| 92 | 219 | |
| 93 |
.gantt_hdr_selected_column_name {
|
|
| 220 |
#today_line {
|
|
| 94 | 221 |
position: absolute; |
| 95 |
inset-block-start: 50%; |
|
| 96 |
inline-size: 100%; |
|
| 97 |
transform: translateY(-50%); |
|
| 98 |
-webkit-transform: translateY(-50%); |
|
| 99 |
font-size: 0.8em; |
|
| 100 |
overflow: hidden; |
|
| 101 |
text-overflow: ellipsis; |
|
| 102 |
white-space: nowrap; |
|
| 103 |
} |
|
| 104 | ||
| 105 |
td.gantt_selected_column {
|
|
| 106 |
inline-size: 50px; |
|
| 222 |
inset-block-start: 1px; |
|
| 223 |
inset-inline-start: var(--gantt-today-start); |
|
| 224 |
inline-size: 10px; |
|
| 225 |
block-size: var(--gantt-content-height); |
|
| 226 |
border-inline-start: 1px dashed red; |
|
| 227 |
pointer-events: none; |
|
| 107 | 228 |
} |
| 108 | 229 | |
| 109 |
td.gantt_selected_column .gantt_hdr, |
|
| 110 |
.gantt_selected_column_container {
|
|
| 111 |
inline-size: 49px; |
|
| 230 |
#gantt_draw_area {
|
|
| 231 |
position: absolute; |
|
| 232 |
inset-block-start: 1px; |
|
| 233 |
inset-inline-start: 0; |
|
| 234 |
inline-size: var(--gantt-chart-width); |
|
| 235 |
block-size: var(--gantt-content-height); |
|
| 236 |
pointer-events: none; |
|
| 112 | 237 |
} |
| 113 | 238 | |
| 114 |
td.gantt_watcher_users_column div.issue_watcher_users ul {
|
|
| 239 |
.gantt_watcher_users_column div.issue_watcher_users ul {
|
|
| 115 | 240 |
margin: 0; |
| 116 | 241 |
padding: 0; |
| 117 | 242 |
list-style: none; |
| 118 | 243 |
} |
| 119 | 244 | |
| 120 |
td.gantt_watcher_users_column div.issue_watcher_users ul li {
|
|
| 245 |
.gantt_watcher_users_column div.issue_watcher_users ul li {
|
|
| 121 | 246 |
display: inline; |
| 122 | 247 |
} |
| 123 | 248 | |
| 124 |
td.gantt_watcher_users_column div.issue_watcher_users ul li:not(:last-child)::after {
|
|
| 249 |
.gantt_watcher_users_column div.issue_watcher_users ul li:not(:last-child)::after {
|
|
| 125 | 250 |
content: ', '; |
| 126 | 251 |
white-space: pre; |
| 127 | 252 |
} |
| 128 | 253 | |
| 129 | 254 |
.task {
|
| 130 | 255 |
position: absolute; |
| 256 |
inset-block-start: var(--gantt-row-top); |
|
| 257 |
inset-inline-start: var(--gantt-task-start); |
|
| 258 |
inline-size: var(--gantt-task-width); |
|
| 131 | 259 |
block-size: 8px; |
| 132 | 260 |
font-size: 0.8em; |
| 133 | 261 |
color: var(--oc-gray-6); |
| ... | ... | |
| 137 | 265 |
white-space: nowrap; |
| 138 | 266 |
} |
| 139 | 267 | |
| 268 |
.gantt-timeline-body .tooltip {
|
|
| 269 |
position: absolute; |
|
| 270 |
inset-block-start: var(--gantt-row-top); |
|
| 271 |
inset-inline-start: var(--gantt-task-start); |
|
| 272 |
inline-size: var(--gantt-task-width); |
|
| 273 |
block-size: 12px; |
|
| 274 |
} |
|
| 275 | ||
| 276 |
.gantt-row > :is(.task, .tooltip) {
|
|
| 277 |
inset-block-start: 0; |
|
| 278 |
} |
|
| 279 | ||
| 280 |
.gantt-chart .toggle-selection {
|
|
| 281 |
display: none; |
|
| 282 |
} |
|
| 283 | ||
| 140 | 284 |
.task.label {
|
| 141 |
inline-size: 100%;
|
|
| 285 |
inline-size: var(--gantt-task-width);
|
|
| 142 | 286 |
} |
| 143 | 287 | |
| 144 | 288 |
.task.label.project, |
| ... | ... | |
| 172 | 316 |
block-size: 3px; |
| 173 | 317 |
} |
| 174 | 318 | |
| 175 |
.task.parent.marker.starting {
|
|
| 176 |
position: absolute; |
|
| 177 |
background: url(/task_parent_end.png) no-repeat 0 0; |
|
| 178 |
inline-size: 8px; |
|
| 179 |
block-size: 16px; |
|
| 180 |
margin-inline-start: -4px; |
|
| 181 |
inset-inline-start: 0; |
|
| 182 |
inset-block-start: -1px; |
|
| 183 |
} |
|
| 184 | ||
| 319 |
.task.parent.marker.starting, |
|
| 185 | 320 |
.task.parent.marker.ending {
|
| 186 | 321 |
position: absolute; |
| 187 | 322 |
background: url(/task_parent_end.png) no-repeat 0 0; |
| 188 | 323 |
inline-size: 8px; |
| 189 | 324 |
block-size: 16px; |
| 190 | 325 |
margin-inline-start: -4px; |
| 191 |
inset-inline-end: 0; |
|
| 192 |
inset-block-start: -1px; |
|
| 193 | 326 |
} |
| 194 | 327 | |
| 195 | 328 |
.version.task_late {
|
| ... | ... | |
| 266 | 399 |
.project-overdue a {
|
| 267 | 400 |
color: var(--oc-red-8); |
| 268 | 401 |
} |
| 402 | ||
| 403 |
@media print {
|
|
| 404 |
/* Include the full timeline in the browser print layout. */ |
|
| 405 |
body.controller-gantts.action-show #wrapper {
|
|
| 406 |
overflow: visible; |
|
| 407 |
} |
|
| 408 | ||
| 409 |
.gantt-chart {
|
|
| 410 |
inline-size: max-content; |
|
| 411 |
overflow: visible; |
|
| 412 |
} |
|
| 413 | ||
| 414 |
.gantt-timeline {
|
|
| 415 |
flex: 0 0 calc(var(--gantt-chart-width) + 1px); |
|
| 416 |
inline-size: calc(var(--gantt-chart-width) + 1px); |
|
| 417 |
overflow: visible; |
|
| 418 |
} |
|
| 419 |
} |
|
| app/assets/stylesheets/responsive.css | ||
|---|---|---|
| 667 | 667 |
} |
| 668 | 668 | |
| 669 | 669 |
/* Gantt charts */ |
| 670 |
/* |
|
| 671 |
* [1] override inline styles with important |
|
| 672 |
* [2] keep border between subjects and gantt area |
|
| 673 |
* [3] remove whitespace between subjects and gantt area |
|
| 674 |
* [4] maintain width due to [3] |
|
| 675 |
*/ |
|
| 676 | 670 |
.gantt_subjects_column {
|
| 677 |
inline-size: 60% !important; /* [1] */
|
|
| 671 |
--gantt-column-width: 60% !important;
|
|
| 678 | 672 |
} |
| 679 | 673 | |
| 680 | 674 |
.gantt_subjects_container {
|
| ... | ... | |
| 682 | 676 |
overflow: hidden; |
| 683 | 677 |
} |
| 684 | 678 | |
| 685 |
.gantt_subjects_column .gantt_hdr {
|
|
| 686 |
inline-size: 100% !important; |
|
| 687 |
inset-inline-end: 0 !important; /* [2] */ |
|
| 688 |
border-inline-end: solid 1px var(--oc-gray-4); |
|
| 689 |
} |
|
| 690 | ||
| 691 |
#gantt_area {
|
|
| 692 |
inset-inline-start: -2px; /* [3] */ |
|
| 693 |
margin-inline-end: -2px; /* [4] */ |
|
| 694 |
} |
|
| 695 | ||
| 696 | 679 |
/*----------------------------------------*\ |
| 697 | 680 |
G) FORMS |
| 698 | 681 |
\*----------------------------------------*/ |
| app/helpers/gantt_helper.rb | ||
|---|---|---|
| 18 | 18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
| 19 | 19 | |
| 20 | 20 |
module GanttHelper |
| 21 |
class ChartLayout |
|
| 22 |
# A calendar period displayed in the timeline header. |
|
| 23 |
Period = Struct.new( |
|
| 24 |
# The first date of the period, or nil for a leading partial week |
|
| 25 |
:date, |
|
| 26 |
# The period width in pixels |
|
| 27 |
:width, |
|
| 28 |
# Whether the period represents a non-working day |
|
| 29 |
:non_working, |
|
| 30 |
# Whether the period's grid line extends through the chart body |
|
| 31 |
:spans_body, |
|
| 32 |
# Whether the period is the last one in its header row |
|
| 33 |
:last, |
|
| 34 |
keyword_init: true |
|
| 35 |
) |
|
| 36 | ||
| 37 |
# The height of one timeline header row in pixels |
|
| 38 |
attr_reader :header_height |
|
| 39 |
# The combined height of all visible header rows in pixels |
|
| 40 |
attr_reader :headers_height |
|
| 41 |
# The gap between the header and the first chart row in pixels |
|
| 42 |
attr_reader :content_top |
|
| 43 |
# The subject pane width used to render its rows in pixels |
|
| 44 |
attr_reader :subject_width |
|
| 45 |
# The number of pixels representing one day |
|
| 46 |
attr_reader :zoom |
|
| 47 | ||
| 48 |
def initialize(gantt) |
|
| 49 |
@gantt = gantt |
|
| 50 |
@zoom = 2**gantt.zoom |
|
| 51 |
@subject_width = 330 |
|
| 52 |
@header_height = 18 |
|
| 53 |
@content_top = 8 |
|
| 54 |
@headers_height = header_rows * header_height |
|
| 55 |
end |
|
| 56 | ||
| 57 |
# The full timeline width in pixels |
|
| 58 |
def chart_width |
|
| 59 |
@chart_width ||= begin |
|
| 60 |
days_in_range = (@gantt.date_to - @gantt.date_from + 1).to_i |
|
| 61 |
days_in_range * zoom |
|
| 62 |
end |
|
| 63 |
end |
|
| 64 | ||
| 65 |
def content_height |
|
| 66 |
@content_height ||= begin |
|
| 67 |
row_height = 20 |
|
| 68 |
extra_rows = 6 |
|
| 69 |
bottom_padding = 150 |
|
| 70 |
minimum_height = 206 |
|
| 71 | ||
| 72 |
# Preserve the extra space and minimum height from the original template. |
|
| 73 |
rows_height = row_height * (@gantt.number_of_rows + extra_rows) |
|
| 74 |
[rows_height + bottom_padding, minimum_height].max |
|
| 75 |
end |
|
| 76 |
end |
|
| 77 | ||
| 78 |
def pane_height |
|
| 79 |
headers_height + content_height |
|
| 80 |
end |
|
| 81 | ||
| 82 |
def months |
|
| 83 |
date = @gantt.date_from |
|
| 84 | ||
| 85 |
Array.new(@gantt.months) do |index| |
|
| 86 |
next_month = date.next_month |
|
| 87 |
days_in_month = (next_month - date).to_i |
|
| 88 |
period = Period.new( |
|
| 89 |
date: date, |
|
| 90 |
width: days_in_month * zoom, |
|
| 91 |
spans_body: !show_weeks?, |
|
| 92 |
last: index == @gantt.months - 1 |
|
| 93 |
) |
|
| 94 |
date = next_month |
|
| 95 |
period |
|
| 96 |
end |
|
| 97 |
end |
|
| 98 | ||
| 99 |
def weeks |
|
| 100 |
return [] unless show_weeks? |
|
| 101 | ||
| 102 |
date = @gantt.date_from |
|
| 103 |
periods = [] |
|
| 104 |
unless date.monday? |
|
| 105 |
week_end = date.end_of_week(:monday) |
|
| 106 |
remaining_days = (week_end - date + 1).to_i |
|
| 107 |
# A leading partial week has no week-number label. |
|
| 108 |
periods << Period.new( |
|
| 109 |
date: nil, |
|
| 110 |
width: remaining_days * zoom, |
|
| 111 |
spans_body: !show_days?, |
|
| 112 |
last: week_end >= @gantt.date_to |
|
| 113 |
) |
|
| 114 |
date = date.next_week(:monday) |
|
| 115 |
end |
|
| 116 | ||
| 117 |
while date <= @gantt.date_to |
|
| 118 |
week_end = [date.end_of_week(:monday), @gantt.date_to].min |
|
| 119 |
days_in_week = (week_end - date + 1).to_i |
|
| 120 |
periods << Period.new( |
|
| 121 |
date: date, |
|
| 122 |
width: days_in_week * zoom, |
|
| 123 |
spans_body: !show_days?, |
|
| 124 |
last: week_end == @gantt.date_to |
|
| 125 |
) |
|
| 126 |
date = date.next_week(:monday) |
|
| 127 |
end |
|
| 128 |
periods |
|
| 129 |
end |
|
| 130 | ||
| 131 |
def day_numbers |
|
| 132 |
return [] unless show_day_numbers? |
|
| 133 | ||
| 134 |
day_periods |
|
| 135 |
end |
|
| 136 | ||
| 137 |
def days |
|
| 138 |
return [] unless show_days? |
|
| 139 | ||
| 140 |
day_periods(spans_body: true) |
|
| 141 |
end |
|
| 142 | ||
| 143 |
def today_start |
|
| 144 |
today = User.current.today |
|
| 145 |
return unless today.between?(@gantt.date_from, @gantt.date_to) |
|
| 146 | ||
| 147 |
days_through_today = (today - @gantt.date_from + 1).to_i |
|
| 148 |
# Place the line at the last pixel of the current day. |
|
| 149 |
days_through_today * zoom - 1 |
|
| 150 |
end |
|
| 151 | ||
| 152 |
def show_weeks? |
|
| 153 |
@gantt.zoom > 1 |
|
| 154 |
end |
|
| 155 | ||
| 156 |
def show_days? |
|
| 157 |
@gantt.zoom > 2 |
|
| 158 |
end |
|
| 159 | ||
| 160 |
def show_day_numbers? |
|
| 161 |
@gantt.zoom > 3 |
|
| 162 |
end |
|
| 163 | ||
| 164 |
private |
|
| 165 | ||
| 166 |
def header_rows |
|
| 167 |
# The month header is always displayed. |
|
| 168 |
1 + [show_weeks?, show_days?, show_day_numbers?].count(true) |
|
| 169 |
end |
|
| 170 | ||
| 171 |
def day_periods(spans_body: false) |
|
| 172 |
(@gantt.date_from..@gantt.date_to).map do |date| |
|
| 173 |
Period.new( |
|
| 174 |
date: date, |
|
| 175 |
width: zoom, |
|
| 176 |
non_working: @gantt.non_working_week_days.include?(date.cwday), |
|
| 177 |
spans_body: spans_body, |
|
| 178 |
last: date == @gantt.date_to |
|
| 179 |
) |
|
| 180 |
end |
|
| 181 |
end |
|
| 182 |
end |
|
| 183 | ||
| 184 |
def gantt_css_variables(variables) |
|
| 185 |
variables.map {|name, value| "--#{name}:#{value}"}.join(';')
|
|
| 186 |
end |
|
| 187 | ||
| 188 |
def gantt_period_style(period) |
|
| 189 |
gantt_css_variables( |
|
| 190 |
'gantt-period-width': "#{period.width}px"
|
|
| 191 |
) |
|
| 192 |
end |
|
| 193 | ||
| 21 | 194 |
def gantt_zoom_link(gantt, in_or_out) |
| 22 | 195 |
case in_or_out |
| 23 | 196 |
when :in |
| ... | ... | |
| 42 | 215 |
end |
| 43 | 216 |
end |
| 44 | 217 | |
| 45 |
def gantt_chart_tag(query, &)
|
|
| 218 |
def gantt_chart_tag(query, layout, &block)
|
|
| 46 | 219 |
data_attributes = {
|
| 47 | 220 |
controller: 'gantt--chart', |
| 48 | 221 |
# Events emitted by child controllers the chart listens to. |
| ... | ... | |
| 62 | 235 |
'gantt--chart-show-progress-value': query.draw_progress_line ? 'true' : 'false' |
| 63 | 236 |
} |
| 64 | 237 | |
| 65 |
tag.table(class: 'gantt-table', data: data_attributes, &) |
|
| 238 |
style = gantt_css_variables( |
|
| 239 |
'gantt-subject-width': "#{layout.subject_width + 1}px",
|
|
| 240 |
'gantt-header-height': "#{layout.header_height}px",
|
|
| 241 |
'gantt-headers-height': "#{layout.headers_height}px",
|
|
| 242 |
'gantt-chart-width': "#{layout.chart_width}px",
|
|
| 243 |
'gantt-content-top': "#{layout.content_top}px",
|
|
| 244 |
'gantt-content-height': "#{layout.content_height}px",
|
|
| 245 |
'gantt-pane-height': "#{layout.pane_height}px"
|
|
| 246 |
) |
|
| 247 | ||
| 248 |
tag.div(class: 'gantt-chart', style: style, data: data_attributes) do |
|
| 249 |
capture(layout, &block) |
|
| 250 |
end |
|
| 66 | 251 |
end |
| 67 | 252 | |
| 68 | 253 |
def gantt_column_tag(column_name, min_width: nil, **options, &) |
| ... | ... | |
| 74 | 259 |
} |
| 75 | 260 |
options[:class] = ["gantt_#{column_name}_column", options[:class]]
|
| 76 | 261 | |
| 77 |
tag.td(**options, &) |
|
| 262 |
options[:style] = gantt_css_variables('gantt-column-width': options.delete(:width)) if options[:width]
|
|
| 263 | ||
| 264 |
tag.div(**options, &) |
|
| 78 | 265 |
end |
| 79 | 266 | |
| 80 | 267 |
def gantt_subjects_tag(&) |
| app/javascript/controllers/gantt/chart_controller.js | ||
|---|---|---|
| 15 | 15 | |
| 16 | 16 |
#drawTop = 0 |
| 17 | 17 |
#drawRight = 0 |
| 18 |
#drawLeft = 0 |
|
| 19 | 18 |
#drawPaper = null |
| 20 | 19 |
#drawPaperGroup = null |
| 21 | 20 | |
| ... | ... | |
| 26 | 25 |
connect() {
|
| 27 | 26 |
this.#drawTop = 0 |
| 28 | 27 |
this.#drawRight = 0 |
| 29 |
this.#drawLeft = 0 |
|
| 30 | 28 | |
| 31 | 29 |
this.#drawProgressLineAndRelations() |
| 32 | 30 |
this.#drawSelectedColumns() |
| ... | ... | |
| 127 | 125 | |
| 128 | 126 |
#setupDrawArea() {
|
| 129 | 127 |
const $drawArea = this.$(this.drawAreaTarget) |
| 130 |
const $ganttArea = this.hasGanttAreaTarget ? this.$(this.ganttAreaTarget) : null |
|
| 131 | 128 | |
| 132 | 129 |
this.#drawTop = $drawArea.position().top |
| 133 | 130 |
this.#drawRight = $drawArea.width() |
| 134 |
this.#drawLeft = $ganttArea ? $ganttArea.scrollLeft() : 0 |
|
| 135 | 131 |
} |
| 136 | 132 | |
| 137 | 133 |
#drawSelectedColumns() {
|
| 138 |
const $selectedColumns = this.$("td.gantt_selected_column")
|
|
| 134 |
const selectedColumns = this.element.querySelectorAll(".gantt_selected_column")
|
|
| 139 | 135 |
const $subjectsContainer = this.$(".gantt_subjects_container")
|
| 140 | 136 | |
| 141 | 137 |
const isMobileDevice = typeof window.isMobile === "function" && window.isMobile() |
| 142 | 138 | |
| 143 | 139 |
if (this.showSelectedColumnsValue) {
|
| 144 | 140 |
if (isMobileDevice) {
|
| 145 |
$selectedColumns.each((_, element) => {
|
|
| 146 |
this.$(element).hide() |
|
| 147 |
}) |
|
| 141 |
selectedColumns.forEach((element) => { element.hidden = true })
|
|
| 148 | 142 |
} else {
|
| 149 | 143 |
$subjectsContainer.addClass("draw_selected_columns")
|
| 150 |
$selectedColumns.show()
|
|
| 144 |
selectedColumns.forEach((element) => { element.hidden = false })
|
|
| 151 | 145 |
} |
| 152 | 146 |
} else {
|
| 153 |
$selectedColumns.each((_, element) => {
|
|
| 154 |
this.$(element).hide() |
|
| 155 |
}) |
|
| 147 |
selectedColumns.forEach((element) => { element.hidden = true })
|
|
| 156 | 148 |
$subjectsContainer.removeClass("draw_selected_columns")
|
| 157 | 149 |
} |
| 158 | 150 |
} |
| ... | ... | |
| 193 | 185 |
if (!issueTo.is(":visible")) return
|
| 194 | 186 | |
| 195 | 187 |
const issueHeight = issueFrom.height() |
| 196 |
const issueFromTop = issueFrom.position().top + issueHeight / 2 - this.#drawTop
|
|
| 188 |
const issueFromTop = this.#taskTop(issueFrom) + issueHeight / 2 - this.#drawTop
|
|
| 197 | 189 |
const issueFromRight = issueFrom.position().left + issueFrom.width() |
| 198 |
const issueToTop = issueTo.position().top + issueHeight / 2 - this.#drawTop
|
|
| 190 |
const issueToTop = this.#taskTop(issueTo) + issueHeight / 2 - this.#drawTop
|
|
| 199 | 191 |
const issueToLeft = issueTo.position().left |
| 200 | 192 |
const relationConfig = this.issueRelationTypesValue[relation.rel_type] || {}
|
| 201 | 193 |
const color = relationConfig.color || "#000" |
| ... | ... | |
| 206 | 198 |
this.#drawPath( |
| 207 | 199 |
[ |
| 208 | 200 |
"M", |
| 209 |
issueFromRight + this.#drawLeft,
|
|
| 201 |
issueFromRight, |
|
| 210 | 202 |
issueFromTop, |
| 211 | 203 |
"L", |
| 212 |
issueFromRightRel + this.#drawLeft,
|
|
| 204 |
issueFromRightRel, |
|
| 213 | 205 |
issueFromTop |
| 214 | 206 |
], |
| 215 | 207 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 219 | 211 |
this.#drawPath( |
| 220 | 212 |
[ |
| 221 | 213 |
"M", |
| 222 |
issueFromRightRel + this.#drawLeft,
|
|
| 214 |
issueFromRightRel, |
|
| 223 | 215 |
issueFromTop, |
| 224 | 216 |
"L", |
| 225 |
issueFromRightRel + this.#drawLeft,
|
|
| 217 |
issueFromRightRel, |
|
| 226 | 218 |
issueToTop |
| 227 | 219 |
], |
| 228 | 220 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 230 | 222 |
this.#drawPath( |
| 231 | 223 |
[ |
| 232 | 224 |
"M", |
| 233 |
issueFromRightRel + this.#drawLeft,
|
|
| 225 |
issueFromRightRel, |
|
| 234 | 226 |
issueToTop, |
| 235 | 227 |
"L", |
| 236 |
issueToLeft + this.#drawLeft,
|
|
| 228 |
issueToLeft, |
|
| 237 | 229 |
issueToTop |
| 238 | 230 |
], |
| 239 | 231 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 243 | 235 |
this.#drawPath( |
| 244 | 236 |
[ |
| 245 | 237 |
"M", |
| 246 |
issueFromRightRel + this.#drawLeft,
|
|
| 238 |
issueFromRightRel, |
|
| 247 | 239 |
issueFromTop, |
| 248 | 240 |
"L", |
| 249 |
issueFromRightRel + this.#drawLeft,
|
|
| 241 |
issueFromRightRel, |
|
| 250 | 242 |
issueMiddleTop |
| 251 | 243 |
], |
| 252 | 244 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 254 | 246 |
this.#drawPath( |
| 255 | 247 |
[ |
| 256 | 248 |
"M", |
| 257 |
issueFromRightRel + this.#drawLeft,
|
|
| 249 |
issueFromRightRel, |
|
| 258 | 250 |
issueMiddleTop, |
| 259 | 251 |
"L", |
| 260 |
issueToLeftRel + this.#drawLeft,
|
|
| 252 |
issueToLeftRel, |
|
| 261 | 253 |
issueMiddleTop |
| 262 | 254 |
], |
| 263 | 255 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 265 | 257 |
this.#drawPath( |
| 266 | 258 |
[ |
| 267 | 259 |
"M", |
| 268 |
issueToLeftRel + this.#drawLeft,
|
|
| 260 |
issueToLeftRel, |
|
| 269 | 261 |
issueMiddleTop, |
| 270 | 262 |
"L", |
| 271 |
issueToLeftRel + this.#drawLeft,
|
|
| 263 |
issueToLeftRel, |
|
| 272 | 264 |
issueToTop |
| 273 | 265 |
], |
| 274 | 266 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 276 | 268 |
this.#drawPath( |
| 277 | 269 |
[ |
| 278 | 270 |
"M", |
| 279 |
issueToLeftRel + this.#drawLeft,
|
|
| 271 |
issueToLeftRel, |
|
| 280 | 272 |
issueToTop, |
| 281 | 273 |
"L", |
| 282 |
issueToLeft + this.#drawLeft,
|
|
| 274 |
issueToLeft, |
|
| 283 | 275 |
issueToTop |
| 284 | 276 |
], |
| 285 | 277 |
{ stroke: color, "stroke-width": RELATION_STROKE_WIDTH, fill: "none" }
|
| ... | ... | |
| 288 | 280 |
this.#drawPath( |
| 289 | 281 |
[ |
| 290 | 282 |
"M", |
| 291 |
issueToLeft + this.#drawLeft,
|
|
| 283 |
issueToLeft, |
|
| 292 | 284 |
issueToTop, |
| 293 | 285 |
"l", |
| 294 | 286 |
-4 * RELATION_STROKE_WIDTH, |
| ... | ... | |
| 306 | 298 |
}) |
| 307 | 299 |
} |
| 308 | 300 | |
| 301 |
#taskTop($task) {
|
|
| 302 |
const row = $task.closest(".gantt-row")
|
|
| 303 |
return row.position().top + $task.position().top |
|
| 304 |
} |
|
| 305 | ||
| 309 | 306 |
get #progressLinesArray() {
|
| 310 | 307 |
const lines = [] |
| 311 | 308 |
const todayLeft = this.$("#today_line").position().left
|
| ... | ... | |
| 382 | 379 |
(previous.is_left_edge && current.is_left_edge) |
| 383 | 380 |
) |
| 384 | 381 |
) {
|
| 385 |
const x1 = previous.left === 0 ? 0 : previous.left + this.#drawLeft
|
|
| 386 |
const x2 = current.left === 0 ? 0 : current.left + this.#drawLeft
|
|
| 382 |
const x1 = previous.left |
|
| 383 |
const x2 = current.left |
|
| 387 | 384 | |
| 388 | 385 |
this.#drawPath(["M", x1, previous.top, "L", x2, current.top], {
|
| 389 | 386 |
stroke: color, |
| ... | ... | |
| 393 | 390 |
} |
| 394 | 391 |
} |
| 395 | 392 |
} |
| 393 | ||
| 396 | 394 |
} |
| app/javascript/controllers/gantt/column_controller.js | ||
|---|---|---|
| 42 | 42 |
} |
| 43 | 43 | |
| 44 | 44 |
#setupResizable() {
|
| 45 |
const alsoResize = [ |
|
| 46 |
`.gantt_${this.columnValue}_container`,
|
|
| 47 |
`.gantt_${this.columnValue}_container > .gantt_hdr`
|
|
| 48 |
] |
|
| 49 | 45 |
const options = {
|
| 50 | 46 |
handles: "e", |
| 51 | 47 |
minWidth: this.minWidthValue, |
| 52 | 48 |
zIndex: 30, |
| 53 |
alsoResize: alsoResize.join(","),
|
|
| 54 |
create: () => {
|
|
| 55 |
this.$(".ui-resizable-e").css("cursor", "ew-resize")
|
|
| 49 |
resize: (_event, ui) => {
|
|
| 50 |
this.element.style.setProperty("--gantt-column-width", `${ui.size.width}px`)
|
|
| 51 |
this.element.style.removeProperty("width")
|
|
| 56 | 52 |
} |
| 57 | 53 |
} |
| 58 | 54 | |
| app/javascript/controllers/gantt/subjects_controller.js | ||
|---|---|---|
| 33 | 33 |
const $element = this.$(element) |
| 34 | 34 |
const json = $element.data("collapse-expand")
|
| 35 | 35 |
const numberOfRows = $element.data("number-of-rows")
|
| 36 |
const barsSelector = `#gantt_area form > div[data-collapse-expand='${json.obj_id}'][data-number-of-rows='${numberOfRows}']`
|
|
| 37 |
const selectedColumnsSelector = `td.gantt_selected_column div[data-collapse-expand='${json.obj_id}'][data-number-of-rows='${numberOfRows}']`
|
|
| 36 |
const barsSelector = `#gantt_area .gantt-timeline-body form > div[data-collapse-expand='${json.obj_id}'][data-number-of-rows='${numberOfRows}']`
|
|
| 37 |
const selectedColumnsSelector = `.gantt_selected_column div[data-collapse-expand='${json.obj_id}'][data-number-of-rows='${numberOfRows}']`
|
|
| 38 | 38 | |
| 39 | 39 |
if (outOfHierarchy || this.#readInlineStart($element) <= subjectInlineStart) {
|
| 40 | 40 |
outOfHierarchy = true |
| ... | ... | |
| 106 | 106 |
#setBlockStart(el, value) {
|
| 107 | 107 |
const node = el.jquery ? el[0] : el |
| 108 | 108 |
const px = typeof value === "number" ? `${value}px` : value
|
| 109 |
node.style.setProperty("inset-block-start", px)
|
|
| 109 |
node.style.setProperty("--gantt-row-top", px)
|
|
| 110 | 110 |
} |
| 111 | 111 | |
| 112 | 112 |
#setIconState(element, open) {
|
| app/views/gantts/_chart.html.erb | ||
|---|---|---|
| 1 | 1 |
<% |
| 2 |
zoom = 1 |
|
| 3 |
gantt.zoom.times { zoom *= 2 }
|
|
| 4 | ||
| 5 |
subject_width = 330 |
|
| 6 |
header_height = 18 |
|
| 7 | ||
| 8 |
headers_height = header_height |
|
| 9 |
show_weeks = false |
|
| 10 |
show_days = false |
|
| 11 |
show_day_num = false |
|
| 12 | ||
| 13 |
if gantt.zoom > 1 |
|
| 14 |
show_weeks = true |
|
| 15 |
headers_height = 2 * header_height |
|
| 16 |
if gantt.zoom > 2 |
|
| 17 |
show_days = true |
|
| 18 |
headers_height = 3 * header_height |
|
| 19 |
if gantt.zoom > 3 |
|
| 20 |
show_day_num = true |
|
| 21 |
headers_height = 4 * header_height |
|
| 22 |
end |
|
| 23 |
end |
|
| 24 |
end |
|
| 25 | ||
| 26 |
g_width = ((gantt.date_to - gantt.date_from + 1) * zoom).to_i |
|
| 2 |
layout = GanttHelper::ChartLayout.new(gantt) |
|
| 27 | 3 |
gantt.render( |
| 28 |
top: headers_height + 8,
|
|
| 29 |
zoom: zoom, |
|
| 30 |
g_width: g_width,
|
|
| 31 |
subject_width: subject_width |
|
| 4 |
top: layout.content_top,
|
|
| 5 |
zoom: layout.zoom,
|
|
| 6 |
g_width: layout.chart_width,
|
|
| 7 |
subject_width: layout.subject_width
|
|
| 32 | 8 |
) |
| 33 |
g_height = [(20 * (gantt.number_of_rows + 6)) + 150, 206].max |
|
| 34 |
t_height = g_height + headers_height |
|
| 35 | 9 |
%> |
| 36 | 10 | |
| 37 | 11 |
<% if gantt.truncated %> |
| 38 | 12 |
<p class="warning"><%= l(:notice_gantt_chart_truncated, max: gantt.max_rows) %></p> |
| 39 | 13 |
<% end %> |
| 40 | 14 | |
| 41 |
<%= gantt_chart_tag(@query) do %> |
|
| 42 |
<tr> |
|
| 43 |
<%= gantt_column_tag('subjects', min_width: 100,
|
|
| 44 |
style: "width:#{query.draw_selected_columns ? subject_width + 1 : subject_width + 2}px;") do %>
|
|
| 45 |
<% |
|
| 46 |
subjects_container_classes = "gantt_subjects_container" |
|
| 47 |
subjects_container_classes << " draw_selected_columns" if query.draw_selected_columns |
|
| 48 |
subjects_container_style = +"position:relative;" |
|
| 49 |
subjects_container_style << "height: #{t_height + 24}px;"
|
|
| 50 |
subjects_container_style << "width: #{subject_width + 1}px;"
|
|
| 51 |
%> |
|
| 52 |
<%= content_tag(:div, |
|
| 53 |
style: subjects_container_style, |
|
| 54 |
class: subjects_container_classes, |
|
| 55 |
data: {'gantt--chart-target': 'subjectsContainer'}) do %>
|
|
| 56 |
<% |
|
| 57 |
header_bg_style = +"width: #{subject_width + 1}px;"
|
|
| 58 |
header_bg_style << "height: #{headers_height}px;"
|
|
| 59 |
header_bg_style << 'background: #f1f3f5;' # oc-gray-1 |
|
| 60 |
%> |
|
| 61 |
<%= content_tag(:div, "", style: header_bg_style, class: "gantt_hdr") %> |
|
| 62 |
<% |
|
| 63 |
header_overlay_style = +"z-index: 1;" |
|
| 64 |
header_overlay_style << "width: #{subject_width + 1}px;"
|
|
| 65 |
header_overlay_style << "height: #{t_height}px;"
|
|
| 66 |
header_overlay_style << 'overflow: hidden;' |
|
| 67 |
%> |
|
| 68 |
<%= content_tag(:div, "", style: header_overlay_style, class: "gantt_hdr") %> |
|
| 15 |
<%= gantt_chart_tag(@query, layout) do %> |
|
| 16 |
<%= gantt_column_tag('subjects', min_width: 100, width: 'var(--gantt-subject-width)') do %>
|
|
| 17 |
<section class="gantt-pane gantt_subjects_container<%= ' draw_selected_columns' if query.draw_selected_columns %>" |
|
| 18 |
data-gantt--chart-target="subjectsContainer"> |
|
| 19 |
<header class="gantt-pane-header" aria-hidden="true"></header> |
|
| 20 |
<div class="gantt-pane-body"> |
|
| 69 | 21 |
<%= gantt_subjects_tag do %> |
| 70 | 22 |
<%= form_tag({}, data: {cm_url: issues_context_menu_path}) do %>
|
| 71 | 23 |
<%= hidden_field_tag 'back_url', url_for(params: request.query_parameters), id: nil %> |
| 72 | 24 |
<%= gantt.subjects.html_safe %> |
| 73 | 25 |
<% end %> |
| 74 | 26 |
<% end %> |
| 75 |
<% end %> |
|
| 76 |
<% end %> |
|
| 77 |
<% |
|
| 78 |
query.columns.each do |column| |
|
| 79 |
next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) |
|
| 80 |
column_name = column.name.to_s.tr('.', '_')
|
|
| 81 |
%> |
|
| 82 |
<%= gantt_column_tag(column_name, min_width: 20, id: column_name, |
|
| 83 |
class: ['gantt_selected_column', { 'last_gantt_selected_column': query.columns.last == column }]) do %>
|
|
| 84 |
<% |
|
| 85 |
column_container_style = +"position: relative;" |
|
| 86 |
column_container_style << "height: #{t_height + 24}px;"
|
|
| 87 |
%> |
|
| 88 |
<%= content_tag(:div, style: column_container_style, class: "gantt_#{column_name}_container gantt_selected_column_container") do %>
|
|
| 89 |
<% |
|
| 90 |
column_header_overlay_style = +"height: #{t_height}px;"
|
|
| 91 |
column_header_overlay_style << 'overflow: hidden;' |
|
| 92 |
%> |
|
| 93 |
<%= content_tag(:div, '', style: column_header_overlay_style, class: "gantt_hdr") %> |
|
| 94 |
<% |
|
| 95 |
column_header_style = +"height: #{headers_height}px;"
|
|
| 96 |
column_header_style << 'background: #f1f3f5;' # oc-gray-1 |
|
| 97 |
%> |
|
| 98 |
<%= content_tag(:div, |
|
| 99 |
content_tag(:p, column.caption, class: 'gantt_hdr_selected_column_name'), |
|
| 100 |
style: column_header_style, |
|
| 101 |
class: "gantt_hdr") %> |
|
| 102 |
<%= content_tag(:div, class: "gantt_#{column_name} gantt_selected_column_content") do %>
|
|
| 103 |
<%= gantt.selected_column_content({column: column, top: headers_height + 8, zoom: zoom, g_width: g_width}).html_safe %>
|
|
| 104 |
<% end %> |
|
| 105 |
<% end %> |
|
| 106 |
<% end %> |
|
| 27 |
</div> |
|
| 28 |
</section> |
|
| 29 |
<% end %> |
|
| 30 | ||
| 31 |
<% |
|
| 32 |
query.columns.each do |column| |
|
| 33 |
next if Redmine::Helpers::Gantt::UNAVAILABLE_COLUMNS.include?(column.name) |
|
| 34 |
column_name = column.name.to_s.tr('.', '_')
|
|
| 35 |
%> |
|
| 36 |
<%= gantt_column_tag(column_name, min_width: 20, width: '50px', id: column_name, |
|
| 37 |
class: 'gantt_selected_column', hidden: !query.draw_selected_columns) do %> |
|
| 38 |
<section class="gantt-pane gantt_selected_column_container gantt_<%= column_name %>_container"> |
|
| 39 |
<header class="gantt-pane-header gantt-selected-column-header"><%= column.caption %></header> |
|
| 40 |
<div class="gantt-pane-body gantt_selected_column_content gantt_<%= column_name %>"> |
|
| 41 |
<%= gantt.selected_column_content(column: column, top: layout.content_top, zoom: layout.zoom, g_width: layout.chart_width).html_safe %> |
|
| 42 |
</div> |
|
| 43 |
</section> |
|
| 107 | 44 |
<% end %> |
| 108 |
<td> |
|
| 109 |
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt_area" data-gantt--chart-target="ganttArea"> |
|
| 110 |
<% |
|
| 111 |
months_header_style = +"width: #{g_width - 1}px;"
|
|
| 112 |
months_header_style << "height: #{headers_height}px;"
|
|
| 113 |
months_header_style << 'background: #f1f3f5;' # oc-gray-1 |
|
| 114 |
%> |
|
| 115 |
<%= content_tag(:div, ' '.html_safe, style: months_header_style, class: "gantt_hdr") %> |
|
| 45 |
<% end %> |
|
| 116 | 46 | |
| 117 |
<% month_f = gantt.date_from %> |
|
| 118 |
<% left = 0 %> |
|
| 119 |
<% months_height = (show_weeks ? header_height : header_height + g_height) %> |
|
| 120 |
<% gantt.months.times do %> |
|
| 121 |
<% width = (((month_f >> 1) - month_f) * zoom - 1).to_i %> |
|
| 122 |
<% month_style = +"inset-inline-start: #{left}px;" %>
|
|
| 123 |
<% month_style << "width: #{width}px;" %>
|
|
| 124 |
<% month_style << "height: #{months_height}px;" %>
|
|
| 125 |
<%= content_tag(:div, style: month_style, class: "gantt_hdr") do %> |
|
| 126 |
<%= link_to "#{month_f.year}-#{month_f.month}",
|
|
| 127 |
gantt.params.merge(year: month_f.year, month: month_f.month), |
|
| 128 |
title: "#{month_name(month_f.month)} #{month_f.year}" %>
|
|
| 47 |
<div id="gantt_area" class="gantt-timeline" data-gantt--chart-target="ganttArea"> |
|
| 48 |
<div class="gantt-timeline-canvas"> |
|
| 49 |
<header class="gantt-timeline-header"> |
|
| 50 |
<div class="gantt-header-row gantt-header-months"> |
|
| 51 |
<% layout.months.each do |period| %> |
|
| 52 |
<div class="gantt-period gantt-period-month<%= ' gantt-period-spans-body' if period.spans_body %><%= ' gantt-period-last' if period.last %>" |
|
| 53 |
style="<%= gantt_period_style(period) %>"> |
|
| 54 |
<%= link_to "#{period.date.year}-#{period.date.month}",
|
|
| 55 |
gantt.params.merge(year: period.date.year, month: period.date.month), |
|
| 56 |
title: "#{month_name(period.date.month)} #{period.date.year}" %>
|
|
| 57 |
</div> |
|
| 129 | 58 |
<% end %> |
| 130 |
<% left += width + 1 %> |
|
| 131 |
<% month_f = month_f >> 1 %> |
|
| 132 |
<% end %> |
|
| 59 |
</div> |
|
| 133 | 60 | |
| 134 |
<% if show_weeks %> |
|
| 135 |
<% left = 0 %> |
|
| 136 |
<% weeks_height = (show_days ? header_height - 1 : header_height - 1 + g_height) %> |
|
| 137 |
<% if gantt.date_from.cwday == 1 %> |
|
| 138 |
<% week_f = gantt.date_from %> |
|
| 139 |
<% else %> |
|
| 140 |
<% week_f = gantt.date_from + (7 - gantt.date_from.cwday + 1) %> |
|
| 141 |
<% width = (7 - gantt.date_from.cwday + 1) * zoom - 1 %> |
|
| 142 |
<% gap_style = +"inset-inline-start: #{left}px;" %>
|
|
| 143 |
<% gap_style << "inset-block-start: 19px;" %> |
|
| 144 |
<% gap_style << "width: #{width}px;" %>
|
|
| 145 |
<% gap_style << "height: #{weeks_height}px;" %>
|
|
| 146 |
<%= content_tag(:div, ' '.html_safe, style: gap_style, class: "gantt_hdr") %> |
|
| 147 |
<% left += width + 1 %> |
|
| 148 |
<% end %> |
|
| 149 |
<% while week_f <= gantt.date_to %> |
|
| 150 |
<% width = ((week_f + 6 <= gantt.date_to) ? 7 * zoom - 1 : (gantt.date_to - week_f + 1) * zoom - 1).to_i %> |
|
| 151 |
<% week_style = +"inset-inline-start: #{left}px;" %>
|
|
| 152 |
<% week_style << "inset-block-start: 19px;" %> |
|
| 153 |
<% week_style << "width: #{width}px;" %>
|
|
| 154 |
<% week_style << "height: #{weeks_height}px;" %>
|
|
| 155 |
<%= content_tag(:div, style: week_style, class: "gantt_hdr") do %> |
|
| 156 |
<%= content_tag(:small) do %> |
|
| 157 |
<%= week_f.cweek if width >= 16 %> |
|
| 158 |
<% end %> |
|
| 61 |
<% if layout.show_weeks? %> |
|
| 62 |
<div class="gantt-header-row gantt-header-weeks"> |
|
| 63 |
<% layout.weeks.each do |period| %> |
|
| 64 |
<div class="gantt-period gantt-period-week<%= ' gantt-period-spans-body' if period.spans_body %><%= ' gantt-period-last' if period.last %>" |
|
| 65 |
style="<%= gantt_period_style(period) %>"> |
|
| 66 |
<small><%= period.date.cweek if period.date && period.width >= 16 %></small> |
|
| 67 |
</div> |
|
| 159 | 68 |
<% end %> |
| 160 |
<% left += width + 1 %> |
|
| 161 |
<% week_f += 7 %> |
|
| 162 |
<% end %> |
|
| 69 |
</div> |
|
| 163 | 70 |
<% end %> |
| 164 | 71 | |
| 165 |
<% if show_day_num %> |
|
| 166 |
<% left = 0 %> |
|
| 167 |
<% days_height = g_height + header_height * 2 - 1 %> |
|
| 168 |
<% wday = gantt.date_from.cwday %> |
|
| 169 |
<% day_num = gantt.date_from %> |
|
| 170 |
<% (gantt.date_to - gantt.date_from + 1).to_i.times do %> |
|
| 171 |
<% width = zoom - 1 %> |
|
| 172 |
<% day_style = +"inset-inline-start:#{left}px;" %>
|
|
| 173 |
<% day_style << "inset-block-start:37px;" %> |
|
| 174 |
<% day_style << "width:#{width}px;" %>
|
|
| 175 |
<% day_style << "height:#{days_height}px;" %>
|
|
| 176 |
<% day_style << "font-size:0.7em;" %> |
|
| 177 |
<% day_classes = +"gantt_hdr" %> |
|
| 178 |
<% day_classes << " nwday" if gantt.non_working_week_days.include?(wday) %> |
|
| 179 |
<%= content_tag(:div, style: day_style, class: day_classes) do %> |
|
| 180 |
<%= day_num.day %> |
|
| 72 |
<% if layout.show_day_numbers? %> |
|
| 73 |
<div class="gantt-header-row gantt-header-day-numbers"> |
|
| 74 |
<% layout.day_numbers.each do |period| %> |
|
| 75 |
<div class="gantt-period gantt-period-day-number<%= ' gantt-period-non-working' if period.non_working %><%= ' gantt-period-last' if period.last %>" |
|
| 76 |
style="<%= gantt_period_style(period) %>"><%= period.date.day %></div> |
|
| 181 | 77 |
<% end %> |
| 182 |
<% left += width + 1 %> |
|
| 183 |
<% day_num += 1 %> |
|
| 184 |
<% wday += 1 %> |
|
| 185 |
<% wday = 1 if wday > 7 %> |
|
| 186 |
<% end %> |
|
| 78 |
</div> |
|
| 187 | 79 |
<% end %> |
| 188 | 80 | |
| 189 |
<% if show_days %> |
|
| 190 |
<% left = 0 %> |
|
| 191 |
<% days_height = g_height + header_height - 1 %> |
|
| 192 |
<% days_top = (show_day_num ? 55 : 37) %> |
|
| 193 |
<% (gantt.date_from..gantt.date_to).each do |g_date| %> |
|
| 194 |
<% width = zoom - 1 %> |
|
| 195 |
<% day_style = +"inset-inline-start: #{left}px;" %>
|
|
| 196 |
<% day_style << "inset-block-start: #{days_top}px;" %>
|
|
| 197 |
<% day_style << "width: #{width}px;" %>
|
|
| 198 |
<% day_style << "height: #{days_height}px;" %>
|
|
| 199 |
<% day_style << "font-size:0.7em;" %> |
|
| 200 |
<% day_classes = +"gantt_hdr" %> |
|
| 201 |
<% day_classes << " nwday" if gantt.non_working_week_days.include?(g_date.cwday) %> |
|
| 202 |
<%= content_tag(:div, style: day_style, class: day_classes) do %> |
|
| 203 |
<%= day_letter(g_date.cwday) %> |
|
| 81 |
<% if layout.show_days? %> |
|
| 82 |
<div class="gantt-header-row gantt-header-days"> |
|
| 83 |
<% layout.days.each do |period| %> |
|
| 84 |
<div class="gantt-period gantt-period-day gantt-period-spans-body<%= ' gantt-period-non-working' if period.non_working %><%= ' gantt-period-last' if period.last %>" |
|
| 85 |
style="<%= gantt_period_style(period) %>"><%= day_letter(period.date.cwday) %></div> |
|
| 204 | 86 |
<% end %> |
| 205 |
<% left += width + 1 %> |
|
| 206 |
<% end %> |
|
| 87 |
</div> |
|
| 207 | 88 |
<% end %> |
| 89 |
</header> |
|
| 208 | 90 | |
| 91 |
<div class="gantt-timeline-body"> |
|
| 209 | 92 |
<%= form_tag({}, data: {cm_url: issues_context_menu_path}) do %>
|
| 210 | 93 |
<%= hidden_field_tag 'back_url', url_for(params: request.query_parameters), id: nil %> |
| 211 | 94 |
<%= gantt.lines.html_safe %> |
| 212 | 95 |
<% end %> |
| 213 | 96 | |
| 214 |
<% if User.current.today >= gantt.date_from && User.current.today <= gantt.date_to %> |
|
| 215 |
<% today_left = (((User.current.today - gantt.date_from + 1) * zoom).floor - 1).to_i %> |
|
| 216 |
<% today_style = +"position: absolute;" %> |
|
| 217 |
<% today_style << "height: #{g_height}px;" %>
|
|
| 218 |
<% today_style << "inset-block-start: #{headers_height + 1}px;" %>
|
|
| 219 |
<% today_style << "inset-inline-start: #{today_left}px;" %>
|
|
| 220 |
<% today_style << "width:10px;" %> |
|
| 221 |
<% today_style << "border-inline-start: 1px dashed red;" %> |
|
| 222 |
<%= content_tag(:div, ' '.html_safe, style: today_style, id: 'today_line') %> |
|
| 97 |
<% if layout.today_start %> |
|
| 98 |
<div id="today_line" style="<%= gantt_css_variables('gantt-today-start': "#{layout.today_start}px") %>" aria-hidden="true"></div>
|
|
| 223 | 99 |
<% end %> |
| 224 | 100 | |
| 225 |
<% |
|
| 226 |
draw_area_style = +"position: absolute;" |
|
| 227 |
draw_area_style << "height: #{g_height}px;"
|
|
| 228 |
draw_area_style << "inset-block-start: #{headers_height + 1}px;"
|
|
| 229 |
draw_area_style << 'inset-inline-start: 0px;' |
|
| 230 |
draw_area_style << "width: #{g_width - 1}px;"
|
|
| 231 |
%> |
|
| 232 |
<%= content_tag(:div, '', style: draw_area_style, id: "gantt_draw_area", data: {'gantt--chart-target': 'drawArea'}) %>
|
|
| 101 |
<div id="gantt_draw_area" data-gantt--chart-target="drawArea" aria-hidden="true"></div> |
|
| 233 | 102 |
</div> |
| 234 |
</td>
|
|
| 235 |
</tr>
|
|
| 103 |
</div>
|
|
| 104 |
</div>
|
|
| 236 | 105 |
<% end %> |
| 237 | 106 | |
| 238 | 107 |
<span class="pagination"> |
| lib/redmine/helpers/gantt.rb | ||
|---|---|---|
| 283 | 283 |
def render_object_row(object, options) |
| 284 | 284 |
class_name = object.class.name.downcase |
| 285 | 285 |
send(:"subject_for_#{class_name}", object, options) unless options[:only] == :lines || options[:only] == :selected_columns
|
| 286 |
send(:"line_for_#{class_name}", object, options) unless options[:only] == :subjects || options[:only] == :selected_columns
|
|
| 287 |
column_content_for_issue(object, options) if options[:only] == :selected_columns && options[:column].present? && object.is_a?(Issue) |
|
| 286 |
unless options[:only] == :subjects || options[:only] == :selected_columns |
|
| 287 |
line = send(:"line_for_#{class_name}", object, options)
|
|
| 288 |
@lines << html_gantt_row(object, options, line) if options[:format] == :html |
|
| 289 |
end |
|
| 290 |
column_content_for_object(object, options) if options[:only] == :selected_columns && options[:column].present? |
|
| 288 | 291 |
options[:top] += options[:top_increment] |
| 289 | 292 |
@number_of_rows += 1 |
| 290 | 293 |
if @max_rows && @number_of_rows >= @max_rows |
| ... | ... | |
| 354 | 357 |
end |
| 355 | 358 |
end |
| 356 | 359 | |
| 357 |
def column_content_for_issue(issue, options)
|
|
| 360 |
def column_content_for_object(object, options)
|
|
| 358 | 361 |
if options[:format] == :html |
| 359 |
data_options = {}
|
|
| 360 |
data_options[:collapse_expand] = "issue-#{issue.id}"
|
|
| 361 |
data_options[:number_of_rows] = number_of_rows |
|
| 362 |
style = "position: absolute;inset-block-start: #{options[:top]}px; font-size: 0.8em;"
|
|
| 362 |
data_options = {
|
|
| 363 |
:collapse_expand => "#{object.class}-#{object.id}".downcase,
|
|
| 364 |
:number_of_rows => number_of_rows |
|
| 365 |
} |
|
| 366 |
value = object.is_a?(Issue) ? view.column_content(options[:column], object) : nil |
|
| 363 | 367 |
content = |
| 364 | 368 |
view.content_tag( |
| 365 |
:div, view.column_content(options[:column], issue), |
|
| 366 |
:style => style, :class => "issue_#{options[:column].name}",
|
|
| 367 |
:id => "#{options[:column].name}_issue_#{issue.id}",
|
|
| 369 |
:div, value, |
|
| 370 |
:style => css_variables('gantt-row-top': "#{options[:top] || 0}px"),
|
|
| 371 |
:class => [ |
|
| 372 |
"gantt-row", |
|
| 373 |
"gantt-column-row", |
|
| 374 |
("issue_#{options[:column].name}" if object.is_a?(Issue))
|
|
| 375 |
], |
|
| 376 |
:id => ("#{options[:column].name}_issue_#{object.id}" if object.is_a?(Issue)),
|
|
| 368 | 377 |
:data => data_options |
| 369 | 378 |
) |
| 370 | 379 |
@columns[options[:column].name] << content if @columns.has_key?(options[:column].name) |
| ... | ... | |
| 372 | 381 |
end |
| 373 | 382 |
end |
| 374 | 383 | |
| 384 |
def column_content_for_issue(issue, options) |
|
| 385 |
column_content_for_object(issue, options) |
|
| 386 |
end |
|
| 387 | ||
| 375 | 388 |
def subject(label, options, object=nil) |
| 376 | 389 |
send :"#{options[:format]}_subject", options, label, object
|
| 377 | 390 |
end |
| ... | ... | |
| 750 | 763 |
s << view.assignee_avatar(issue.assigned_to, :size => 13, :class => 'icon-avatar') |
| 751 | 764 |
s << view.link_to_issue(issue).html_safe |
| 752 | 765 |
s << view.content_tag(:input, nil, :type => 'checkbox', :name => 'ids[]', |
| 753 |
:value => issue.id, :style => 'display:none;',
|
|
| 766 |
:value => issue.id, |
|
| 754 | 767 |
:class => 'toggle-selection') |
| 755 | 768 |
view.content_tag(:span, s, :class => css_classes).html_safe |
| 756 | 769 |
when Version |
| ... | ... | |
| 827 | 840 |
params[:indent] += 18 |
| 828 | 841 |
end |
| 829 | 842 |
end |
| 830 |
style = "position: absolute;inset-block-start:#{params[:top]}px;inset-inline-start:#{params[:indent]}px;"
|
|
| 831 |
style += "width:#{params[:subject_width] - params[:indent]}px;" if params[:subject_width]
|
|
| 832 |
tag_options[:style] = style |
|
| 843 |
tag_options[:class] = [tag_options[:class], 'gantt-row', 'gantt-subject-row'].compact.join(' ')
|
|
| 844 |
row_variables = {
|
|
| 845 |
'gantt-row-top': "#{params[:top] || 0}px",
|
|
| 846 |
'gantt-row-indent': "#{params[:indent]}px"
|
|
| 847 |
} |
|
| 848 |
if params[:subject_width] |
|
| 849 |
row_variables['gantt-row-width'] = "#{params[:subject_width] - params[:indent]}px"
|
|
| 850 |
end |
|
| 851 |
tag_options[:style] = css_variables(row_variables) |
|
| 833 | 852 |
output = view.content_tag(:div, content, tag_options) |
| 834 | 853 |
@subjects << output |
| 835 | 854 |
output |
| ... | ... | |
| 889 | 908 |
# Renders the task bar, with progress and late |
| 890 | 909 |
if coords[:bar_start] && coords[:bar_end] |
| 891 | 910 |
width = coords[:bar_end] - coords[:bar_start] - 2 |
| 892 |
style = +"" |
|
| 893 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 894 |
style << "inset-inline-start:#{coords[:bar_start]}px;"
|
|
| 895 |
style << "width:#{width}px;"
|
|
| 911 |
style = task_style(coords[:bar_start], width) |
|
| 896 | 912 |
html_id = "task-todo-issue-#{object.id}" if object.is_a?(Issue)
|
| 897 | 913 |
html_id = "task-todo-version-#{object.id}" if object.is_a?(Version)
|
| 898 | 914 |
content_opt = {:style => style,
|
| ... | ... | |
| 909 | 925 |
output << view.content_tag(:div, ' '.html_safe, content_opt) |
| 910 | 926 |
if coords[:bar_late_end] |
| 911 | 927 |
width = coords[:bar_late_end] - coords[:bar_start] - 2 |
| 912 |
style = +"" |
|
| 913 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 914 |
style << "inset-inline-start:#{coords[:bar_start]}px;"
|
|
| 915 |
style << "width:#{width}px;"
|
|
| 928 |
style = task_style(coords[:bar_start], width) |
|
| 916 | 929 |
output << view.content_tag(:div, ' '.html_safe, |
| 917 | 930 |
:style => style, |
| 918 | 931 |
:class => "#{css} task_late",
|
| ... | ... | |
| 920 | 933 |
end |
| 921 | 934 |
if coords[:bar_progress_end] |
| 922 | 935 |
width = coords[:bar_progress_end] - coords[:bar_start] - 2 |
| 923 |
style = +"" |
|
| 924 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 925 |
style << "inset-inline-start:#{coords[:bar_start]}px;"
|
|
| 926 |
style << "width:#{width}px;"
|
|
| 936 |
style = task_style(coords[:bar_start], width) |
|
| 927 | 937 |
html_id = "task-done-issue-#{object.id}" if object.is_a?(Issue)
|
| 928 | 938 |
html_id = "task-done-version-#{object.id}" if object.is_a?(Version)
|
| 929 | 939 |
output << view.content_tag(:div, ' '.html_safe, |
| ... | ... | |
| 936 | 946 |
# Renders the markers |
| 937 | 947 |
if markers |
| 938 | 948 |
if coords[:start] |
| 939 |
style = +"" |
|
| 940 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 941 |
style << "inset-inline-start:#{coords[:start]}px;"
|
|
| 942 |
style << "width:15px;" |
|
| 949 |
style = task_style(coords[:start], 15) |
|
| 943 | 950 |
output << view.content_tag(:div, ' '.html_safe, |
| 944 | 951 |
:style => style, |
| 945 | 952 |
:class => "#{css} marker starting",
|
| 946 | 953 |
:data => data_options) |
| 947 | 954 |
end |
| 948 | 955 |
if coords[:end] |
| 949 |
style = +"" |
|
| 950 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 951 |
style << "inset-inline-start:#{coords[:end]}px;"
|
|
| 952 |
style << "width:15px;" |
|
| 956 |
style = task_style(coords[:end], 15) |
|
| 953 | 957 |
output << view.content_tag(:div, ' '.html_safe, |
| 954 | 958 |
:style => style, |
| 955 | 959 |
:class => "#{css} marker ending",
|
| ... | ... | |
| 958 | 962 |
end |
| 959 | 963 |
# Renders the label on the right |
| 960 | 964 |
if label |
| 961 |
style = +"" |
|
| 962 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 963 |
style << "inset-inline-start:#{(coords[:bar_end] || 0) + 8}px;"
|
|
| 964 |
style << "width:15px;" |
|
| 965 |
style = task_style((coords[:bar_end] || 0) + 8, 15) |
|
| 965 | 966 |
output << view.content_tag(:div, label, |
| 966 | 967 |
:style => style, |
| 967 | 968 |
:class => "#{css} label",
|
| ... | ... | |
| 973 | 974 |
view.render_issue_tooltip(object).html_safe, |
| 974 | 975 |
:class => "tip") |
| 975 | 976 |
s += view.content_tag(:input, nil, :type => 'checkbox', :name => 'ids[]', |
| 976 |
:value => object.id, :style => 'display:none;',
|
|
| 977 |
:value => object.id, |
|
| 977 | 978 |
:class => 'toggle-selection') |
| 978 |
style = +"" |
|
| 979 |
style << "position: absolute;" |
|
| 980 |
style << "inset-block-start:#{params[:top]}px;"
|
|
| 981 |
style << "inset-inline-start:#{coords[:bar_start]}px;"
|
|
| 982 |
style << "width:#{coords[:bar_end] - coords[:bar_start]}px;"
|
|
| 983 |
style << "height:12px;" |
|
| 979 |
style = task_style(coords[:bar_start], coords[:bar_end] - coords[:bar_start]) |
|
| 984 | 980 |
output << view.content_tag(:div, s.html_safe, |
| 985 | 981 |
:style => style, |
| 986 | 982 |
:class => "tooltip hascontextmenu", |
| 987 | 983 |
:data => data_options) |
| 988 | 984 |
end |
| 989 |
@lines << output |
|
| 990 | 985 |
output |
| 991 | 986 |
end |
| 992 | 987 | |
| 988 |
def html_gantt_row(object, params, content) |
|
| 989 |
data_options = {
|
|
| 990 |
:collapse_expand => "#{object.class}-#{object.id}".downcase,
|
|
| 991 |
:number_of_rows => number_of_rows |
|
| 992 |
} |
|
| 993 |
style = css_variables('gantt-row-top': "#{params[:top] || 0}px")
|
|
| 994 |
view.content_tag( |
|
| 995 |
:div, |
|
| 996 |
content.to_s.html_safe, |
|
| 997 |
:class => 'gantt-row gantt-timeline-row', |
|
| 998 |
:style => style, |
|
| 999 |
:data => data_options |
|
| 1000 |
) |
|
| 1001 |
end |
|
| 1002 | ||
| 1003 |
def task_style(start, width) |
|
| 1004 |
css_variables( |
|
| 1005 |
'gantt-task-start': "#{start}px",
|
|
| 1006 |
'gantt-task-width': "#{width}px"
|
|
| 1007 |
) |
|
| 1008 |
end |
|
| 1009 | ||
| 1010 |
def css_variables(variables) |
|
| 1011 |
variables.map {|name, value| "--#{name}:#{value}"}.join(';')
|
|
| 1012 |
end |
|
| 1013 | ||
| 993 | 1014 |
def pdf_task(params, coords, markers, label, object) |
| 994 | 1015 |
cell_height_ratio = params[:pdf].get_cell_height_ratio |
| 995 | 1016 |
params[:pdf].set_cell_height_ratio(0.1) |
| test/functional/gantts_controller_test.rb | ||
|---|---|---|
| 56 | 56 |
end |
| 57 | 57 | |
| 58 | 58 |
# Assert context menu on issues subject and gantt bar |
| 59 |
assert_select 'div[class=?]', 'issue-subject hascontextmenu'
|
|
| 59 |
assert_select 'div.issue-subject.hascontextmenu'
|
|
| 60 | 60 |
assert_select 'div.tooltip.hascontextmenu' do |
| 61 | 61 |
assert_select 'img[class="gravatar avatar"]' |
| 62 | 62 |
end |
| ... | ... | |
| 108 | 108 |
assert_response :success |
| 109 | 109 |
end |
| 110 | 110 | |
| 111 |
def test_gantt_should_warn_when_truncated |
|
| 112 |
with_settings :gantt_items_limit => '1' do |
|
| 113 |
get(:show, :params => {:project_id => 1})
|
|
| 114 |
end |
|
| 115 | ||
| 116 |
assert_response :success |
|
| 117 |
assert_select 'p.warning', :text => I18n.t(:notice_gantt_chart_truncated, :max => 1) |
|
| 118 |
end |
|
| 119 | ||
| 111 | 120 |
def test_show_should_run_custom_query |
| 112 | 121 |
query = IssueQuery.create!(:name => 'Gantt Query', :description => 'Description for Gantt Query', :visibility => IssueQuery::VISIBILITY_PUBLIC) |
| 113 | 122 |
get( |
| ... | ... | |
| 214 | 223 |
} |
| 215 | 224 |
) |
| 216 | 225 |
assert_response :success |
| 217 |
assert_select 'div.gantt_hdr>a', :text => /^[\d-]+$/, :count => 40
|
|
| 226 |
assert_select 'div.gantt-period-month>a', :text => /^[\d-]+$/, :count => 40
|
|
| 218 | 227 | |
| 219 | 228 |
# Displays 6 months (the default value for `months`) if `months` exceeds |
| 220 | 229 |
# gant_months_limit |
| ... | ... | |
| 227 | 236 |
} |
| 228 | 237 |
) |
| 229 | 238 |
assert_response :success |
| 230 |
assert_select 'div.gantt_hdr>a', :text => /^[\d-]+$/, :count => 6
|
|
| 239 |
assert_select 'div.gantt-period-month>a', :text => /^[\d-]+$/, :count => 6
|
|
| 231 | 240 |
end |
| 232 | 241 |
end |
| 233 | 242 | |
| ... | ... | |
| 313 | 322 | |
| 314 | 323 |
6.times do |offset| |
| 315 | 324 |
m = selected_start.since(offset.month) |
| 316 |
assert_select 'div.gantt_hdr > a', text: "#{m.year}-#{m.month}"
|
|
| 325 |
assert_select 'div.gantt-period-month > a', text: "#{m.year}-#{m.month}"
|
|
| 317 | 326 |
end |
| 318 | 327 | |
| 319 | 328 |
# eCookbook |
| ... | ... | |
| 357 | 366 |
6.times do |offset| |
| 358 | 367 |
m = start_of_month.since(offset.months) |
| 359 | 368 | |
| 360 |
assert_select 'div.gantt_hdr > a', text: "#{m.year}-#{m.month}"
|
|
| 369 |
assert_select 'div.gantt-period-month > a', text: "#{m.year}-#{m.month}"
|
|
| 361 | 370 |
end |
| 362 | 371 | |
| 363 | 372 |
assert_select 'input#months[value=?]', '6' |
| ... | ... | |
| 382 | 391 |
end |
| 383 | 392 | |
| 384 | 393 |
def assert_chart_row(selector, row:, style_substring:) |
| 394 |
style_substring = "--gantt-task-start:#{style_substring.delete_prefix('inset-inline-start:').sub(';width:', ';--gantt-task-width:')}"
|
|
| 385 | 395 |
matcher = "#gantt_area #{selector}[data-number-of-rows=?][style*=?]"
|
| 386 | 396 |
assert_select matcher, row, style_substring, minimum: 1 |
| 387 | 397 |
end |
- « Previous
- 1
- …
- 3
- 4
- 5
- Next »