Project

General

Profile

Feature #2024 » gantt-editting-r2924.patch

Hiroyuki Yoshioka, 2009-10-17 18:46

View differences:

app/controllers/issues_controller.rb (working copy)
18 18
class IssuesController < ApplicationController
19 19
  menu_item :new_issue, :only => :new
20 20
  
21
  before_filter :find_issue, :only => [:show, :edit, :reply]
21
  before_filter :find_issue, :only => [:show, :edit, :reply, :edit_gantt]
22 22
  before_filter :find_issues, :only => [:bulk_edit, :move, :destroy]
23 23
  before_filter :find_project, :only => [:new, :update_form, :preview]
24
  before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu]
24
  before_filter :authorize, :except => [:index, :changes, :gantt, :calendar, :preview, :update_form, :context_menu, :edit_gantt]
25 25
  before_filter :find_optional_project, :only => [:index, :changes, :gantt, :calendar]
26 26
  accept_key_auth :index, :show, :changes
27 27

  
......
170 170
  # Attributes that can be updated on workflow transition (without :edit permission)
171 171
  # TODO: make it configurable (at least per role)
172 172
  UPDATABLE_ATTRS_ON_TRANSITION = %w(status_id assigned_to_id fixed_version_id done_ratio) unless const_defined?(:UPDATABLE_ATTRS_ON_TRANSITION)
173

  
174
  def edit_gantt
175
    if !@issue.start_date || !@issue.due_before
176
      render :text=>l(:notice_locking_conflict), :status=>400
177
      return
178
    end
179
    @issue.init_journal(User.current)
180
    date_from = Date.parse(params[:date_from])
181
    old_start_date = @issue.start_date
182
    o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom])
183
    text_for_revert = "#{@issue.id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}"
184
    
185
    if params[:day]
186
      #bar moved
187
      day = params[:day].to_i
188
      duration = @issue.due_before - @issue.start_date
189
      @issue.start_date = date_from + day
190
      @issue.due_date = @issue.start_date + duration.to_i if @issue.due_date
191
    elsif params[:start_date]
192
      #start date changed
193
      start_date = Date.parse(params[:start_date])
194
      if @issue.start_date == start_date
195
        render :text=>""
196
        return #nothing has changed
197
      end
198
      @issue.start_date = start_date
199
      @issue.due_date = start_date if @issue.due_date && start_date > @issue.due_date
200
    elsif params[:due_date]
201
      #due date changed
202
      due_date = Date.parse(params[:due_date])
203
      if @issue.due_date == due_date
204
        render :text=>""
205
        return #nothing has changed
206
      end
207
      @issue.due_date = due_date
208
      @issue.start_date = due_date if due_date < @issue.start_date
209
    end
210
    fv = @issue.fixed_version
211
    if fv && fv.effective_date && !@issue.due_date && fv.effective_date < @issue.start_date
212
      @issue.start_date = old_start_date
213
    end  
214
    
215
    begin
216
      @issue.save!
217
      o = get_position(@issue, date_from, Date.parse(params[:date_to]), params[:zoom])
218
      text = "#{@issue.id}=#{format_date(@issue.start_date)},#{@issue.start_date},#{format_date(@issue.due_before)},#{@issue.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}"
219

  
220
      #check dependencies
221
      issues = @issue.all_precedes_issues
222
      issues.each do |i|
223
        o = get_position(i, date_from, Date.parse(params[:date_to]), params[:zoom])
224
        text += "|#{i.id}=#{format_date(i.start_date)},#{i.start_date},#{format_date(i.due_before)},#{i.due_before},#{o[0]},#{o[1]},#{o[2]},#{o[3]}"
225
      end
226
      render :text=>text
227
    rescue 
228
      render :text=>@issue.errors.full_messages.join("\n") + "|" + text_for_revert  , :status=>400
229
    end
230
  end
173 231
  
174 232
  def edit
175 233
    @allowed_statuses = @issue.new_statuses_allowed_to(User.current)
