This is an automated email from the git hooks/post-receive script. New commit to branch develop in repository pollen. See https://gitlab.nuiton.org/chorem/pollen.git commit fa2d5186a1eb3adeeb84d51bd7d842dc1728a9a1 Author: Kevin Morin <morin@codelutin.com> Date: Thu Jul 27 23:12:38 2017 +0200 fixes #74 Visualisation des votes condensé --- pollen-ui-riot-js/src/main/web/css/blaze.css | 86 ++++++ pollen-ui-riot-js/src/main/web/css/custom.css | 6 + pollen-ui-riot-js/src/main/web/i18n.json | 4 + .../src/main/web/tag/poll/Votes.tag.html | 334 ++++++++++----------- 4 files changed, 260 insertions(+), 170 deletions(-) diff --git a/pollen-ui-riot-js/src/main/web/css/blaze.css b/pollen-ui-riot-js/src/main/web/css/blaze.css index 1e01379d..bb368082 100644 --- a/pollen-ui-riot-js/src/main/web/css/blaze.css +++ b/pollen-ui-riot-js/src/main/web/css/blaze.css @@ -1444,3 +1444,89 @@ h6.c-heading{ color: var(--link); content:"/"; } + +.c-tooltip { + position:relative; + overflow:visible; +} + +.c-tooltip:after,.c-tooltip:before { + visibility:hidden; + z-index:1; +} + +.c-tooltip:before { + position:absolute; + border:.6em solid transparent; + content:""; +} + +.c-tooltip:after { + position:absolute; + padding:.25em .5em; + border:1px solid var(--tooltip-background); + border-radius:4px; + background-color:var(--tooltip-background); + color:var(--tooltip-color); + line-height:1.45; + white-space:nowrap; + content:attr(aria-label); + visibility:hidden; +} + +.c-tooltip:hover:after,.c-tooltip:hover:before { + visibility:visible; +} + +.c-tooltip--top:before { + top:0; + left:50%; + transform:translate(-50%,-1em); + border-top-color:var(--tooltip-background); +} + +.c-tooltip--top:after { + top:0; + left:50%; + transform:translate(-50%,-3em); +} + +.c-tooltip--right:before { + top:50%; + left:100%; + transform:translateY(-50%); + border-right-color:var(--tooltip-background); +} + +.c-tooltip--right:after { + top:50%; + left:100%; + transform:translate(1em,-50%); +} + +.c-tooltip--bottom:before { + bottom:0; + left:50%; + transform:translate(-50%,1em); + border-bottom-color:var(--tooltip-background); +} + +.c-tooltip--bottom:after { + bottom:0; + left:50%; + transform:translate(-50%,3em); +} + +.c-tooltip--left:before { + top:50%; + right:100%; + transform:translateY(-50%); + border-left-color:var(--tooltip-background); +} + +.c-tooltip--left:after { + top:50%; + right:100%; + transform:translate(-1em,-50%); +} + diff --git a/pollen-ui-riot-js/src/main/web/css/custom.css b/pollen-ui-riot-js/src/main/web/css/custom.css index c854c8af..95ee3317 100644 --- a/pollen-ui-riot-js/src/main/web/css/custom.css +++ b/pollen-ui-riot-js/src/main/web/css/custom.css @@ -84,4 +84,10 @@ --tab-selected-background: #fff; --poll-info: #aaa; + --vote-selected: #04c4bb; + --vote-not-selected: #e0e0e0; + --vote-hover: #e7eef1; + + --tooltip-background: #111; + --tooltip-color: #FFF; } diff --git a/pollen-ui-riot-js/src/main/web/i18n.json b/pollen-ui-riot-js/src/main/web/i18n.json index e75d1078..2b71f984 100644 --- a/pollen-ui-riot-js/src/main/web/i18n.json +++ b/pollen-ui-riot-js/src/main/web/i18n.json @@ -125,6 +125,8 @@ "poll_votes_anonymousVoter": "Anonyme", "poll_votes_authorPlaceHolder": "Renseignez votre nom", "poll_votes_toVote": "Voter", + "poll_votes_validateEdition": "Valider", + "poll_votes_cancelEdition": "Annuler", "poll_votes_tooManyChoicesSelected": "Trop de choix sélectionnés. Maximum :", "poll_votes_addChoice": "Ajouter un choix", "poll_votes_choices": "Choix", @@ -657,6 +659,8 @@ "poll_votes_anonymousVoter": "Anonymous", "poll_votes_authorPlaceHolder": "Fill your name", "poll_votes_toVote": "Vote", + "poll_votes_validateEdition": "Validate", + "poll_votes_cancelEdition": "Cancel", "poll_votes_tooManyChoicesSelected": "Too many choices selected. Maximum:", "poll_votes_addChoice": "Add choice", "poll_votes_choices": "Choices", diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html index 7917c63b..dc51f91e 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Votes.tag.html @@ -4,19 +4,19 @@ require("./Podium.tag.html"); require("../components/HumanInput.tag.html"); <Votes> <div class="container" show="{loaded}"> + + <!-- Form to vote --> <div class="voter"> - <form ref="formAddVote" class="fix separator-top separator-left separator-bottom"> - <HumanInput onsubmit="{addVote}"/> + <form id="voteForm" ref="formAddVote" class="fix separator-top separator-left separator-bottom"> + <HumanInput onsubmit="{voteInEdition ? updateVote : addVote}"/> <div class="current-voter"> <div class="o-field o-field--icon-left o-field--icon-right" if={poll.canVote} > <i class="fa fa-fw fa-user c-icon"></i> - <input class="c-field {c-field--error: !voteId && error && error['voter.name']}" + <input class="c-field {c-field--error: !voteInEdition && error && error['voter.name']}" type="text" ref="voterName" name="voterName" - required={!voteId} - disabled={voteId} placeholder={__.authorPlaceHolder} value={poll.voterName}> </div> @@ -31,36 +31,47 @@ require("../components/HumanInput.tag.html"); </div> <div class="current-choice" if={poll.canVote}> - <input if={poll.voteCountingTypeValue && poll.voteCountingTypeValue.renderType==='checkbox'} + <input if={pollTypeCheckbox} class="check" type="checkbox" onchange="{onVoteChanged}" - ref="{choice.id}_voteValue" - disabled={voteId}> - <input if={poll.voteCountingTypeValue && poll.voteCountingTypeValue.renderType==='text'} - class="text c-field {c-field--error: !voteId && error && (error['vote.voteValue#' + choice.id] || error['vote.totalVoteValue'])}" + ref="{choice.id}_voteValue"> + <input if={!pollTypeCheckbox} + class="text c-field {c-field--error: !voteInEdition && error && (error['vote.voteValue#' + choice.id] || error['vote.totalVoteValue'])}" type="number" min="{poll.voteCountingTypeValue.minimumValue}" max="{poll.voteCountingTypeValue.maximumValue}" onchange="{onVoteChanged}" - ref="{choice.id}_voteValue" - disabled={voteId}> + ref="{choice.id}_voteValue"> </div> </div> <div class="current-voter-actions separator-top"> - <button if={poll.canVote} + <button if={poll.canVote && !voteInEdition} class="c-button c-button--brand pull-right" type="submit" name="newVote" - disabled={voteId || tooManyChoicesSelected}> + disabled={tooManyChoicesSelected}> <i class="fa fa-envelope"></i> {__.toVote} </button> + <button if="{voteInEdition}" + class="c-button c-button--error" + type="button" + onclick="{cancelEditVote}"> + <i class="fa fa-remove"/> + {__.cancelEdition} + </button> + <button if="{voteInEdition}" + class="c-button c-button--success" + type="submit"> + <i class="fa fa-check"/> + {__.validateEdition} + </button> </div> - <div class="c-hint--static c-hint--error" if="{!voteId && tooManyChoicesSelected}"> + <div class="c-hint--static c-hint--error" if="{tooManyChoicesSelected}"> {__.tooManyChoicesSelected} {poll.maxChoiceNumber} </div> - <div class="c-hint--static c-hint--error" if="{!voteId && error}"> + <div class="c-hint--static c-hint--error" if="{error}"> <div each={fields in error}> {fields} </div> @@ -71,12 +82,12 @@ require("../components/HumanInput.tag.html"); <div class="frame"> <div if={poll.resultIsVisible} class="results"> <div class="result-label"> - {__.results} + <strong>{__.results}</strong> </div> <div each={choice in poll.choices} class="score-choice separator-top"> <span if={!choice.score}>{parent.__.noVote}</span> <span if={choice.score}> - <i if={choice.score.scoreOrder === 0} class="fa fa-trophy fa-15x winner"></i> + <i if="{choice.score.scoreOrder === 0}" class="fa fa-trophy fa-15x winner"></i> {choice.score.scoreValue} {parent.__["results_unit_" + poll.voteCountingType + "_" + (choice.score.scoreValue > 1 ? "many" : "one")]} </span> @@ -89,123 +100,64 @@ require("../components/HumanInput.tag.html"); </div> </div> + <!-- Show votes --> <div class="voters"> <div each="{vote, index in poll.votes}" class="row"> - <div class="name"> - <i class="fa fa-user"/> - <span if="{!vote.anonymous}">{vote.voterName}</span> - <span if="{vote.anonymous}" class="anonymous-voter">{parent.__.anonymousVoter}</span> + <div class="c-input-group vote-edition"> + <button type="button" + class="c-button u-xsmall c-button--brand" + if="{!poll.isClosed && vote.permission}" + disabled="{voteInEdition != null}" + onclick="{parent.onEditVote(vote)}"> + <i class="fa fa-pencil-square-o"/> + </button> + <button type="button" + class="c-button u-xsmall c-button--error" + if="{poll.permission || !poll.isClosed && vote.permission}" + disabled="{voteInEdition != null}" + onclick="{parent.deleteVote(vote)}"> + <i class="fa fa-trash"/> + </button> + </div> + <div class="name" title="{vote.voterName}"> + <i class="fa fa-user-circle c-icon"/> + <span class="voter-name"> + <span if="{!vote.anonymous}">{vote.voterName}</span> + <span if="{vote.anonymous}" class="anonymous-voter">{parent.__.anonymousVoter}</span> + </span> </div> <div class="results"> <div each="{choice in poll.choices}" - class="result c-tooltip c-tooltip--bottom {'selected' : poll.getVoteValue(vote, choice) == 1}" - aria-label="{choice.choiceValue}"> + class="result {'checkbox' : pollTypeCheckbox} {'selected' : pollTypeCheckbox && poll.getVoteValue(vote, choice) == 1}" + title="{choice.choiceValue}"> + <span if="{!pollTypeCheckbox}">{poll.getVoteValue(vote, choice)}</span> </div> </div> </div> </div> - <form each={vote, index in poll.votes} class="vote separator-right"> - <HumanInput onsubmit="{updateVote(vote)}"/> - <div class="voter"> - <span class="voter-name" - if="{vote.id !== voteId}"> - <i class="fa fa-user"/> - <span if="{!vote.anonymous}">{vote.voterName}</span> - <span if="{vote.anonymous}" class="anonymous-voter">{parent.__.anonymousVoter}</span> - </span> - <div class="o-field o-field--icon-left" - if={vote.id === voteId}> - <i class="fa fa-fw fa-user c-icon"></i> - <input ref="vote_{vote.id}_voter" - name="vote_{vote.id}_voter" - type="text" - class="c-field {c-field--error: error && error['voter.name']}" - value="{vote.voterName}" - required={vote.id === voteId} - disabled={vote.id !== voteId} - placeholder="{__.authorPlaceHolder}"> - </div> - </div> - <div each={choice in choices} class="vote-choice separator-top" > - <input if={poll.voteCountingTypeValue && poll.voteCountingTypeValue.renderType==='checkbox' && (poll.getVoteChoice(vote, choice) || voteId === vote.id)} - ref="vote_{vote.id}_{choice.id}" - name="vote_{vote.id}_{choice.id}" - class="check {c-field--error: vote.id === voteId && error && error['vote.voteValue#' + choice.id]}" - type="checkbox" - checked={poll.getVoteValue(vote, choice) == 1} - disabled={voteId !== vote.id}> - <input if={poll.voteCountingTypeValue.renderType === 'text' && (poll.getVoteChoice(vote, choice) || voteId === vote.id)} - ref="vote_{vote.id}_{choice.id}" - class="text c-field {c-field--error: vote.id === voteId && error && error['vote.voteValue#' + choice.id]}" - name="vote_{vote.id}_{choice.id}" - type="number" - min="{poll.voteCountingTypeValue.minimumValue}" - max="{poll.voteCountingTypeValue.maximumValue}" - required={vote.id === voteId} - disabled={voteId !== vote.id} - value={poll.getVoteValue(vote, choice)}> - </div> - <div class="vote-actions separator-top"> - <span class="c-input-group"> - <button type="button" - class="c-button c-button--error" - if="{(poll.permission || !poll.isClosed && vote.permission) && (!voteId || voteId != vote.id)}" - onclick="{parent.deleteVote(vote)}"> - <i class="fa fa-trash"/> - </button> - <button type="button" - class="c-button c-button--brand" - if="{!poll.isClosed && vote.permission && (!voteId || voteId != vote.id)}" - onclick="{parent.onEditVote(vote)}"> - <i class="fa fa-pencil-square-o"/> - </button> - </span> - <span class="c-input-group" - if={vote.id === voteId}> - <button class="c-button c-button--error" - type="button" - onclick="{cancelEditVote(vote)}"> - <i class="fa fa-remove"/> - </button> - <button class="c-button c-button--success" - ref="vote_{vote.id}_vote" - type="submit"> - <i class="fa fa-check"/> - </button> - </span> - </div> - <div class="c-hint--static c-hint--error" if="{vote.id === voteId && tooManyChoicesSelected}"> - {__.tooManyChoicesSelected} {poll.maxChoiceNumber} - </div> - <div class="c-hint--static c-hint--error" if="{vote.id === voteId && error}"> - <div each={fields in error}> - {fields} - </div> + <!-- Form to add a choice --> + <form ref="formAddChoice" + if={poll.status === "ADDING_CHOICES"}> + <HumanInput onsubmit="{addChoice}"/> + <div class="o-form-element"> + <label class="c-label" for="choice">{__.addChoice}</label> + <div class="c-input-group"> + <div class="o-field"> + <Choice ref="choice" + class="choice c-field" + name="choice" + choice="{choiceToAdd}"/> + </div> + <button type="submit" + class="c-button c-button--success" + tooltips="{__.addChoice}"> + <i class="fa fa-plus"/> + </button> + </div> </div> - </form> - - <form ref="formAddChoice" - if={poll.status === "ADDING_CHOICES"}> - <HumanInput onsubmit="{addChoice}"/> - <div class="o-form-element"> - <label class="c-label" for="choice">{__.addChoice}</label> - <div class="c-input-group"> - <div class="o-field"> - <Choice ref="choice" - class="choice c-field" - name="choice" - choice="{choiceToAdd}"/> - </div> - <button type="submit" - class="c-button c-button--success" - tooltips="{__.addChoice}"> - <i class="fa fa-plus"/> - </button> - </div> - </div> - </form> + </form> </div> <script type="es6"> @@ -222,6 +174,7 @@ require("../components/HumanInput.tag.html"); this.onPollChange = poll => { this.loaded = poll.choices !== undefined; this.poll = poll; + this.pollTypeCheckbox = poll.voteCountingTypeValue && poll.voteCountingTypeValue.renderType === 'checkbox'; this.choiceToAdd = this.poll.initChoice(this.choiceToAdd); this.onVoteChanged(); this.update(); @@ -233,24 +186,28 @@ require("../components/HumanInput.tag.html"); this.update(); }); - this.voteId = null; + this.voteInEdition = null; this.error = null; - this.cancelEditVote = vote => () => { - this.voteId = null; - vote.choice.forEach(c => { - if (this.poll.voteCountingTypeValue.renderType === "text") { - this.refs["vote_" + vote.id + "_" + c.choiceId].value = c.voteValue; - } else { - this.refs["vote_" + vote.id + "_" + c.choiceId].checked = c.voteValue === 1 ? "checked" : ""; - } - }); - this.error = null; + this.cancelEditVote = () => { + this.voteInEdition = null; + this.resetPoll(); }; this.onEditVote = (vote) => () => { - this.voteId = vote.id; + this.voteInEdition = vote; this.error = null; + this.refs.voterName.value = vote.voterName; + vote.choice.forEach(choice => { + let input = this.refs[choice.choiceId + "_voteValue"]; + if (input) { + if (this.poll.voteCountingTypeValue.renderType === "text") { + input.value = choice.voteValue; + } + input.checked = choice.voteValue == 1; + } + }); + this.refs.voterName.focus(); }; this.getChoiceVoteValue = ref => { @@ -279,36 +236,40 @@ require("../components/HumanInput.tag.html"); } }; + this.resetPoll = () => { + if (this.poll.canVote) { + this.refs.voterName.value = null; + this.poll.choices.forEach(choice => { + let input = this.refs[choice.id + "_voteValue"]; + if (this.poll.voteCountingTypeValue.renderType === "text") { + input.value = ""; + } else { + input.checked = ""; + } + }); + } + this.selectedChoiceNb = 0; + this.error = null; + }; + this.addVote = (e) => { e.preventDefault(); e.stopPropagation(); let vote = { id: null, voterName: this.refs.voterName.value, - choice: []}; + choice: [] + }; this.poll.choices.forEach(c => { vote.choice.push({ choiceId: c.id, voteValue: this.getChoiceVoteValue(c.id + "_voteValue") }); - }); this.poll.addVote(vote).then(() => { - if (this.poll.canVote) { - this.refs.voterName.value = null; - this.poll.choices.forEach(choice => { - let input = this.refs[choice.id + "_voteValue"]; - if (this.poll.voteCountingTypeValue.renderType === "text") { - input.value = ""; - } else { - input.checked = ""; - } - }); - } - this.selectedChoiceNb = 0; - this.error = null; + this.resetPoll(); if (this.poll.resultIsVisible) { this.poll.loadResults().then(() => { this.update(); @@ -323,11 +284,12 @@ require("../components/HumanInput.tag.html"); }); }; - this.updateVote = vote => e => { + this.updateVote = e => { e.preventDefault(); e.stopPropagation(); - let updateVote = Object.assign({}, vote); // don't modify original vote - updateVote.voterName = this.refs["vote_" + vote.id + "_voter"].value; + + let updateVote = Object.assign({}, this.voteInEdition); // don't modify original vote + updateVote.voterName = this.refs.voterName.value; this.poll.choices.forEach(choice => { let voteChoice = this.poll.getVoteChoice(updateVote, choice); @@ -337,12 +299,12 @@ require("../components/HumanInput.tag.html"); }; updateVote.choice.push(voteChoice); } - voteChoice.voteValue = this.getChoiceVoteValue("vote_" + vote.id + "_" + choice.id); + voteChoice.voteValue = this.getChoiceVoteValue(choice.id + "_voteValue"); }); + this.poll.updateVote(updateVote).then(() => { - this.voteId = null; - this.selectedChoiceNb = 0; - this.error = null; + this.voteInEdition = null; + this.resetPoll(); if (this.poll.resultIsVisible) { this.poll.loadResults().then(() => { this.update(); @@ -450,10 +412,11 @@ require("../components/HumanInput.tag.html"); } .current-voter, - .voter, .result-label { height: 40px; text-align: center; + padding: 3px; + line-height: 35px; } .voter .fix .choice, @@ -488,26 +451,61 @@ require("../components/HumanInput.tag.html"); flex-direction: column; justify-content: center; align-items: center; + margin: 50px 0; + } + + .voters .row:first-of-type > :not(.vote-edition) { + border-top: 1px solid var(--separator); } .voters .row { display: flex; - width: 70%; + width: 100%; align-items: center; + padding: 0 100px 0 30px; + } + + .voters .vote-edition { + width: 70px; + height: 1.8em; + justify-content: right; + margin-right: 5px; } .voters .name { width: 200px; - text-align: right; - margin-right: 10px; + height: 2em; + padding: 5px; + padding-right: 10px; + border: 1px solid var(--separator); + border-top: none; + overflow: hidden; + white-space:nowrap; + text-overflow:ellipsis; + } + + .voters .row:hover .name { + background-color: var(--focus); + border-bottom-color: var(--brand); } .voters .result { - background-color: rgb(237, 237, 237); min-width: 15px; - height: 15px; - margin: 2px; width: 100%; + height: 2em; + padding: 5px; + border-right: 1px solid var(--separator); + border-bottom: 1px solid var(--separator); + text-align: center; + } + + .voters .row:hover .result { + background-color: var(--vote-hover); + border-bottom-color: var(--brand); + } + + .voters .result.checkbox { + background-color: var(--vote-not-selected); } .voters .results { @@ -515,12 +513,8 @@ require("../components/HumanInput.tag.html"); width: 100%; } - .voters .result:hover { - border: 1px solid #000; - } - - .voters .selected { - background-color: #13a2ff; + .voters .result.checkbox.selected { + background-color: var(--vote-selected); } </style> </Votes> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.