OXYGENHUB
# 64e5964e9f13 oxygenhub / templates / project.html
15.0 KiB · HTML · last changed Iterations on project boards: a named span of days with a burn-up of cards done; closing one archives its Done column's cards under it, its page lists them (closes #343) 9 days ago source blame history raw
1
{% extends "base.html" %}
2
{% block title %}{{ project.title }} · Projects · {{ project.owner }}{% endblock %}
3
{% block mainclass %} class="wide"{% endblock %}
4
{% macro burnup(v) %}<svg class="burnup" viewBox="0 0 {{ crate::web::projects::BURN_W }} {{ crate::web::projects::BURN_H }}" preserveAspectRatio="none" role="img" aria-label="Burn-up: {{ v.done }} card{% if v.done != 1 %}s{% endif %} done over {{ v.so_far }} of {{ v.days }} days"><line x1="0" y1="{{ crate::web::projects::BURN_H }}" x2="{{ crate::web::projects::BURN_W }}" y2="{{ crate::web::projects::BURN_H }}"></line><polyline points="{{ v.points }}"></polyline></svg>{% endmacro %}
5
{% block content %}
6
<div class="head"><h1>{{ project.title }} <small><a href="/projects/{{ project.owner }}">{{ project.owner }}'s projects</a> · #{{ project.number }}{% if !project.public %} · private{% endif %}{% if project.closed %} · closed{% endif %}</small></h1>
7
{% if !project.description.is_empty() %}<p class="mute">{{ project.description }}</p>{% endif %}</div>
8
{% if let Some(n) = notice %}<p class="note">{{ n }}</p>{% endif %}
9
{% if let Some(e) = error %}<p class="error">{{ e }}</p>{% endif %}
10
{% if let Some(v) = shown_iteration %}
11
<section class="iteration past" aria-label="Iteration {{ v.it.name }}">
12
  <div><h2>{{ v.it.name }}</h2><p class="small mute">{{ v.first }} to {{ v.last }} · {{ v.done }} card{% if v.done != 1 %}s{% endif %} done, archived when it closed{% if can_edit %}; restore one to put it back on the board{% endif %} · <a href="/projects/{{ project.owner }}/{{ project.number }}">back to the board</a></p></div>
13
  {% call burnup(v) %}
14
</section>
15
{% else if archived %}<p class="note board-archive-note">Archived cards: off the board, kept and searchable{% if can_edit %}; restore one to put it back at the end of its column{% endif %}. <a href="/projects/{{ project.owner }}/{{ project.number }}">Back to the board</a></p>
16
{% else %}
17
<section class="iteration" aria-label="Iteration">
18
  {% if let Some(v) = current %}
19
  <div><h2>{{ v.it.name }}</h2><p class="small mute">{{ v.first }} to {{ v.last }} · day {{ v.so_far }} of {{ v.days }} · {{ v.done }} card{% if v.done != 1 %}s{% endif %} done</p>
20
  {% if can_edit %}<form class="tiny" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="iteration" value="{{ v.it.id }}"><button name="action" value="close_iteration" class="small" data-confirm="Close {{ v.it.name }}? The cards in the Done column are archived under it.">CLOSE ITERATION</button></form>{% endif %}</div>
21
  {% call burnup(v) %}
22
  {% else if can_edit %}
23
  <details class="iteration-new"><summary class="small">start an iteration</summary>
24
    <form method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="action" value="start_iteration">
25
      <label for="it-name">Name</label><input type="text" id="it-name" name="name" maxlength="60" placeholder="Sprint 1" required>
26
      <div class="inline-fields"><label>First day <input type="date" name="start" value="{{ next_first }}" required></label><label>Last day <input type="date" name="end" value="{{ next_last }}" required></label></div>
27
      <p class="small mute">Closing it archives the cards in the Done column under it.</p>
28
      <button type="submit" class="small">START</button>
29
    </form>
30
  </details>
31
  {% endif %}
32
  {% if !past.is_empty() %}<details class="iteration-past"><summary class="small">past iterations ({{ past.len() }})</summary><ul>{% for v in past %}<li><a href="/projects/{{ project.owner }}/{{ project.number }}?iteration={{ v.it.id }}">{{ v.it.name }}</a> <span class="small mute">{{ v.first }} to {{ v.last }} · {{ v.done }} done</span></li>{% endfor %}</ul></details>{% endif %}
33
</section>
34
{% endif %}
35
<form class="board-filter" method="get" action="/projects/{{ project.owner }}/{{ project.number }}" role="search">{% if let Some(v) = shown_iteration %}<input type="hidden" name="iteration" value="{{ v.it.id }}">{% else if archived %}<input type="hidden" name="archived" value="1">{% endif %}
36
  <label class="visually-hidden" for="board-q">Filter cards</label><input type="search" id="board-q" name="q" value="{{ q }}" placeholder="filter: words, label:bug, assignee:andy" title="Words in the title or reference; label:NAME, assignee:NAME and state:STATE as on the issue list">
37
  <label class="visually-hidden" for="board-state">State</label><select id="board-state" name="state"><option value="">any state</option>{% for s in ["open", "closed", "landed", "draft"] %}<option{% if state == *s %} selected{% endif %}>{{ s }}</option>{% endfor %}</select>
38
  <button type="submit" class="small">FILTER</button>{% if filtered %} <a class="small" href="/projects/{{ project.owner }}/{{ project.number }}{% if archived %}?archived=1{% endif %}">clear</a>{% endif %}
39
  {% if !archived && archived_total > 0 %}<a class="small board-archived-link" href="/projects/{{ project.owner }}/{{ project.number }}?archived=1">{{ archived_total }} archived card{% if archived_total != 1 %}s{% endif %}</a>{% endif %}
40
</form>
41
<div class="board" role="list" aria-label="Columns">
42
{% for col in columns %}
43
<section class="board-col" role="listitem" aria-labelledby="col-{{ col.column.id }}">
44
  <header><h2 id="col-{{ col.column.id }}">{{ col.column.name }} <span class="count-chip">{% if filtered %}{{ col.cards.len() }} of {{ col.total }}{% else %}{{ col.total }}{% endif %}</span></h2>
45
  {% if can_edit && !archived %}<form class="tiny" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}"><button name="action" value="left" title="Move the column left" aria-label="Move {{ col.column.name }} left">←</button><button name="action" value="right" title="Move the column right" aria-label="Move {{ col.column.name }} right">→</button></form>{% endif %}</header>
46
  {% if !archived %}{% match col.column.archive_after_days %}{% when Some(-1) %}{% when Some(d) %}<p class="small mute col-archives">archives cards {{ d }} day{% if *d != 1 %}s{% endif %} after they reach it</p>{% when None %}{% if col.finished %}<p class="small mute col-archives">finished cards are archived after {{ crate::db::projects::FINISHED_KEPT_DAYS }} days</p>{% endif %}{% endmatch %}{% endif %}
47
  {% if col.hidden > 0 && !filtered && !archived %}<p class="small mute col-hidden">{{ col.hidden }} older card{% if col.hidden != 1 %}s{% endif %} not shown · <a href="/projects/{{ project.owner }}/{{ project.number }}?all={{ col.column.id }}#col-{{ col.column.id }}">show all {{ col.total }}</a></p>{% endif %}
48
  <ul class="cards">
49
  {% for c in col.cards %}
50
    <li class="card-item{% if c.card.kind == "note" %} note{% endif %}">
51
      {% if c.card.kind == "note" %}<p>{{ c.title }}</p>
52
      {% else %}<a href="{{ c.url }}">{{ c.title }}</a>
53
      <p class="small mute"><span class="glyph" aria-hidden="true">{% if c.card.kind == "issue" %}◇{% else %}⇄{% endif %}</span> {{ c.reference }} · <span class="state-{{ c.state }}">{{ c.state }}</span>{% for a in c.assignees %} · <a class="card-who" href="/projects/{{ project.owner }}/{{ project.number }}?q=assignee:{{ a|urlencode }}" title="Cards assigned to {{ a }}">@{{ a }}</a>{% endfor %}</p>
54
      {% if !c.labels.is_empty() %}<p class="card-labels">{% for l in c.labels %}<a class="label" style="--c: {{ l.colour }}" href="/projects/{{ project.owner }}/{{ project.number }}?q=label:{{ l.name|urlencode }}" title="{% if l.description.is_empty() %}Cards labelled {{ l.name }}{% else %}{{ l.description }}{% endif %}">{{ l.name }}</a>{% endfor %}</p>{% endif %}{% endif %}
55
      {% if let Some(t) = c.card.archived_ms %}<p class="small mute">archived <time title="{{ t|when_i }}">{{ t|ago_i }}</time></p>{% endif %}
56
      {% if can_edit && archived %}
57
      <form class="tiny card-actions" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="card" value="{{ c.card.id }}"><button name="action" value="restore">restore</button></form>
58
      {% else if can_edit %}
59
      <form class="tiny card-actions" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="card" value="{{ c.card.id }}">
60
        <label class="visually-hidden" for="mv-{{ c.card.id }}">Column</label>
61
        <select id="mv-{{ c.card.id }}" name="column">{% for o in columns %}<option value="{{ o.column.id }}"{% if o.column.id == col.column.id %} selected{% endif %}>{{ o.column.name }}</option>{% endfor %}</select>
62
        <button name="action" value="move">move</button><button name="action" value="up" aria-label="Up">↑</button><button name="action" value="down" aria-label="Down">↓</button><button name="action" value="archive" title="Archive: off the board, kept and restorable">archive</button><button name="action" value="remove" aria-label="Remove from the project" title="Remove from the project">✕</button>
63
      </form>{% endif %}
64
    </li>
65
  {% endfor %}
66
  </ul>
67
  {% if can_edit && !archived %}
68
  <details class="add-card"><summary class="small">add a card</summary>