app/helpers/issues_helper.rb (working copy)
27 27
    @cached_label_priority ||= l(:field_priority)
28 28
    
29 29
    link_to_issue(issue) + ": #{h(issue.subject)}<br /><br />" +
30
      "<strong>#{@cached_label_start_date}</strong>: #{format_date(issue.start_date)}<br />" +
31
      "<strong>#{@cached_label_due_date}</strong>: #{format_date(issue.due_date)}<br />" +
30
      "<strong>#{@cached_label_start_date}</strong>: <span id='tooltip_start_date_#{issue.id}'>#{format_date(issue.start_date)}</span><br />" +
31
      "<strong>#{@cached_label_due_date}</strong>: <span id='tooltip_due_date_#{issue.id}'>#{format_date(issue.due_date)}</span><br />" +
32 32
      "<strong>#{@cached_label_assigned_to}</strong>: #{issue.assigned_to}<br />" +
33 33
      "<strong>#{@cached_label_priority}</strong>: #{issue.priority.name}"
34 34
  end
......
200 200
    export.rewind
201 201
    export
202 202
  end
203

  
204
  def get_position(event, date_from, date_to, zoom_str)
205
    zoom = zoom_str.to_i
206
    i_start_date = (event.start_date >= date_from ? event.start_date : date_from )
207
    i_end_date = (event.due_before <= date_to ? event.due_before : date_to )
208

  
209
    i_done_date = event.start_date + ((event.due_before - event.start_date+1)*event.done_ratio/100).floor
210
    i_done_date = (i_done_date <= date_from ? date_from : i_done_date )
211
    i_done_date = (i_done_date >= date_to ? date_to : i_done_date )
212

  
213
    i_late_date = [i_end_date, Date.today].min if i_start_date <= Date.today
214

  
215
    i_left = ((i_start_date - date_from)*zoom).floor 	
216
    i_left = 0 if i_left < 0
217
    i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2                  # total width of the issue (- 2 for left and right borders)
218
    i_width = 0 if i_width < 0
219
    d_width = ((i_done_date - i_start_date)*zoom).floor - 2                     # done width
220
    d_width = 0 if d_width < 0
221
    l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
222
    l_width = 0 if l_width < 0
223
    return i_left, i_width, l_width, d_width
224
  end
203 225
end
app/models/issue.rb (working copy)
246 246
    end
247 247
    dependencies
248 248
  end
249

  
250
  def all_precedes_issues
251
    dependencies = []
252
    relations_from.each do |relation|
253
      next unless relation.relation_type == IssueRelation::TYPE_PRECEDES
254
      dependencies << relation.issue_to
255
      dependencies += relation.issue_to.all_dependent_issues
256
    end
257
    dependencies
258
  end
249 259
  
250 260
  # Returns an array of issues that duplicate this one
251 261
  def duplicates
app/views/issues/gantt.rhtml (working copy)
46 46
<% zoom = 1
47 47
@gantt.zoom.times { zoom = zoom * 2 }
48 48

  
49
subject_width = 330
49
subject_width = 280
50 50
header_heigth = 18
51 51

  
52 52
headers_height = header_heigth
......
66 66
g_height = [(20 * @gantt.events.length + 6)+150, 206].max
67 67
t_height = g_height + headers_height
68 68
%>
69
<input type="hidden" name="_date_from" id="i_date_from" value="<%=h(@gantt.date_from)%>" />
70
<input type="hidden" name="_date_to" id="i_date_to" value="<%=h(@gantt.date_to)%>" />
71
<input type="hidden" name="zm" id="i_zm" value="<%=h(zoom)%>" />
72
<script type='text/javascript'>
73
  function issue_moved(elem) {
74
    var id_str = elem.id.substring(3, elem.id.length);
75
    var v_date_from = document.getElementById('i_date_from').getAttribute("value");
76
    var v_date_to = document.getElementById('i_date_to').getAttribute("value");
77
    var v_zm = document.getElementById('i_zm').getAttribute("value");
78
    var url_str = '/issues/edit_gantt/' + id_str;
79
    var day = parseInt(elem.style.left)/parseInt(v_zm);
80
    new Ajax.Request(url_str, {asynchronous:true, evalScripts:true,
81
      parameters: 'day='+day+'&date_from='+v_date_from+'&date_to='+v_date_to+'&zoom='+v_zm,
82
      onSuccess:function(obj) {
83
        change_dates(obj.responseText);
84
      },
85
      onFailure:function(obj) {
86
        handle_failure(obj.responseText);
87
      }
88
    });
69 89

  
90
  }
