-
-
Save romain-huyvaert/a3ff6eaa8e3ec4728cdd810887d75424 to your computer and use it in GitHub Desktop.
Génération de tickets pour les scénarii Scrum
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="fr"> | |
<head> | |
<meta charset="utf-8"/> | |
<title>Génération de tickets pour les scénarii Scrum</title> | |
<!-- | |
Copyright © 2016 Simon Leblanc <contact@leblanc-simon.eu> | |
This work is free. You can redistribute it and/or modify it under the | |
terms of the Do What The Fuck You Want To Public License, Version 2, | |
as published by Sam Hocevar. See http://www.wtfpl.net/ for more details. | |
Contributor : Romain Huyvaert <romain.huyvaert@gmail.com> | |
--> | |
<link rel="icon" href="favicon.ico" /> | |
<link rel="icon" type="image/png" href="favicon.png" /> | |
<link href="https://fonts.googleapis.com/css?family=Ubuntu" rel="stylesheet"> | |
<style type="text/css"> | |
html, body { | |
margin: 0; | |
padding: 0; | |
font-family: 'Ubuntu', sans-serif; | |
} | |
header, #container { | |
width: 98%; | |
margin: 1em auto; | |
} | |
#colorpicker { | |
position: absolute; | |
left: -10000px; | |
top: auto; | |
width: 1px; | |
height: 1px; | |
overflow: hidden; | |
} | |
/** | |
* Header | |
*/ | |
h1 { | |
text-align: center; | |
} | |
header { | |
display: flex; | |
justify-content: space-between; | |
} | |
.build { | |
width: 45%; | |
border-radius: 1rem; | |
background-color: #eee; | |
padding: 1rem; | |
margin: 0 calc(2.5% - 1em); | |
display: flex; | |
flex-direction: column; | |
} | |
header .title { | |
text-align: center; | |
margin-bottom: 1rem; | |
font-size: 1.3rem; | |
} | |
input, textarea { | |
display: block; | |
width: 95%; | |
margin: 0.5em auto; | |
padding: 0.2rem; | |
border: 1px solid #ccc; | |
} | |
#empty { | |
text-align: center; | |
} | |
textarea { | |
height: 10rem; | |
} | |
.submit { | |
text-align: center; | |
margin-top: auto; | |
} | |
.submit button { | |
padding-left: 2rem; | |
padding-right: 2rem; | |
font-weight: bold; | |
font-size: 1.2rem; | |
color: #3262F9; | |
border-radius: 2rem; | |
background: #ddd; | |
border: none; | |
cursor: pointer; | |
} | |
.submit button:focus { | |
outline: none; | |
} | |
.submit button::before { | |
/** | |
* https://www.iconfinder.com/icons/1086770/bullets_content_form_guide_list_menu_plan_to_do_icon | |
* Licence : CC BY | |
* Author : Vladlena Shibaeva | |
*/ | |
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAADJElEQVRoQ+2YTWjUQBTH35tsk83WoiIKglhE9KLW7qZ4sQeVCiJeeuhBS2m7qYKlFIsXvciiBylaerAilP2gFDzqQQVB8dSDqMmWUvDiB1VRUMHa1e5ms5mR7FKQYjdJTRMXk8se5u0//9+8N28mg1DjD9a4fwgA/M6g8wwkGFkr09JH4JRx1J3o2waQ4npriRqdBMlOJy9wFmt8AeQ+EYPeUibCr+381xaAaZ4C6yAhY0QZF9/ZEV5tTDRe3A/IBkPUSDzPRN5b6dgCaI4XxxCNa9mUOGcl6MZ49HShDSnsU1PhUSs9WwCxePGxmubbrMTcGm/uKexCDoayqXC/lWYAYDVDqxkPMrB81oI14LCOPC2hWG/+iOlPzYhPHPpcMdwzgAO9hRM6kqNAANCgj9RM+L7pak8/W8flfkSqAc1MNnxeadwjAIZSjzYGvHDuuwCkYUEbzU5U+nZULnYBNY5VA8hmxE6fAQBa5GK0xIwLphGOccNKhlfdKCOPMlCx2tSX32H+ziTFt26YNzU8BXDL9O86AcB/s5G1nmIbF4XiBFCAiM53T93Gb26UlCcl1NHBuDfr9VGk9KZp2gDSn91eNwQJpLG41o6Ah6vBKGl+0Pc2KvVqFymwecYAAXH9dEa4Wu5MXbktIYHfVA1ATQovfQcAYCjJ2nnTiJISRgCQ1UwJuWH0H8jA2mB4soiXrDf1LW6r7MSRD27heAbQImt7Swwum8ZDCJdepIRZNyA8AmDYJOfHjAVxaHM9kHmiX59O8wNLp1FkxvGqXSgtnvR9DVT6PWstn0YBp56lhbuVNsrq60QQq+4D4/jVdwDTQEwulM/9air80I3yCU6jf5rF4FbCYW151IUcunIQHgAsn6yaXgOHEiyUm9OTSobvcVAFfxUalfONhJGzSloo33hUe2xdr0u92hXG4QM1yT+1EnRjXJKL3RSAZlP8pJWePYAzP7eWjNAwAtxDhq5dnyw3R0JMoyU8CAhSLlI38OoGaq4AVHZHtoGg3s6Q7bYSXe04Eqoxg8xmG/k75qepHR1bGbAj5FdMAODXzC+9N8iA3xn4BYtZ20B3G0sLAAAAAElFTkSuQmCC); | |
height: 48px; | |
display: inline-block; | |
vertical-align: middle; | |
} | |
/** | |
* Cards | |
*/ | |
.card { | |
width: calc(50% - 2px); | |
height: 250px; | |
min-height: 250px; | |
max-height: 250px; | |
float: left; | |
border: 1px solid #000; | |
position: relative; | |
border-top-left-radius: 1rem; | |
border-top-right-radius: 1rem; | |
page-break-inside: avoid; | |
} | |
#container .title { | |
position: relative; | |
background-color: #49962C; | |
padding: 0.5rem; | |
border-top-left-radius: 1rem; | |
border-top-right-radius: 1rem; | |
font-weight: bold; | |
} | |
.color, .delete { | |
position: absolute; | |
background: none; | |
border: none; | |
cursor: pointer; | |
} | |
.color { | |
right: 0.5rem; | |
} | |
.delete { | |
right: calc(0.5em + 16px + 0.5em); | |
} | |
.color:focus, .delete:focus { | |
outline: none; | |
} | |
.color::before, .delete::before { | |
width: 16px; | |
height: 16px; | |
} | |
.color::before { | |
/** | |
* https://www.iconfinder.com/icons/1055087/color_wheel_palette_swatch_icon | |
* Licence : GPL | |
* Author : Nick Roach | |
*/ | |
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACoUlEQVQ4T32T3UtUQRjGn/ecs19ubuu6ZOaau4sFXviVEFEiaERGV+FNEHYhZEURQf9Af0AXUVQqREVddOFtUIRWGlFguC4aZtIePxc/cl3X1d31nJmYWV1Wg+Zq3pl5fu/MM+9L2DN0feQY5/wqEVoAqhDbnENXFOpnzOwKBOpD+RLaCebnhwoyGetDgHfshebHnPMeTdu4XV5+clOsS0BWbHkHoFEe5hzpiQi2FpZkaPWVwho8nOMQsQFF2WwVEAnQ9fDTnczJjTS05DpWHjyTIDm8RSi9eRnYiAGuErlERN0VFTXXSLwZwHexOD45j55XfbjefgaOb18BcQNFgbXpBDzaHLToGFhDG5Ti7G04Z/UUiYS6iaiTMYa793oRT6RQW+XDpbZGGIYhMsFut2Fr8DlsqRhM2z6oTVckGMAT0vXQL4Aqf0zMovtlvxTc6jiNoP/QLi/j0Sm4wr3SNLPuAtSSIAD2UzwhA8CyuJzA+w9DcLucOH/2+D8fwTnHn9AnKJkkHJUNcBQfFIBMDrCaBibjBKcGVHm2zduDsaenobA00tYymGpBFhCJjEwQ4ch0gvBGV+QVLx414bbtVitGAp61QRABUbUGmssnbBynqalwl6g8BuDFKEOKWxBwZtAaVHcRClcGYKd1pEwrVt3NUDUVjPFHFIkM1xEpw+L02Gwcn2NFaPauYVZLYjGTlqaeKvKgbG0UXssKplkVnF6/hDOm1MpCyv/KheVV2F1OvF6cw44TblLR5i3GZnwJ+0v8Egrgsd9fe0POZma+OBhzvOVcaRKxYRoYmYsiZhrSkwMWK6p9ZXlPoo+cF54LBAKpXDMJiGEU3BdF9b9mEpk5d90R4lwz5Qu2PekEWAuAQHaPfjOGPkDtCQarw/nn/wLZnxe/RFGwIAAAAABJRU5ErkJggg==); | |
} | |
.delete::before { | |
/** | |
* https://www.iconfinder.com/icons/928426/bin_delete_empty_junk_remove_trash_icon | |
* Licence : CC BY | |
* Author : Chanut is Industries | |
*/ | |
content: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABIUlEQVQ4T6XTOyiHURgH4Mc9xWRmYHTJYFCUTSImJUUZyMRiEIPkltFiMighk0FMFspiZDGJYjQpYXDp6IhO/y//cpbv/pzfec/7FfjnKEi+H8UEijLcN6xh8/v5b6ACDyj7I9QzqhCOfgPlaMtzRWd4SYEQuwOlfyCvOMV7CgxjK88Eg9hLgW4cYRsNaI7XIVlXnPURvejEcQrU4hpjaMQkZlCMRczG8wXU4C4FCvGEJXxgOQHCFodUI6jMtY3h3kWMeomNBOjDEOrQkgV8FQY7OEiAVqziHqHgXyPtxPm4ldM4T4BQo0PsxuXlBAYwhx7cJEDo1FuMYz8rQVOceR1TOImVb8dKBOtxlQWURL06o6FCqn6EnyrnEvJsxJ/XPgE5azkRuxeDCAAAAABJRU5ErkJggg==); | |
} | |
.description { | |
padding: 0.5rem; | |
} | |
.time { | |
position: absolute; | |
bottom: 0; | |
left: 0; | |
min-height: 40px; | |
border-top: 1px solid #000; | |
width: 100%; | |
} | |
.time .estimated, .time .real { | |
position: relative; | |
float: right; | |
min-width: 115px; | |
min-height: 40px; | |
text-align: center; | |
line-height: 40px; | |
font-weight: bold; | |
padding-top: 0.6rem; | |
} | |
.time .estimated { | |
border-left: 1px solid #000; | |
border-right: 1px solid #000; | |
} | |
.time .estimated::before, .time .real::before { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
text-align: center; | |
font-size: 0.6rem; | |
line-height: normal; | |
border-bottom: 1px dashed #000; | |
} | |
.time .estimated::before { | |
content: "Estimation"; | |
} | |
.time .real::before { | |
content: "Réel"; | |
} | |
@media print { | |
h1, header, .delete, .color { | |
display: none; | |
} | |
@page { { | |
size: auto; /* auto is the initial value */ | |
margin: 0mm; /* this affects the margin in the printer settings */ | |
} | |
html { | |
background-color: #FFFFFF; | |
margin: 0px; /* this affects the margin on the html before sending to printer */ | |
} | |
body { | |
margin: 10mm 15mm 10mm 15mm; /* margin you want for the content */ | |
} | |
} | |
</style> | |
</head> | |
<body> | |
<h1>Génération de tickets pour les scénarii Scrum</h1> | |
<header> | |
<div class="build"> | |
<div class="title">Générer des tickets vierges</div> | |
<div class="form"> | |
<input type="number" id="empty" min="1" value="8" /> | |
<input type="color" id="empty-color" value="#49962C"> | |
</div> | |
<div class="submit"> | |
<button id="generate-empty">Générer</button> | |
</div> | |
</div> | |
<div class="build"> | |
<div class="title">Générer des tickets</div> | |
<div class="form"> | |
<input type="text" id="title" placeholder="Titre des tickets" /> | |
<textarea id="contents" placeholder="Une ligne par ticket"></textarea> | |
<input type="color" id="content-color" value="#49962C"> | |
</div> | |
<div class="submit"> | |
<button id="generate-content">Générer</button> | |
</div> | |
</header> | |
<div id="container"></div> | |
<input type="color" id="colorpicker"> | |
<script> | |
/******************************************************************************* | |
* | |
* EXTERNAL FUNCTIONS | |
* | |
*******************************************************************************/ | |
/** | |
* Escape / Unescape HTML | |
* @see http://jsfiddle.net/Daniel_Hug/qPUEX/show/light/ | |
*/ | |
(function() { | |
var escapeEl = document.createElement('textarea'); | |
window.escapeHTML = function(html) { | |
escapeEl.textContent = html; | |
return escapeEl.innerHTML; | |
}; | |
window.unescapeHTML = function(html) { | |
escapeEl.innerHTML = html; | |
return escapeEl.textContent; | |
}; | |
})(); | |
/** | |
* Convert RGB to Hex | |
* @see http://jsfiddle.net/Mottie/xcqpF/1/light/ | |
*/ | |
function rgb2hex(rgb){ | |
rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); | |
return (rgb && rgb.length === 4) ? "#" + | |
("0" + parseInt(rgb[1],10).toString(16)).slice(-2) + | |
("0" + parseInt(rgb[2],10).toString(16)).slice(-2) + | |
("0" + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; | |
} | |
/******************************************************************************* | |
* | |
* BASE FUNCTIONS | |
* | |
*******************************************************************************/ | |
function addDiv(css, content, content_editable) | |
{ | |
var div = document.createElement('div'); | |
if (css) { | |
div.setAttribute('class', css); | |
} | |
if (content) { | |
div.innerHTML = content; | |
} | |
if (content_editable) { | |
div.setAttribute('contenteditable', 'true'); | |
} | |
return div; | |
} | |
function addButton(css) | |
{ | |
var button = document.createElement('button'); | |
if (css) { | |
button.setAttribute('class', css); | |
} | |
return button; | |
} | |
function generateCard(title, description, color) | |
{ | |
var div_card = addDiv('card'); | |
var div_title = addDiv('title', title, 'true'); | |
var button_delete = addButton('delete'); | |
var button_color = addButton('color'); | |
var div_description = addDiv('description', description, 'true'); | |
var div_time = addDiv('time'); | |
var div_time_real = addDiv('real', ' '); | |
var div_time_estimated = addDiv('estimated', ' ', 'true'); | |
button_color.addEventListener('click', colorCard, false); | |
button_delete.addEventListener('click', deleteCard, false); | |
div_title.appendChild(button_color); | |
div_title.appendChild(button_delete); | |
if (color) { | |
div_title.setAttribute('style', 'background-color:' + color); | |
} | |
div_time.appendChild(div_time_real); | |
div_time.appendChild(div_time_estimated); | |
div_card.appendChild(div_title); | |
div_card.appendChild(div_description); | |
div_card.appendChild(div_time); | |
return div_card; | |
} | |
function deleteCard(event) | |
{ | |
event.target.parentNode.parentNode.remove(); | |
} | |
function colorCard(event) | |
{ | |
var title = event.target.parentNode; | |
current_card_title = title; | |
colorpicker.focus(); | |
colorpicker.value = rgb2hex(window.getComputedStyle(title, null).getPropertyValue("background-color")); | |
colorpicker.click(); | |
} | |
function checkHash(container) | |
{ | |
if ("" == window.location.hash) { | |
return; | |
} | |
var hash_issues = decodeURIComponent(window.location.hash.substring(1)); | |
var issues = hash_issues.split("¤"); | |
issues.forEach(function(issue){ | |
var random_color = '#' + (Math.random() * 0xFFFFFF << 0).toString(16); | |
var project_titles = issue.split('|'); | |
if (project_titles.length !== 2) { | |
return; | |
} | |
var project = project_titles[0]; | |
var titles = project_titles[1].split('§'); | |
titles.forEach(function(title){ | |
container.appendChild(generateCard(escapeHTML(project), escapeHTML(title), random_color)); | |
}); | |
}); | |
} | |
/******************************************************************************* | |
* | |
* MAIN PROGRAM | |
* | |
*******************************************************************************/ | |
var container = document.getElementById('container'); | |
var colorpicker = document.getElementById('colorpicker'); | |
var current_card_title = null; | |
checkHash(container); | |
document.getElementById('generate-empty').addEventListener('click', function() { | |
var number = parseInt(document.getElementById('empty').value, 10); | |
var color = document.getElementById('empty-color').value; | |
for (var iterator = 0; iterator < number; iterator++) { | |
container.appendChild(generateCard(' ', ' ', color)); | |
} | |
}, false); | |
document.getElementById('generate-content').addEventListener('click', function() { | |
var title = document.getElementById('title').value; | |
var color = document.getElementById('content-color').value; | |
var contents = document.getElementById('contents').value.split('\n'); | |
contents.forEach(function (content) { | |
if (!content) { | |
return; | |
} | |
container.appendChild(generateCard(escapeHTML(title), escapeHTML(content), color)); | |
}); | |
}, false); | |
colorpicker.addEventListener('change', function () { | |
if (current_card_title === null) { | |
return; | |
} | |
current_card_title.style.backgroundColor = this.value; | |
current_card_title = null; | |
}, false); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment