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 0af581788866b2f91b47327822d130f48c8cb42e Author: Sylvain Bavencoff <bavencoff@codelutin.com> Date: Thu May 17 16:34:54 2018 +0200 refs #199 vote par ordre --- pollen-ui-riot-js/src/main/web/css/custom.css | 2 + pollen-ui-riot-js/src/main/web/css/main.css | 12 + pollen-ui-riot-js/src/main/web/i18n/en.json | 3 + pollen-ui-riot-js/src/main/web/i18n/fr.json | 3 + pollen-ui-riot-js/src/main/web/js/Poll.js | 4 + pollen-ui-riot-js/src/main/web/tag/Pollen.tag.html | 14 + .../src/main/web/tag/components/Draggable.tag.html | 132 ++++++ .../web/tag/components/MultiLineLabel.tag.html | 6 +- .../src/main/web/tag/poll/ChoiceView.tag.html | 61 +-- .../src/main/web/tag/poll/EditVoteOrder.tag.html | 505 +++++++++++++++++++++ .../src/main/web/tag/poll/Report.tag.html | 112 +---- .../src/main/web/tag/poll/Votes.tag.html | 13 +- .../src/main/web/tag/poll/VotesTable.tag.html | 2 +- .../src/main/web/tag/popup/AddReportModal.tag.html | 102 +++++ .../main/web/tag/popup/ChoiceDetailModal.tag.html | 84 ++++ .../main/web/tag/popup/ShowReportsModal.tag.html | 98 ++++ 16 files changed, 980 insertions(+), 173 deletions(-) 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 5de9e7fd..4b5153c2 100644 --- a/pollen-ui-riot-js/src/main/web/css/custom.css +++ b/pollen-ui-riot-js/src/main/web/css/custom.css @@ -34,6 +34,8 @@ --poll-voting: #fff2ba; --poll-closed: #ffd9ba; + --choice-tile: #d1eaea; + --link: #96a8b2; --separator: #b2c7d3; diff --git a/pollen-ui-riot-js/src/main/web/css/main.css b/pollen-ui-riot-js/src/main/web/css/main.css index 8d7aefcc..f2131bf1 100644 --- a/pollen-ui-riot-js/src/main/web/css/main.css +++ b/pollen-ui-riot-js/src/main/web/css/main.css @@ -223,6 +223,18 @@ ul { background-color: var(--poll-closed); } +.choice-tile { + background-color: var(--choice-tile); +} + +.choicesScale, +.choicesStash { + background-color: var(--h1-background); + color: var(--h1); + opacity: 0.4; + border-radius: 10px; +} + .info-label { font-size: 0.8em; color: var(--info); diff --git a/pollen-ui-riot-js/src/main/web/i18n/en.json b/pollen-ui-riot-js/src/main/web/i18n/en.json index a6d87a58..c37ddfe1 100644 --- a/pollen-ui-riot-js/src/main/web/i18n/en.json +++ b/pollen-ui-riot-js/src/main/web/i18n/en.json @@ -145,6 +145,9 @@ "poll_votes_totals": "Total", "poll_votes_eliminated": "Eliminated", "poll_votes_noVote": "No vote", + "poll_votes_choicesScaleHelper": "Place here the choices below in the order of your preferences", + "poll_votes_choicesScaleHelper_withMax": "Place here the {0} firsts choices below in the order of your preferences", + "poll_votes_choicesStashHelper": "Stash here the choices not selected", "poll_votes_results_unit_1_one": "vote", "poll_votes_results_unit_1_many": "votes", "poll_votes_results_unit_2_one": "point", diff --git a/pollen-ui-riot-js/src/main/web/i18n/fr.json b/pollen-ui-riot-js/src/main/web/i18n/fr.json index dfc12241..3ac0d4a0 100644 --- a/pollen-ui-riot-js/src/main/web/i18n/fr.json +++ b/pollen-ui-riot-js/src/main/web/i18n/fr.json @@ -145,6 +145,9 @@ "poll_votes_totals": "Total", "poll_votes_eliminated": "Éliminé", "poll_votes_noVote": "Aucun vote", + "poll_votes_choicesScaleHelper": "Placez ici les choix ci-dessous dans l'ordre de vos préférences", + "poll_votes_choicesScaleHelper_withMax": "Placez ici vos {0} premiers choix ci-dessous dans l'ordre de vos préférences", + "poll_votes_choicesStashHelper": "Remisez ici les choix non selectionnés", "poll_votes_results_unit_1_one": "vote", "poll_votes_results_unit_1_many": "votes", "poll_votes_results_unit_2_one": "point", diff --git a/pollen-ui-riot-js/src/main/web/js/Poll.js b/pollen-ui-riot-js/src/main/web/js/Poll.js index 8f4ed9a3..e3114ac5 100644 --- a/pollen-ui-riot-js/src/main/web/js/Poll.js +++ b/pollen-ui-riot-js/src/main/web/js/Poll.js @@ -412,6 +412,10 @@ class Poll { return Promise.reject("Init poll after get invalid emails"); } + isVoteByOrder() { + return [3, 5, 6, 7].indexOf(this.voteCountingType) >= 0; + } + } export default singleton(Poll); diff --git a/pollen-ui-riot-js/src/main/web/tag/Pollen.tag.html b/pollen-ui-riot-js/src/main/web/tag/Pollen.tag.html index 4a689c36..9cfd4e59 100644 --- a/pollen-ui-riot-js/src/main/web/tag/Pollen.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/Pollen.tag.html @@ -46,10 +46,16 @@ import "./favoriteList/FavoriteList.tag.html"; import "./popup/ConfirmPopup.tag.html"; import "./popup/InformationPopup.tag.html"; import "./popup/GtuChangeModal.tag.html"; +import "./popup/ChoiceDetailModal.tag.html"; +import "./popup/AddReportModal.tag.html"; +import "./popup/ShowReportsModal.tag.html"; <Pollen class="body-wrapper colors-default"> <ConfirmPopup/> <InformationPopup/> <GtuChangeModal/> + <ChoiceDetailModal/> + <AddReportModal/> + <ShowReportsModal/> <PollenHeader/> <PollenWaiter parent-id="body-content"/> <div id="body-content" class="body-content"> @@ -302,6 +308,14 @@ import "./popup/GtuChangeModal.tag.html"; } }; + window.onresize = () => { + this.bus.trigger("resize"); + }; + + window.addEventListener("orientationchange", () => { + setTimeout(() => this.bus.trigger("orientationchange"), 500); + }); + </script> <style> diff --git a/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html new file mode 100644 index 00000000..52c3c035 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/components/Draggable.tag.html @@ -0,0 +1,132 @@ +<!-- + #%L + Pollen :: UI RiotJs + %% + Copyright (C) 2009 - 2017 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> +<Draggable onmousedown={startDesktop} + ontouchstart={startModbil} + ontouchmove={moveMobil} + ontouchend={endMobil}> + <yield/> + + <script type="es6"> + + this.positionMouseInit = {x: 0, y: 0}; + this.positionElementInit = {x: 0, y: 0}; + this.positionCurent = {x: 0, y: 0}; + + this.setPosition = (x, y) => { + this.positionCurent.x = Math.round(x); + this.positionCurent.y = Math.round(y); + this.root.style.transform = "translate(" + this.positionCurent.x + "px, " + this.positionCurent.y + "px)"; + // this.logger.log("Move at " + x + ", " + y); + }; + + this.getX = () => this.positionCurent.x; + this.getY = () => this.positionCurent.y; + + this.startPosition = (x, y) => { + this.positionMouseInit.x = x; + this.positionMouseInit.y = y; + this.positionElementInit.x = this.positionCurent.x; + this.positionElementInit.y = this.positionCurent.y; + this.root.classList.add("drag"); + // this.logger.log("Start at " + this.positionMouseInit.x + ", " + this.positionMouseInit.y); + }; + + this.movePosition = (x, y) => { + // calculate the new cursor position: + this.setPosition( + this.positionElementInit.x + x - this.positionMouseInit.x, + this.positionElementInit.y + y - this.positionMouseInit.y + ); + }; + + this.endPosition = (x, y) => { + this.movePosition(x, y); + this.root.classList.remove("drag"); + this.opts.onDrop && this.opts.onDrop(this, this.positionCurent.x, this.positionCurent.y); + }; + + /*********************************** + ** Desktop Drage & Drop + *************************************/ + + this.startDesktop = event => { + this.startPosition(event.clientX, event.clientY); + document.onmousemove = event2 => this.moveDesktop(event2); + document.onmouseup = event1 => this.endDesktop(event1); + document.onclick = event3 => {event3.stopImmediatePropagation();}; + event.stopImmediatePropagation(); + }; + + this.moveDesktop = event => { + this.movePosition(event.clientX, event.clientY); + event.stopImmediatePropagation(); + }; + + this.endDesktop = event => { + this.endPosition(event.clientX, event.clientY); + /* stop moving when mouse button is released:*/ + document.onmouseup = null; + document.onmousemove = null; + document.onclick = null; + event.stopImmediatePropagation(); + }; + + /*********************************** + ** Mobil Drage & Drop + *************************************/ + + this.startModbil = event => { + let touch = event.changedTouches[0]; + this.startPosition(touch.pageX, touch.pageY); + event.stopPropagation(); + }; + + this.moveMobil = event => { + let touch = event.changedTouches[0]; + this.movePosition(touch.pageX, touch.pageY); + event.stopPropagation(); + event.preventDefault(); + }; + + this.endMobil = event => { + let touch = event.changedTouches[0]; + this.endPosition(touch.pageX, touch.pageY); + event.stopPropagation(); + }; + + </script> + <style> + draggable { + position: absolute; + top: 0; + left: 0; + user-select: none; + transition: transform 500ms; + } + + draggable.drag { + z-index: 1; + transition: none; + } + + </style> + +</Draggable> diff --git a/pollen-ui-riot-js/src/main/web/tag/components/MultiLineLabel.tag.html b/pollen-ui-riot-js/src/main/web/tag/components/MultiLineLabel.tag.html index 5cbfe5a2..aab89abf 100644 --- a/pollen-ui-riot-js/src/main/web/tag/components/MultiLineLabel.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/components/MultiLineLabel.tag.html @@ -8,12 +8,12 @@ it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - + You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. #L% @@ -23,7 +23,7 @@ <span ref="label"></span> <script> - this.on("mount", () => { + this.on("update", () => { let labelText; if (this.opts.label) { labelText = this.opts.label; diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/ChoiceView.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/ChoiceView.tag.html index 78f1f090..3eb25302 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/ChoiceView.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/ChoiceView.tag.html @@ -38,11 +38,11 @@ import "../components/MultiLineLabel.tag.html"; {formatDate(parseInt(opts.choice.choiceValue, 10), "L LT")} </span> <div class="choice-ressource" - if={opts.choice.choiceType === "RESOURCE" && meta && isImage(meta)}> + if={opts.choice.choiceType === "RESOURCE" && meta && isImage()}> <img class="image-preview" src="{resourceService.getPreviewUrl(opts.choice.choiceValue)}"/> </div> <div class="choice-ressource" - if={opts.choice.choiceType === "RESOURCE" && meta && !isImage(meta)}> + if={opts.choice.choiceType === "RESOURCE" && meta && !isImage()}> {meta.name} </div> <div if={opts.choice.choiceType === "RESOURCE" || opts.choice.description && !opts.showdescription} class="info-label"> @@ -54,43 +54,6 @@ import "../components/MultiLineLabel.tag.html"; </div> <Report target="{opts.choice}" if="{!opts.hidereport}"/> - <div if={showModalImage} onclick={closeModalImage}> - <div class="c-overlay"></div> - <div class="modal-image o-modal"> - <div class="c-card"> - <header class="c-card__header"> - <h2 if={opts.choice.choiceType === "TEXT"}> - {opts.choice.choiceValue} - </h2> - <h2 if={opts.choice.choiceType === "DATE"}> - {formatDate(parseInt(opts.choice.choiceValue, 10),"LLL")} - </h2> - <h2 if={opts.choice.choiceType === "DATETIME"}> - {formatDate(parseInt(opts.choice.choiceValue, 10))} - </h2> - <div if={opts.choice.choiceType === "RESOURCE" && meta && isImage(meta)}> - <img if={meta && isImage(meta)} - class="image-preview" - src="{session.configuration.endPoint}/v1/resources/{opts.choice.choiceValue}"/> - </div> - <div if={opts.choice.choiceType === "RESOURCE" && meta && !isImage(meta)}> - <h2>{meta.name}</h2> - <a class="c-button c-button--info" - if={meta && !isImage(meta)} - href="{session.configuration.endPoint}/v1/resources/{opts.choice.choiceValue}/download" - target="_blank"> - <i class="fa fa-download"></i> - {__.download} - </a> - </div> - </header> - <div class="c-card__body"> - <MultiLineLabel label="{opts.choice.description}"></MultiLineLabel> - </div> - </div> - </div> - </div> - <script type="es6"> import session from "../../js/Session"; import resourceService from "../../js/ResourceService"; @@ -108,8 +71,8 @@ import "../components/MultiLineLabel.tag.html"; }); } - this.isImage = (meta) => { - return meta.contentType.match(/^image\//i); + this.isImage = () => { + return this.meta.contentType.match(/^image\//i); }; this.standardTooltip = () => { @@ -118,18 +81,7 @@ import "../components/MultiLineLabel.tag.html"; this.openModalImage = () => { if (this.opts.choice.description && !this.opts.showdescription || this.opts.choice.choiceType === "RESOURCE") { - this.showModalImage = true; - } - }; - - this.closeModalImage = () => { - this.showModalImage = false; - }; - - this.onEscape = () => { - if (this.showModalImage) { - this.closeModalImage(); - this.update(); + this.bus.trigger("openChoiceDetail", this.opts.choice, this.meta); } }; @@ -145,8 +97,6 @@ import "../components/MultiLineLabel.tag.html"; }); } - this.listen("escape", this.onEscape); - </script> <style> @@ -170,6 +120,7 @@ import "../components/MultiLineLabel.tag.html"; .choice-ressource .image-preview{ max-width: 7em; max-height: 3em; + pointer-events: none; } .modal-image { diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/EditVoteOrder.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/EditVoteOrder.tag.html new file mode 100644 index 00000000..204aea04 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/poll/EditVoteOrder.tag.html @@ -0,0 +1,505 @@ +<!-- + #%L + Pollen :: UI RiotJs + %% + Copyright (C) 2009 - 2017 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> + +<EditVoteOrder class="no-anim"> + + <div class="form-wrapper"> + <form id="voteForm" class="voter" ref="formAddVote"> + <HumanInput onsubmit="{voteInEdition ? updateVote : addVote}"></HumanInput> + <div class="current-voter"> + <div class="o-field o-field--icon-left o-field--icon-right" + if={poll.canVote || voteInEdition} > + <i class="fa fa-fw fa-user c-icon"></i> + <input class="c-field {c-field--error: !voteInEdition && error && error['voter.name']}" + type="text" + ref="voterName" + name="voterName" + required + maxlength="255" + tabindex="1" + placeholder={_t.authorPlaceHolder} + value={poll.voterName}> + </div> + <div if={!poll.canVote && !voteInEdition} + class="choices-label"> + {_t.choices} + </div> + </div> + <div class="choicesScale" ref="scale"> + <i class="fa fa-plus-circle"></i> + {maxChoiceNumber ? _l("choicesScaleHelper_withMax", maxChoiceNumber) : _t.choicesScaleHelper} + <i class="fa fa-minus-circle"></i> + </div> + <div class="choicesDock" ref="dock"> + <button if={!voteInEdition} + class="c-button c-button--brand pull-right" + type="submit" + name="newVote" + disabled={voting || !orderedAllChoices || maxChoiceNumber && maxChoiceNumber < choicesSelectedCount}> + <i class="fa fa-envelope"></i> + {_t.toVote} + </button> + <button if="{voteInEdition}" + class="c-button c-button--error" + type="button" + onclick="{cancelEditVote}" + disabled={voting}> + <i class="fa fa-remove"></i> + {_t.cancelEdition} + </button> + <button if="{voteInEdition}" + class="c-button c-button--success" + disabled={voting || !orderedAllChoices || maxChoiceNumber && maxChoiceNumber < choicesSelectedCount} + type="submit"> + <i class="fa fa-check"></i> + {_t.validateEdition} + </button> + </div> + + <div class="c-hint--static c-hint--error"> + <span if="{maxChoiceNumber && maxChoiceNumber < choicesSelectedCount}"> + {_t.tooManyChoicesSelected} {maxChoiceNumber} + </span> + </div> + <div class="choicesStash" ref="stash" show={maxChoiceNumber}> + <i class="fa fa-trash"></i> + {_t.choicesStashHelper} + </div> + <Draggable each={choice in poll.choices && poll.choices.slice().reverse() || []} + ref="choices" + on-drop={dropChoice} + class="choice-draggable"> + <div class={choice-tile: true, noFirst: !isFirstGroup, noLast: !isLastGroup}> + <div class="choice-order">{voteOrder !== undefined ? voteOrder + 1 : ""}</div> + <ChoiceView choice={choice} center="true" tooltip-placement="left"></ChoiceView> + <div if={parent.poll.resultIsVisible} class="score-choice"> + <span if={!choice.score}>{parent.parent._t.noVote}</span> + <span if={choice.score}> + <i if="{choice.score.scoreOrder === 0}" class="fa fa-trophy fa-15x winner"></i> + {choice.score.scoreValue} + <span if={choice.score.scoreValue || !parent.pollTypeCoombs}> + {parent.parent._t["results_unit_" + parent.poll.voteCountingType + "_" + (choice.score.scoreValue > 1 ? "many" : "one")]} + </span> + <span if={!choice.score.scoreValue && parent.pollTypeCoombs}> + {parent.parent._t.eliminated} + </span> + </span> + </div> + </div> + </Draggable> + </form> + + </div> + <script type="es6"> + import "../components/Draggable.tag.html"; + import session from "../../js/Session"; + import moment from "moment"; + import poll from "../../js/Poll.js"; + + this.loaded = false; + this.moment = moment; + this.installBundle(session, "poll_votes"); + + this.voting = false; + this.orderedAllChoices = false; + + this.poll = poll; + this.poll.loadForVotes().then(() => { + this.update(); + }); + + this.onPollChange = poll2 => { + this.poll = poll2; + this.pollTypeCoombs = poll2.voteCountingType && poll2.voteCountingType === 7; + this.maxChoiceNumber = this.poll.voteCountingConfig.maxChoiceNumber; + this.update(); + this.placeChoices(true); + }; + + this.listen("resize", () => this.placeChoices(true)); + this.listen("orientationchange", () => this.placeChoices(true)); + + this.listen("poll", this.onPollChange); + this.listen("user", (user, oldUser) => { + if (user !== oldUser) { + if (user && this.refs.voterName && this.refs.voterName.value !== "") { + this.refs.voterName.value = user.name; + } + this.update(); + } + }); + + this.placeChoices = noAnim => { + if (!this.refs.choices) { + return; + } + if (noAnim) { + this.root.classList.add("no-anim"); + } + + // on place les choix selectionnés sur l'échelle + let scale = this.refs.scale; + let scaleCenterX = scale.offsetLeft + scale.offsetWidth / 2; + + + let choicesSelected = this.refs.choices + .filter(choice1 => choice1.voteOrder !== undefined) + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)); + + this.choicesSelectedCount = choicesSelected.length; + + let scaleHeight = choicesSelected + .map(c => (c.choiceType === "RESOURCE" ? 56 : 40) + 20) // FIXME dans 95% une ressource est une image + .reduce((h1, h2) => h1 + h2, 20); + scale.style.height = scaleHeight + "px"; + + let choicesHeightTot = choicesSelected + .map(choice1 => choice1.root.offsetHeight) + .reduce((h1, h2) => h1 + h2, 0); + + let maxIndex = choicesSelected + .map(choice1 => choice1.voteOrder) + .reduce((order1, order2) => Math.max(order1, order2), 0); + + let paddingChoice = (scale.offsetHeight - choicesHeightTot) / (maxIndex + 2); + let currentY = scale.offsetTop + paddingChoice; + + for (let index = 0; index <= maxIndex; index++) { + let choices = choicesSelected.filter(choice => choice.voteOrder === index); + for (let index2 = 0, l = choices.length; index2 < l; index2++) { + let choice = choices[index2]; + choice.isFirstGroup = index2 === 0; + choice.isLastGroup = index2 === l - 1; + let x = scaleCenterX - (choice.root.offsetWidth / 2); + choice.setPosition(x, currentY); + currentY += choice.root.offsetHeight; + } + currentY += paddingChoice; + } + + // on place les choix en attent sur la pile + const dockOffset = 3; + const dockChoiceVisibleMax = 3; + let nbDock = this.refs.choices + .filter(choice1 => choice1.voteOrder === undefined && !choice1.stashed) + .length; + let dockChoiceVisible = Math.min(dockChoiceVisibleMax, nbDock); + + let dock = this.refs.dock; + let dockX = dock.offsetLeft + (dock.offsetWidth + dockOffset * dockChoiceVisible) / 2; + let dockY = dock.offsetTop + (dock.offsetHeight + dockOffset * dockChoiceVisible) / 2; + let dockIndex = 0; + + this.orderedAllChoices = true; + + this.refs.choices + .filter(choice => choice.voteOrder === undefined && !choice.stashed) + .forEach(choice => { + let offset = Math.min(0, (nbDock - dockChoiceVisible) - dockIndex) * dockOffset; + let y = dockY - choice.root.offsetHeight / 2 + offset; + let x = dockX - choice.root.offsetWidth / 2 + offset; + dockIndex++; + choice.isFirstGroup = true; + choice.isLastGroup = true; + choice.setPosition(x, y); + this.orderedAllChoices = false; + }); + + // on place les choix remisé dans la remise + if (this.maxChoiceNumber) { + + let stash = this.refs.stash; + let stashCenterX = stash.offsetLeft + stash.offsetWidth / 2; + let stashHeight = 0; + currentY = stash.offsetTop; + this.refs.choices + .filter(choice => choice.stashed) + .sort((c1, c2) => Math.sign(c1.choice.choiceOrder - c2.choice.choiceOrder)) + .forEach((choice) => { + let y = currentY; + let x = stashCenterX - choice.root.offsetWidth / 2; + choice.isFirstGroup = true; + choice.isLastGroup = true; + choice.setPosition(x, y); + currentY += choice.root.offsetHeight; + stashHeight += choice.root.offsetHeight; + }); + stash.style.height = stashHeight + "px"; + } + + setTimeout(() => { + this.root.classList.remove("no-anim"); + }, 100); + this.update(); + }; + + this.dropChoice = (choiceElt, x, y) => { + let scale = this.refs.scale; + let yMin = scale.offsetTop - choiceElt.root.offsetHeight / 2; + let yMax = scale.offsetTop + scale.offsetHeight - choiceElt.root.offsetHeight / 2; + + choiceElt.voteOrder = undefined; + let curentChoices = this.refs.choices + .filter(choice1 => choice1.voteOrder !== undefined && choice1 !== choiceElt) + .sort((c1, c2) => Math.sign(c1.getY() - c2.getY())); + + if (yMin <= y && y <= yMax) { + + curentChoices.forEach(c => { + c.voteOrder = 1 + c.voteOrder * 2; // on multiplie par 2 pour toujours avoir un index d'insertion de libre + }); + + let index = 0; + curentChoices.forEach(choice1 => { + let choice1Min = choice1.getY() - choiceElt.root.offsetHeight / 2; + let choice1Max = choice1Min + choice1.root.offsetHeight; + if (choice1Min <= y && y <= choice1Max) { // le choix est palcé au méme endroit + index = choice1.voteOrder; + } else if (y > choice1Max) { // le choix est palcé strictement après + index = choice1.voteOrder + 1; + } + this.logger.log("drop :" + choice1Min + ", " + choice1Max + ", " + y + ", " + choice1.voteOrder + " ==> " + index); + }); + choiceElt.voteOrder = index; + } + + if (this.maxChoiceNumber) { + yMin = this.refs.stash.offsetTop - choiceElt.root.offsetHeight / 2; + choiceElt.stashed = yMin <= y; + } + + this.compactVoteOrders(); + choiceElt.root.style["z-index"] = "1"; + + this.placeChoices(); + + setTimeout(() => { + choiceElt.root.style["z-index"] = ""; + }, 600); + }; + + this.compactVoteOrders = () => { + // on recompacte la liste pour ne plus avoir de trous dans les index; + let newIndex = -1; + let currentIndex = -1; + this.refs.choices + .filter(choice1 => choice1.voteOrder !== undefined) + .sort((c1, c2) => Math.sign(c1.voteOrder - c2.voteOrder)) + .forEach(choice1 => { + if (choice1.voteOrder > currentIndex) { + currentIndex = choice1.voteOrder; + newIndex++; + } + choice1.voteOrder = newIndex; + }); + }; + + this.resetVoteForm = () => { + if (this.poll.canVote) { + this.refs.voterName.value = null; + this.refs.choices.forEach(choice => { + choice.voteOrder = undefined; + choice.stashed = false; + }); + this.placeChoices(); + } + }; + + this.cancelEditVote = () => { + this.voteInEdition = null; + this.resetVoteForm(); + }; + + this.editVote = vote => { + this.voteInEdition = vote; + this.update(); + this.refs.voterName.value = vote.voterName; + this.refs.choices.forEach(choiceElt => { + let choice = vote.choice.find(c => c.choiceId === choiceElt.choice.id); + if (this.maxChoiceNumber && !choice.voteValue) { + choiceElt.voteOrder = undefined; + choiceElt.stashed = true; + } else { + choiceElt.voteOrder = choice && choice.voteValue; + choiceElt.stashed = false; + } + }); + this.compactVoteOrders(); + this.placeChoices(); + }; + + this.addVote = e => { + e.preventDefault(); + e.stopPropagation(); + + this.voting = true; + this.update(); + + let vote = { + id: null, + voterName: this.refs.voterName.value, + choice: [] + }; + + this.refs.choices.forEach(c => { + vote.choice.push({ + choiceId: c.choice.id, + voteValue: c.voteOrder + 1 + }); + }); + + this.poll.addVote(vote).then(() => { + //this.resetVoteForm(); + if (this.poll.resultIsVisible) { + this.poll.loadResults().then(() => { + this.voting = false; + this.update(); + }, () => { + this.voting = false; + this.update(); + }); + } else { + this.voting = false; + this.update(); + } + }, + (error) => { + this.error = error; + this.voting = false; + this.update(); + }); + }; + + this.updateVote = e => { + e.preventDefault(); + e.stopPropagation(); + + this.voting = true; + this.update(); + + let updateVote = Object.assign({}, this.voteInEdition); // don't modify original vote + updateVote.voterName = this.refs.voterName.value; + + this.refs.choices.forEach(c => { + let voteChoice = this.poll.getVoteChoice(updateVote, c.choice); + if (!voteChoice) { + voteChoice = { + choiceId: c.choice.id + }; + updateVote.choice.push(voteChoice); + } + voteChoice.voteValue = c.voteOrder + 1; + }); + + this.poll.updateVote(updateVote).then(() => { + this.voteInEdition = null; + this.resetVoteForm(); + if (this.poll.resultIsVisible) { + this.poll.loadResults().then(() => { + this.voting = false; + this.update(); + }, () => { + this.voting = false; + this.update(); + }); + } else { + this.voting = false; + this.update(); + } + }, + (error) => { + this.voting = false; + this.error = error; + this.update(); + }); + }; + + </script> + <style> + + .form-wrapper { + width: 320px; + max-width: 90%; + margin: 0 auto; + position: relative; + } + + .choice-draggable { + width: 95%; + + } + + .choicesScale, + .choicesStash { + min-height: 200px; + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + text-align: center; + margin: 5px 0; + font-size: 2.4em; + } + + .choicesScale { + min-height: 400px; + } + + .choicesDock { + height: 50px; + display: flex; + justify-content: space-around; + align-items: center; + } + + .choice-tile { + width: 100%; + min-height: 40px; + font-size: 1.2em; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 1px 1px 1px hsla(0,0%,7%,.6); + padding: 5px; + border-radius: 10px; + } + + .choice-tile.noFirst { + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + .choice-tile.noLast { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + + editvoteorder.no-anim .choice-draggable { + transition: none; + } + + .c-hint--static { + height: 1.2em; + } + + + </style> +</EditVoteOrder> diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/Report.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/Report.tag.html index 5f08f02f..323aa905 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/Report.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/Report.tag.html @@ -33,64 +33,6 @@ <i class="fa fa-exclamation-circle" aria-hidden="true"></i> </span> - <modal ref="modalAddReport" header={__.add_title} onsubmit={submitAddReport} label={__.add_action} type="warning"> - <fieldset class="o-fieldset"> - <legend class="o-fieldset__legend">{parent.__.level}</legend> - <label class="c-field c-field--choice"> - <input type="radio" - name="level" - ref="level" - required - value="SPAM"> - {parent.__.level_spam_detail} - </label> - <label class="c-field c-field--choice"> - <input type="radio" - name="level" - ref="level" - value="OFF_TOPIC"> - {parent.__.level_off_topic_detail} - </label> - <label class="c-field c-field--choice"> - <input type="radio" - name="level" - ref="level" - value="ILLEGAL"> - {parent.__.level_illegal_detail} - </label> - </fieldset> - - <div class="o-form-element"> - <label class="c-label" for="email">{parent.__.email}</label> - <input type="email" - id="email" - ref="email" - placeholder="{parent.__.email_placeholder}" - value={parent.session.getUser() && parent.session.getUser().email} - required - maxlength="255" - class="c-field"/> - </div> - </modal> - - <modal ref="modalReports" header={__.reports_title} only-ok="true"> - <div each={level in parent.reportLevels}> - <div class="c-card__item c-card__item--{error : level == 'ILLEGAL', warning: level == 'SPAM', info : level == 'OFF_TOPIC'}"> - {parent.parent.__['level_' + level.toLowerCase()]} - </div> - <div class="c-card__item"> - <div each={report in parent.parent.filterReports(level)} class="report {ignore : report.ignore}"> - <div class="report-email"> - {report.email} - </div> - <a onclick={parent.parent.parent.toggleIgnoreReport(report)} title={report.ignore ? parent.parent.parent.__.toEnable : parent.parent.parent.__.toIgnore}> - <i class="fa fa-{report.ignore ? 'bell' : 'bell-slash'}" aria-hidden="true"></i> - </a> - </div> - </div> - </div> - </modal> - <script type="es6"> import session from "../../js/Session"; import poll from "../../js/Poll.js"; @@ -105,63 +47,13 @@ this.openModalAddReport = e => { e.preventDefault(); e.stopPropagation(); - this.refs.modalAddReport.refs.level.forEach(radio => {radio.checked = false;}); - this.refs.modalAddReport.open().then(() => {this.update();}, () => {}); - }; - - this.submitAddReport = () => { - let report = { - level: this.refs.modalAddReport.refs.level.find(radio => radio.checked).value, - email: this.refs.modalAddReport.refs.email.value - }; - - return this.poll.addReport(this.opts.target, report); - + this.bus.trigger("openAddReport", this.opts.target); }; this.openReports = e => { e.preventDefault(); e.stopPropagation(); - this.poll.getReports(this.opts.target).then(reports => { - this.reports = reports; - this.reportLevels = this.levels.filter(level => reports.find(report => report.level === level)); - this.refs.modalReports.open().then(() => { - if (this.poll.isPoll(this.opts.target)) { - this.poll.reloadPoll(); - } else if (this.poll.isComment(this.opts.target)) { - this.poll.loadComments(); - } else if (this.poll.isChoice(this.opts.target)) { - this.poll.loadChoices(); - } else { - this.update(); - } - }, () => {}); - this.update(); - }); - }; - - this.toggleIgnoreReport = report => () => { - let report2 = Object.assign({}, report); - report2.ignore = !report2.ignore; - this.poll.saveReport(this.opts.target, report2).then(() => { - this.poll.getReports(this.opts.target).then(reports => { - this.reports = reports; - this.reportLevels = this.levels.filter(level => reports.find(report3 => report3.level === level)); - this.update(); - }); - }); - }; - - this.getReportLevels = () => { - this.levels.filter(level => this.reports.find(report => report.level === level)); - }; - - this.hasReports = level => { - return this.filterReports(level).length > 0; - }; - - this.filterReports = level => { - return this.reports.filter(report => report.level === level); + this.bus.trigger("openReports", this.opts.target); }; </script> 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 19d88d5f..4d679bf0 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 @@ -21,6 +21,7 @@ import "./Choice.tag.html"; import "./ChoiceView.tag.html"; import "./EditVote.tag.html"; +import "./EditVoteOrder.tag.html"; import "./VotesTable.tag.html"; import "../components/HumanInput.tag.html"; import "../components/LazyLoad.tag.html"; @@ -28,7 +29,10 @@ import "../components/Avatar.tag.html"; <Votes> <div class="container" show="{loaded}"> - <EditVote ref="editVote"/> + <EditVote ref="editVote" + if={!poll.isVoteByOrder()}/> + <EditVoteOrder ref="editVoteOrder" + if={poll.isVoteByOrder()}/> <!-- Form to vote --> <p class="warning-label warning" if="{loaded && !poll.canVote}"> @@ -59,7 +63,7 @@ import "../components/Avatar.tag.html"; </form> <!-- Show votes --> - <VotesTable if="{poll.voteIsVisible && poll.voteCount > 0}" /> + <VotesTable if="{poll.voteIsVisible && poll.voteCount > 0}" on-edit-vote={editVote}/> </div> @@ -85,8 +89,9 @@ import "../components/Avatar.tag.html"; this.listen("poll", this.onPollChange); - this.editVote = (vote) => () => { - this.refs.editVote.editVote(vote); + this.editVote = (vote) => { + let editor = this.poll.isVoteByOrder() ? this.refs.editVoteOrder : this.refs.editVote; + editor.editVote(vote); }; this.addChoice = (e) => { diff --git a/pollen-ui-riot-js/src/main/web/tag/poll/VotesTable.tag.html b/pollen-ui-riot-js/src/main/web/tag/poll/VotesTable.tag.html index d9bf12e3..fded9bc5 100644 --- a/pollen-ui-riot-js/src/main/web/tag/poll/VotesTable.tag.html +++ b/pollen-ui-riot-js/src/main/web/tag/poll/VotesTable.tag.html @@ -146,7 +146,7 @@ }); this.editVote = (vote) => () => { - this.parent.refs.editVote.editVote(vote); + this.opts.onEditVote && this.opts.onEditVote(vote); }; this.deleteVote = (vote) => (e) => { diff --git a/pollen-ui-riot-js/src/main/web/tag/popup/AddReportModal.tag.html b/pollen-ui-riot-js/src/main/web/tag/popup/AddReportModal.tag.html new file mode 100644 index 00000000..34335b8b --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/popup/AddReportModal.tag.html @@ -0,0 +1,102 @@ +<!-- + #%L + Pollen :: UI RiotJs + %% + Copyright (C) 2009 - 2017 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> +import "./Modal.tag.html"; +<AddReportModal> + <modal ref="modal" + header={_t.add_title} + onsubmit={submit} + label={_t.add_action} + type="warning"> + <fieldset class="o-fieldset"> + <legend class="o-fieldset__legend">{parent._t.level}</legend> + <label class="c-field c-field--choice"> + <input type="radio" + name="level" + ref="level" + required + value="SPAM"> + {parent._t.level_spam_detail} + </label> + <label class="c-field c-field--choice"> + <input type="radio" + name="level" + ref="level" + value="OFF_TOPIC"> + {parent._t.level_off_topic_detail} + </label> + <label class="c-field c-field--choice"> + <input type="radio" + name="level" + ref="level" + value="ILLEGAL"> + {parent._t.level_illegal_detail} + </label> + </fieldset> + + <div class="o-form-element"> + <label class="c-label" for="email">{parent._t.email}</label> + <input type="email" + id="email" + ref="email" + placeholder="{parent._t.email_placeholder}" + value={parent.session.getUser() && parent.session.getUser().email} + required + maxlength="255" + class="c-field"/> + </div> + </modal> + + <script type="es6"> + import session from "../../js/Session"; + import poll from "../../js/Poll.js"; + + this.session = session; + this.installBundle(this.session, "report"); + this.poll = poll; + this.levels = ["ILLEGAL", "SPAM", "OFF_TOPIC"]; + this.reports = []; + this.ignoreReports = []; + + this.open = (target) => { + this.target = target; + this.refs.modal.refs.level.forEach(radio => {radio.checked = false;}); + let promise = this.refs.modal.open().then(() => { + this.update(); + }, () => {}); + this.update(); + return promise; + }; + + this.submit = () => { + let report = { + level: this.refs.modal.refs.level.find(radio => radio.checked).value, + email: this.refs.modal.refs.email.value + }; + + return this.poll.addReport(this.target, report); + + }; + + this.listen("openAddReport", this.open); + + </script> + +</AddReportModal> diff --git a/pollen-ui-riot-js/src/main/web/tag/popup/ChoiceDetailModal.tag.html b/pollen-ui-riot-js/src/main/web/tag/popup/ChoiceDetailModal.tag.html new file mode 100644 index 00000000..a5a58728 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/popup/ChoiceDetailModal.tag.html @@ -0,0 +1,84 @@ +<!-- + #%L + Pollen :: UI RiotJs + %% + Copyright (C) 2009 - 2017 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> +import "../components/MultiLineLabel.tag.html"; +<ChoiceDetailModal> + <Modal header={title} + ref="modal" + only-ok="true"> + <div if={parent.choice && parent.choice.choiceType === "RESOURCE" && parent.meta}> + <img if={parent.isImage()} + class="image-preview" + src="{parent.session.configuration.endPoint}/v1/resources/{parent.choice.choiceValue}"/> + <a class="c-button c-button--info" + if={!parent.isImage()} + href="{parent.session.configuration.endPoint}/v1/resources/{parent.choice.choiceValue}/download" + target="_blank"> + <i class="fa fa-download"></i> + {parent._t.download} + </a> + </div> + <MultiLineLabel label="{parent.choice && parent.choice.description}"></MultiLineLabel> + </Modal> + + <script type="es6"> + import session from "../../js/Session"; + import resourceService from "../../js/ResourceService"; + + this.session = session; + this.resourceService = resourceService; + this.installBundle(this.session, "choice"); + + this.open = (choice, meta) => { + this.choice = choice; + this.meta = meta; + if (this.choice.choiceType === "TEXT") { + this.title = this.choice.choiceValue; + } else if (this.choice.choiceType === "DATE") { + this.title = this.formatDate(parseInt(this.choice.choiceValue, 10), "LLL"); + } else if (this.choice.choiceType === "DATETIME") { + this.title = this.formatDate(parseInt(this.choice.choiceValue, 10)); + } else if (this.choice.choiceType === "RESOURCE") { + this.title = this.meta && this.meta.name; + } + let promise = this.refs.modal.open().then(() => { + this.update(); + }, () => {}); + this.update(); + return promise; + }; + + + this.isImage = () => { + return this.meta.contentType.match(/^image\//i); + }; + + this.listen("openChoiceDetail", this.open); + + </script> + + <style> + .modal-image img { + max-width: 100%; + max-height:100%; + } + + </style> +</ChoiceDetailModal> diff --git a/pollen-ui-riot-js/src/main/web/tag/popup/ShowReportsModal.tag.html b/pollen-ui-riot-js/src/main/web/tag/popup/ShowReportsModal.tag.html new file mode 100644 index 00000000..2a2f5100 --- /dev/null +++ b/pollen-ui-riot-js/src/main/web/tag/popup/ShowReportsModal.tag.html @@ -0,0 +1,98 @@ +<!-- + #%L + Pollen :: UI RiotJs + %% + Copyright (C) 2009 - 2017 CodeLutin + %% + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + #L% + --> +import "../popup/Modal.tag.html"; +<ShowReportsModal> + <modal ref="modalReports" header={_t.reports_title} only-ok="true"> + <div each={level in parent.reportLevels}> + <div class="c-card__item c-card__item--{error : level == 'ILLEGAL', warning: level == 'SPAM', info : level == 'OFF_TOPIC'}"> + {parent.parent._t['level_' + level.toLowerCase()]} + </div> + <div class="c-card__item"> + <div each={report in parent.parent.filterReports(level)} class="report {ignore : report.ignore}"> + <div class="report-email"> + {report.email} + </div> + <a onclick={parent.parent.parent.toggleIgnoreReport(report)} title={report.ignore ? parent.parent.parent._t.toEnable : parent.parent.parent._t.toIgnore}> + <i class="fa fa-{report.ignore ? 'bell' : 'bell-slash'}" aria-hidden="true"></i> + </a> + </div> + </div> + </div> + </modal> + + <script type="es6"> + import session from "../../js/Session"; + import poll from "../../js/Poll.js"; + + this.session = session; + this.installBundle(this.session, "report"); + this.poll = poll; + this.levels = ["ILLEGAL", "SPAM", "OFF_TOPIC"]; + this.reports = []; + this.ignoreReports = []; + + this.open = target => { + this.target = target; + this.poll.getReports(this.target).then(reports => { + this.reports = reports; + this.reportLevels = this.levels.filter(level => reports.find(report => report.level === level)); + this.refs.modalReports.open().then(() => { + if (this.poll.isPoll(this.target)) { + this.poll.reloadPoll(); + } else if (this.poll.isComment(this.target)) { + this.poll.loadComments(); + } else if (this.poll.isChoice(this.target)) { + this.poll.loadChoices(); + } + this.update(); + }, () => {}); + this.update(); + }); + }; + + this.toggleIgnoreReport = report => () => { + let report2 = Object.assign({}, report); + report2.ignore = !report2.ignore; + this.poll.saveReport(this.target, report2).then(() => { + this.poll.getReports(this.target).then(reports => { + this.reports = reports; + this.reportLevels = this.levels.filter(level => reports.find(report3 => report3.level === level)); + this.update(); + }); + }); + }; + + this.getReportLevels = () => { + this.levels.filter(level => this.reports.find(report => report.level === level)); + }; + + this.hasReports = level => { + return this.filterReports(level).length > 0; + }; + + this.filterReports = level => { + return this.reports.filter(report => report.level === level); + }; + + this.listen("openReports", this.open); + + </script> +</ShowReportsModal> -- To stop receiving notification emails like this one, please contact chorem.org SCM administrator <admin+scm@chorem.org>.