91

  
92
  function handle_failure(res_text) {
93
    var text = res_text.split('|');
94
    alert(text[0]);
95
    if (text.length == 1) {
96
      return;
97
    }
98
    change_dates(text[1]);//revert
99
  }
100

  
101
  function change_dates(issue_infos) {
102
    if (!issue_infos) {
103
      return;
104
    }
105
    var issue_list = issue_infos.split("|");
106
    for (i = 0; i < issue_list.length; i++) {
107
      change_date(issue_list[i]);
108
    }
109
  }
110

  
111
  function change_date(text) {
112
    if (!text) {
113
      return;
114
    }
115
    var issue_info = text.split("=");
116
    var elem_id = issue_info[0];
117
    var vals = issue_info[1].split(',');
118
    var start_date_elem = document.getElementById(elem_id + '_start_date_str');
119
    if (!start_date_elem) {
120
      //target not exists
121
      return;
122
    }
123
    start_date_elem.innerHTML = vals[0];
124
    var tooltip_start_date_elem = document.getElementById('tooltip_start_date_' + elem_id);
125
    tooltip_start_date_elem.innerHTML = vals[0];
126
    var due_date_elem = document.getElementById(elem_id + '_due_date_str');
127
    due_date_elem.innerHTML = vals[2];
128
    var tooltip_due_date_elem = document.getElementById('tooltip_due_date_' + elem_id);
129
    tooltip_due_date_elem.innerHTML = vals[2];
130

  
131
    var ev_elem = document.getElementById('ev_' + elem_id);
132
    ev_elem.style.left = vals[4] + 'px';
133
    ev_elem.style.width = (parseInt(vals[5])+100)+'px';
134
    var todo_elem = document.getElementById('task_todo_' + elem_id);
135
    todo_elem.style.width = vals[5] + 'px';
136
    var late_elem = document.getElementById('task_late_' + elem_id);
137
    if (late_elem) {
138
      late_elem.style.width = vals[6] + 'px';
139
      if (vals[6] == '0') {
140
        late_elem.className = 'task task_none';
141
      } else {
142
        late_elem.className = 'task task_late';
143
      }
144
    }
145
    var done_elem = document.getElementById('task_done_' + elem_id);
146
    if (done_elem) {
147
      done_elem.style.width = vals[7] + 'px';
148
      if (vals[7] == '0') {
149
        done_elem.className = 'task task_none';
150
      } else {
151
        done_elem.className = 'task task_done';
152
      }
153
    }
154
    var task_elem = document.getElementById('task_task_' + elem_id);
155
    if (task_elem) {
156
      task_elem.style.left = (parseInt(vals[5])+5) + 'px';
157
    }
158
    var tooltip = document.getElementById("tt_" + elem_id);
159
    tooltip.style.left = ev_elem.style.left;
160

  
161
    //change calendar date
162
    document.getElementById(elem_id+"_hidden_start_date").value = vals[1];
163
    document.getElementById(elem_id+"_start_date").value = vals[1];
164
    document.getElementById(elem_id+"_hidden_due_date").value = vals[3];
165
    document.getElementById(elem_id+"_due_date").value = vals[3];
166
  }
167
</script>
168

  
70 169
<table width="100%" style="border:0; border-collapse: collapse;">
71 170
<tr>
72 171
<td style="width:<%= subject_width %>px; padding:0px;">
......
80 179
#
81 180
top = headers_height + 8
82 181
@gantt.events.each do |i| %>
83
    <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>    