69
    <form method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}"><input type="hidden" name="action" value="add">
70
      <label for="ref-{{ col.column.id }}">Issue or proposal</label>
71
      <div class="inline-fields"><select name="repo" aria-label="Repository">{% for r in repos %}<option>{{ r }}</option>{% endfor %}</select><input type="text" id="ref-{{ col.column.id }}" name="reference" placeholder="#12 or repo#12"></div>
72
      <label for="note-{{ col.column.id }}">or a note</label><textarea id="note-{{ col.column.id }}" name="note" rows="2" maxlength="500"></textarea>
73
      <button type="submit" class="small">ADD</button>
74
    </form>
75
  </details>
76
  <details class="add-card"><summary class="small">column</summary>
77
    <form method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}">
78
      <input type="text" name="name" value="{{ col.column.name }}" aria-label="Column name" maxlength="40"><button name="action" value="rename_column" class="small">RENAME</button> <button name="action" value="remove_column" class="small danger-btn" data-confirm="Remove this column? Its cards move to the first column.">REMOVE</button>
79
    </form>
80
    <form class="col-archive" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}">
81
      <label for="older-{{ col.column.id }}">Archive cards here more than</label>
82
      <div class="inline-fields"><input type="number" id="older-{{ col.column.id }}" name="days" min="0" max="3650" value="14" required><span class="small mute">days</span><button name="action" value="archive_older" class="small">ARCHIVE</button></div>
83
    </form>
84
    <form class="col-archive" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}"><input type="hidden" name="action" value="takes_finished">
85
      {% if col.column.takes_finished %}<p class="small">Finished work comes here: a card moves here when its issue closes or its proposal lands or closes.</p><button name="mode" value="off" class="small">STOP</button>{% else %}<label>Finished work</label><button name="mode" value="on" class="small" title="A card moves here when its issue closes or its proposal lands or closes (one column of the board)">MOVE IT HERE</button>{% endif %}
86
    </form>
87
    <form class="col-archive" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><input type="hidden" name="column" value="{{ col.column.id }}">
88
      <label for="mode-{{ col.column.id }}">Archive automatically</label>
89
      <div class="inline-fields"><select id="mode-{{ col.column.id }}" name="mode" aria-label="Archive automatically"><option value="finished"{% if col.column.archive_after_days.is_none() %} selected{% endif %}>finished cards after {{ crate::db::projects::FINISHED_KEPT_DAYS }} days</option><option value="days"{% if col.column.archive_after_days.unwrap_or(-1) >= 0 %} selected{% endif %}>every card after</option><option value="never"{% if col.column.archive_after_days == Some(-1) %} selected{% endif %}>never</option></select><input type="number" id="after-{{ col.column.id }}" name="days" min="0" max="3650" placeholder="days" aria-label="Days"{% if let Some(d) = col.column.archive_after_days %}{% if *d >= 0 %} value="{{ d }}"{% endif %}{% endif %}><button name="action" value="archive_after" class="small">SAVE</button></div>
90
    </form>
91
  </details>
92
  {% endif %}
93
</section>
94
{% endfor %}
95
{% if can_edit && !archived %}
96
<section class="board-col add-col"><form method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}"><label for="newcol">New column</label><input type="text" id="newcol" name="name" maxlength="40" required><button name="action" value="add_column" class="small">ADD COLUMN</button></form></section>
97
{% endif %}
98
</div>
99
{% if can_manage %}
100
<details class="panel"><summary>Settings</summary>
101
<form class="card" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
102
  <label for="ptitle">Title</label><input type="text" id="ptitle" name="title" value="{{ project.title }}" maxlength="100">
103
  <label for="pdesc">Description</label><input type="text" id="pdesc" name="description" value="{{ project.description }}">
104
  <div class="choice"><label><input type="checkbox" name="private" value="1"{% if !project.public %} checked{% endif %}> private</label> <label><input type="checkbox" name="closed" value="1"{% if project.closed %} checked{% endif %}> closed (read-only)</label></div>
105
  <button name="action" value="settings">SAVE</button> <button name="action" value="delete" class="danger-btn" data-confirm="Delete this project? Issues and proposals are not touched.">DELETE PROJECT</button>
106
</form>
107
{% if !transfer_to.is_empty() %}
108
<form class="card" method="post" action="/projects/{{ project.owner }}/{{ project.number }}"><input type="hidden" name="_csrf" value="{{ shell.csrf }}">
109
  <h3>Transfer</h3>
110
  <p class="small mute">Move this project to another owner. It takes the next number there, with its columns and cards; this address goes on redirecting to it.</p>
111
  <label for="pto">New owner</label>
112
  <select id="pto" name="to">{% for o in transfer_to %}<option value="{{ o }}">{{ o }}</option>{% endfor %}</select>
113
  <button name="action" value="transfer" data-confirm="Transfer this project?">TRANSFER</button>
114
</form>
115
{% endif %}
116
</details>
117
{% endif %}
118
{% endblock %}