182
    <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;left:4px;overflow:hidden;"><small>
84 183
    <% if i.is_a? Issue %>
85 184
      	<%= h("#{i.project} -") unless @project && @project == i.project %>
86 185
      	<%= link_to_issue i %>:	<%=h i.subject %>
......
89 188
	      	<%= h("#{i.project} -") unless @project && @project == i.project %>
90 189
	      	<%= link_to_version i %>
91 190
		</span>
92
  	<% end %>  	
191
  	<% end %>
93 192
  	</small></div>
94 193
    <% top = top + 20
95 194
end %>
96 195
</div>
97 196
</td>
197
<td style="width:225px; padding:0px;">
198
<div style="position:relative;height:<%= t_height + 24 %>px;width:225px;">
199
<div style="width:225px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr"></div>
200
<div style="width:225px;height:<%= t_height %>px;border-left: 1px solid #c0c0c0;overflow:hidden;" class="gantt_hdr"></div>
201
<%
202
top = headers_height + 8
203
@gantt.events.each do |i| %>
204
  <div style="position: absolute;line-height:1.2em;height:16px;top:<%= top %>px;"><small>
205
  <% if i.is_a? Issue %>
206
    <span id="<%=i.id%>_start_date_str" style="font-size: 7pt;"><%= format_date(i.start_date) %></span>
207
    <input type="hidden" size="12" id="<%=i.id%>_hidden_start_date" value="<%= i.start_date %>">
208
    <input type="hidden" size="12" id="<%=i.id%>_start_date" value="<%= i.start_date %>"><%= calendar_for("#{i.id}_start_date") if i.is_a? Issue %>
209
<script type="text/javascript">
210
//<![CDATA[
211
new Form.Element.Observer('<%=i.id%>_start_date', 0.25,
212
  function(element, value) {
213
    if (value == document.getElementById('<%=i.id%>_hidden_start_date').value) {
214
      return ;
215
    }
216
    new Ajax.Request('<%=url_for(:controller=>:issues, :action => :edit_gantt, :id=>i.id, :date_from=>@gantt.date_from.strftime("%Y-%m-%d"), :date_to=>@gantt.date_to.strftime("%Y-%m-%d"), :zoom=>zoom, :escape => false) %>', {asynchronous:true, evalScripts:true, onFailure:function(request){handle_failure(request.responseText)}, onSuccess:function(request){change_dates(request.responseText)}, parameters:'start_date=' + encodeURIComponent(value)});
217
  })
218
//]]>
219
</script>
220
    <span id="<%=i.id%>_due_date_str" style="font-size: 7pt;<%= "color: blue;" unless i.due_date%>">
221
      <%= format_date(i.due_before) %>
222
    </span>
223
    <input type="hidden" size="12" id="<%=i.id%>_hidden_due_date" value="<%= i.due_date %>">
224
    <input type="hidden" size="12" id="<%=i.id%>_due_date" value="<%= i.due_date %>"><%= calendar_for("#{i.id}_due_date") if i.due_date%>
225
<script type="text/javascript">
226
//<![CDATA[
227
new Form.Element.Observer('<%=i.id%>_due_date', 0.25,
228
  function(element, value) {
229
    if (value == document.getElementById('<%=i.id%>_hidden_due_date').value) {
230
      return ;
231
    }
232
    new Ajax.Request('<%=url_for(:controller=>:issues, :action => :edit_gantt, :id=>i.id, :date_from=>@gantt.date_from.strftime("%Y-%m-%d"), :date_to=>@gantt.date_to.strftime("%Y-%m-%d"), :zoom=>zoom, :escape => false) %>', {asynchronous:true, evalScripts:true, onFailure:function(request){handle_failure(request.responseText)}, onSuccess:function(request){change_dates(request.responseText)}, parameters:'due_date=' + encodeURIComponent(value)});
233
  })
234
//]]>
235
</script>
236
  <% else %>
237
    <span style="font-size: 7pt;"><%= format_date(i.start_date) %></span>
238
  <% end %>
239
  </small></div>
240
<% top = top + 20
241
end %>
242
</div>
243
</td>
98 244
<td>
99 245

  
100
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;">
246
<div style="position:relative;height:<%= t_height + 24 %>px;overflow:auto;" id="gantt-container">
101 247
<div style="width:<%= g_width-1 %>px;height:<%= headers_height %>px;background: #eee;" class="gantt_hdr">&nbsp;</div>
102
<% 
248
<%
103 249
#
104 250
# Months headers
105 251
#
106 252
month_f = @gantt.date_from
107 253
left = 0
108 254
height = (show_weeks ? header_heigth : header_heigth + g_height)
109
@gantt.months.times do 
255
@gantt.months.times do
110 256
	width = ((month_f >> 1) - month_f) * zoom - 1
111 257
	%>
112 258
	<div style="left:<%= left %>px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
113 259
	<%= link_to "#{month_f.year}-#{month_f.month}", @gantt.params.merge(:year => month_f.year, :month => month_f.month), :title => "#{month_name(month_f.month)} #{month_f.year}"%>
114 260
	</div>
115
	<% 
261
	<%
116 262
	left = left + width + 1
117 263
	month_f = month_f >> 1
118 264
end %>
119 265

  
120
<% 
266
<%
121 267
#
122 268
# Weeks headers
123 269
#
......
133 279
		width = (7 - @gantt.date_from.cwday + 1) * zoom-1
134 280
		%>
135 281
		<div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">&nbsp;</div>
136
		<% 
282
		<%
137 283
		left = left + width+1
138 284
	end %>
139 285
	<%
......
143 289
		<div style="left:<%= left %>px;top:19px;width:<%= width %>px;height:<%= height %>px;" class="gantt_hdr">
144 290
		<small><%= week_f.cweek if width >= 16 %></small>
145 291
		</div>
146
		<% 
292
		<%
147 293
		left = left + width+1
148 294
		week_f = week_f+7
149 295
	end
150 296
end %>
151 297

  
152
<% 
298
<%
153 299
#
154 300
# Days headers
155 301
#
......
157 303
	left = 0
158 304
	height = g_height + header_heigth - 1
159 305
	wday = @gantt.date_from.cwday
160
	(@gantt.date_to - @gantt.date_from + 1).to_i.times do 
306
  dt = @gantt.date_from
307
	(@gantt.date_to - @gantt.date_from + 1).to_i.times do
161 308
	width =  zoom - 1
162 309
	%>
163 310
	<div style="left:<%= left %>px;top:37px;width:<%= width %>px;height:<%= height %>px;font-size:0.7em;<%= "background:#f1f1f1;" if wday > 5 %>" class="gantt_hdr">
164
	<%= day_name(wday).first %>
311
	<%=  "#{dt.day}<br>"if @gantt.zoom == 4 %><%= day_name(wday).first %>
165 312
	</div>
166
	<% 
313
	<%
167 314
	left = left + width+1
168 315
	wday = wday + 1
316
  dt = dt + 1
169 317
	wday = 1 if wday > 7
170 318
	end
171 319
end %>
......
175 323
# Tasks
176 324
#
177 325
top = headers_height + 10
178
@gantt.events.each do |i| 
179
  if i.is_a? Issue 
180
	i_start_date = (i.start_date >= @gantt.date_from ? i.start_date : @gantt.date_from )
181
	i_end_date = (i.due_before <= @gantt.date_to ? i.due_before : @gantt.date_to )
182
	
183
	i_done_date = i.start_date + ((i.due_before - i.start_date+1)*i.done_ratio/100).floor
184
	i_done_date = (i_done_date <= @gantt.date_from ? @gantt.date_from : i_done_date )
185
	i_done_date = (i_done_date >= @gantt.date_to ? @gantt.date_to : i_done_date )
186
	
187
	i_late_date = [i_end_date, Date.today].min if i_start_date < Date.today
188
	
189
	i_left = ((i_start_date - @gantt.date_from)*zoom).floor 	
190
	i_width = ((i_end_date - i_start_date + 1)*zoom).floor - 2                  # total width of the issue (- 2 for left and right borders)
191
	d_width = ((i_done_date - i_start_date)*zoom).floor - 2                     # done width
192
	l_width = i_late_date ? ((i_late_date - i_start_date+1)*zoom).floor - 2 : 0 # delay width
326
@gantt.events.each do |i|
327
  if i.is_a? Issue
328
    i_left, i_width, l_width, d_width = get_position(i, @gantt.date_from, @gantt.date_to, zoom)
193 329
	%>
194
	<div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
195
	<% if l_width > 0 %>
196
	    <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= l_width %>px;" class="task task_late">&nbsp;</div>
197
	<% end %>
198
	<% if d_width > 0 %>
199
	    <div style="top:<%= top %>px;left:<%= i_left %>px;width:<%= d_width %>px;" class="task task_done">&nbsp;</div>
200
	<% end %>
201
	<div style="top:<%= top %>px;left:<%= i_left + i_width + 5 %>px;background:#fff;" class="task">
202
	<%= i.status.name %>
203
	<%= (i.done_ratio).to_i %>%
204
	</div>
205
	<div class="tooltip" style="position: absolute;top:<%= top %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:12px;">
206
	<span class="tip">
330
  <div id="ev_<%=i.id%>" style="position:absolute;left:<%= i_left %>px;top:<%= top %>px;padding-top:3px;height:18px;width:<%= i_width+100 %>px;" class="handle">
331
    <div id="task_todo_<%=i.id%>" style="float:left:0px; width:<%= i_width %>px;" class="task task_todo">&nbsp;</div>
332
    <div id="task_late_<%=i.id%>" style="float:left:0px; width:<%= l_width %>px;" class="<%= l_width == 0 ? 'task task_none' : 'task task_late' %>">&nbsp;</div>
333
    <div id="task_done_<%=i.id%>" style="float:left:0px; width:<%= d_width %>px;" class="<%= d_width == 0 ? 'task task_none' : 'task task_done' %>">&nbsp;</div>
334
    <div id="task_task_<%=i.id%>" style="position:absolute;left:<%= (i_width + 5) %>px;background:#fff;" class="task">
335
    <%= h(i.status.name) %>
336
    <%= (i.done_ratio).to_i %>%
337
    <% if !i.due_date && i.fixed_version %>
338
      -&nbsp;<strong><%= h(i.fixed_version.name)  %></strong>
339
    <% end %>
340
    </div>
341
  </div>
342
	<%# === tooltip === %>
343
	<div id="tt_<%=i.id%>" class="tooltip" style="position: absolute;top:<%= top+14 %>px;left:<%= i_left %>px;width:<%= i_width %>px;height:8px;">
344
	<span class="tip" style="position: relative;top:-2px;">
207 345
    <%= render_issue_tooltip i %>
208 346
	</span></div>
209
<% else 
347
  <%= draggable_element("ev_#{i.id}",
348
    :revert =>false, :scroll=>"'gantt-container'", :constraint => "'horizontal'", :snap=>zoom,
349
       :onEnd=>"function( draggable, event )  {issue_moved(draggable.element);}"
350
    ) %>
351

  
352
<% else
210 353
    i_left = ((i.start_date - @gantt.date_from)*zoom).floor
211 354
    %>
212
    <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
355
  <div style="top:<%= top %>px;left:<%= i_left %>px;width:15px;" class="task milestone">&nbsp;</div>
213 356
	<div style="top:<%= top %>px;left:<%= i_left + 12 %>px;background:#fff;" class="task">
214 357
		<%= h("#{i.project} -") unless @project && @project == i.project %>
215 358
		<strong><%=h i %></strong>
public/stylesheets/application.css (working copy)
658 658
.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; }
659 659
.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; }  
660 660
.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; }
661
.task_none { background:transparent; border-style: none; }
662
.task_none { background:transparent; border-style: none; }
661 663
.milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; }
662 664

  
663 665
/***** Icons *****/
(9-9/35)