Using bootstrap 4 and related UI refresh (#205)
Changes at a glance: - Switch from BS3 to BS4 - Polishing of some UI elements (low-hanging fruit for UX) - Mobile-friendly layout.
This commit is contained in:
parent
25271e4019
commit
c23ba1f6fb
42 changed files with 471 additions and 675 deletions
|
@ -2,16 +2,14 @@ import { inject as service } from '@ember/service';
|
|||
import { readOnly, mapBy, filter } from '@ember/object/computed';
|
||||
import Component from '@ember/component';
|
||||
import { isPresent, isEmpty } from '@ember/utils';
|
||||
import { observer, computed, get } from '@ember/object';
|
||||
import { observer, get } from '@ember/object';
|
||||
import {
|
||||
validator, buildValidations
|
||||
}
|
||||
from 'ember-cp-validations';
|
||||
import { raw } from 'ember-awesome-macros';
|
||||
import { groupBy } from 'ember-awesome-macros/array';
|
||||
import { next, scheduleOnce } from '@ember/runloop';
|
||||
import BsForm from 'ember-bootstrap/components/bs-form';
|
||||
import BsFormElement from 'ember-bootstrap/components/bs-form/element';
|
||||
import { next } from '@ember/runloop';
|
||||
|
||||
let modelValidations = buildValidations({
|
||||
dates: [
|
||||
|
@ -101,10 +99,6 @@ export default Component.extend(modelValidations, {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
next(() => {
|
||||
this.notifyPropertyChange('_nestedChildViews');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -153,73 +147,5 @@ export default Component.extend(modelValidations, {
|
|||
|
||||
groupedDates: groupBy('dates', raw('day')),
|
||||
|
||||
childFormElements: computed('_nestedChildViews', function() {
|
||||
let form = this.childViews.find((childView) => {
|
||||
return childView instanceof BsForm;
|
||||
});
|
||||
|
||||
if (!form) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return form.childViews.filter((childView) => {
|
||||
return childView instanceof BsFormElement;
|
||||
});
|
||||
}),
|
||||
|
||||
// Can't use a computed property cause Ember Bootstrap seem to modify validation twice in a single render.
|
||||
// Therefor we use the scheduleOnce trick.
|
||||
// This is the same for {{create-options-text}} component.
|
||||
daysValidationState: null,
|
||||
updateDaysValidationState: observer('childFormElements.@each.validation', function() {
|
||||
scheduleOnce('sync', () => {
|
||||
this.set('daysValidationState',
|
||||
this.childFormElements.reduce(function(daysValidationState, item) {
|
||||
const day = item.get('model.day');
|
||||
const validation = item.get('validation');
|
||||
let currentValidationState;
|
||||
|
||||
// there maybe form elements without model or validation
|
||||
if (isEmpty(day) || validation === undefined) {
|
||||
return daysValidationState;
|
||||
}
|
||||
|
||||
// if it's not existing initialize with current value
|
||||
if (!daysValidationState.hasOwnProperty(day)) {
|
||||
daysValidationState[day] = validation;
|
||||
return daysValidationState;
|
||||
}
|
||||
|
||||
currentValidationState = daysValidationState[day];
|
||||
switch (currentValidationState) {
|
||||
// error overrules all validation states
|
||||
case 'error':
|
||||
break;
|
||||
|
||||
// null ist overruled by 'error'
|
||||
case null:
|
||||
if (validation === 'error') {
|
||||
daysValidationState[day] = 'error';
|
||||
}
|
||||
break;
|
||||
|
||||
// success is overruled by anyother validation state
|
||||
case 'success':
|
||||
daysValidationState[day] = validation;
|
||||
break;
|
||||
}
|
||||
|
||||
return daysValidationState;
|
||||
}, {})
|
||||
);
|
||||
});
|
||||
}).on('init'),
|
||||
|
||||
store: service(),
|
||||
|
||||
didInsertElement() {
|
||||
// childViews is not observeable by default. Need to notify about a change manually.
|
||||
// Lucky enough we know that child views will only be changed on init and if times are added / removed.
|
||||
this.notifyPropertyChange('_nestedChildViews');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,9 +1,6 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import { observer, computed, get } from '@ember/object';
|
||||
import { run, next } from '@ember/runloop';
|
||||
import BsFormElement from 'ember-bootstrap/components/bs-form/element';
|
||||
import { any } from 'ember-awesome-macros/array';
|
||||
import { observer } from '@ember/object';
|
||||
|
||||
export default Component.extend({
|
||||
actions: {
|
||||
|
@ -15,76 +12,13 @@ export default Component.extend({
|
|||
position,
|
||||
fragment
|
||||
);
|
||||
|
||||
next(() => {
|
||||
this.notifyPropertyChange('_childViews');
|
||||
});
|
||||
},
|
||||
deleteOption(element) {
|
||||
let position = this.options.indexOf(element);
|
||||
this.options.removeAt(position);
|
||||
|
||||
next(() => {
|
||||
this.notifyPropertyChange('_childViews');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
anyElementHasFeedback: any('childFormElements.@each.hasFeedback', function(childFormElement) {
|
||||
return get(childFormElement, 'hasFeedback');
|
||||
}),
|
||||
|
||||
anyElementIsInvalid: any('childFormElements.@each.validation', function(childFormElement) {
|
||||
return get(childFormElement, 'validation') === 'error';
|
||||
}),
|
||||
|
||||
everyElementIsValid: computed('childFormElements.@each.validation', function() {
|
||||
const anyElementIsInvalid = this.anyElementIsInvalid;
|
||||
if (anyElementIsInvalid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// childFormElements contains button wrapper element which should not be taken into account here
|
||||
const childFormElements = this.childFormElements.filterBy('hasValidator');
|
||||
if (childFormElements) {
|
||||
return childFormElements.every((childFormElement) => {
|
||||
return childFormElement.get('hasFeedback') && childFormElement.get('validation') === 'success';
|
||||
});
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
childFormElements: computed('_childViews', function() {
|
||||
return this.childViews.filter((childView) => {
|
||||
return childView instanceof BsFormElement;
|
||||
});
|
||||
}),
|
||||
|
||||
// Can't use a computed property cause Ember Bootstrap seem to modify validation twice in a single render.
|
||||
// Therefor we use the scheduleOnce trick.
|
||||
// This is the same for {{create-options-datetime}} component.
|
||||
labelValidationClass: 'label-has-no-validation',
|
||||
updateLabelValidationClass: observer('anyElementHasFeedback', 'anyElementIsInvalid', 'everyElementIsValid', function() {
|
||||
run.scheduleOnce('sync', () => {
|
||||
let validationClass;
|
||||
|
||||
if (!this.anyElementHasFeedback) {
|
||||
validationClass = 'label-has-no-validation';
|
||||
} else if (this.anyElementIsInvalid) {
|
||||
validationClass = 'label-has-error';
|
||||
} else if (this.everyElementIsValid) {
|
||||
validationClass = 'label-has-success';
|
||||
} else {
|
||||
validationClass = 'label-has-no-validation';
|
||||
}
|
||||
|
||||
this.set('labelValidationClass', validationClass);
|
||||
});
|
||||
}).on('init'),
|
||||
|
||||
classNameBindings: ['labelValidationClass'],
|
||||
|
||||
enforceMinimalOptionsAmount: observer('options', 'isMakeAPoll', function() {
|
||||
if (this.get('options.length') < 2) {
|
||||
let options = this.options;
|
||||
|
@ -97,11 +31,4 @@ export default Component.extend({
|
|||
}).on('init'),
|
||||
|
||||
store: service('store'),
|
||||
|
||||
didInsertElement() {
|
||||
// childViews is not observeable by default. Need to notify about a change manually.
|
||||
// Lucky enough we know that child views will only be changed on init and if times are added / removed.
|
||||
// Use a custom variable to avoid any complications with framework.
|
||||
this.notifyPropertyChange('_childViews');
|
||||
}
|
||||
});
|
||||
|
|
|
@ -41,9 +41,9 @@ export default Component.extend({
|
|||
let renderPrevButton = this.renderPrevButton;
|
||||
|
||||
if (renderPrevButton) {
|
||||
return ['col-xs-6', 'col-md-8'];
|
||||
return ['col-6', 'col-md-8'];
|
||||
} else {
|
||||
return ['col-md-8', 'col-md-offset-4'];
|
||||
return ['col-md-8', 'offset-md-4'];
|
||||
}
|
||||
}),
|
||||
|
||||
|
|
|
@ -24,18 +24,18 @@
|
|||
{{content-for "body-footer"}}
|
||||
|
||||
<noscript>
|
||||
<div class="container-fluid">
|
||||
<div id="header">
|
||||
<h1 class="logo">Croodle</h1>
|
||||
<nav class="cr-navbar navbar navbar-dark">
|
||||
<h1 class="cr-logo">Croodle</h1>
|
||||
</nav>
|
||||
|
||||
<main role="main" class="container cr-main">
|
||||
<div class="alert alert-danger" role="alert">
|
||||
<h2>Please enable JavaScript!</h2>
|
||||
<p>JavaScript is currently disabled in your browser settings or your browser does not support JavaScript.</p>
|
||||
<p>Croodle requires JavaScript. Therefore to use Croodle, you must first enable JavaScript.</p>
|
||||
<a class="btn btn-outline-dark btn-lg" href="http://www.enable-javascript.com/">Click here for instructions on how to enable JavaScript.</a>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div class="box">
|
||||
<h1>Please enable JavaScript</h1>
|
||||
<p>Croodle requires JavaScript. But JavaScript is currently disabled in your browser settings or your browser does not support JavaScript. Therefore you can't use Croodle.</p>
|
||||
<h2><a href="http://www.enable-javascript.com/">Click here for instructions on how to enable JavaScript.</a></h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</noscript>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -98,6 +98,7 @@ export default {
|
|||
'poll.input.newUserName.label': 'Nom',
|
||||
'poll.input.newUserName.placeholder': 'El teu nom',
|
||||
'poll.link.copied': 'Enllaç copiat al porta-retalls.',
|
||||
'poll.link.copy-label': 'Copia l\'enllaç al porta-retalls',
|
||||
'poll.link.selected': 'Enllaç seleccionat. Premeu Command + C per copiar.',
|
||||
'poll.modal.timezoneDiffers.title': 'En quines zones horàries s\'han de presentar les dates?',
|
||||
'poll.modal.timezoneDiffers.body': 'L\'enquesta es va crear per a una zona horària diferent de la vostra hora local. En quines zones horàries s\'han de presentar les dates?',
|
||||
|
|
|
@ -98,6 +98,7 @@ export default {
|
|||
'poll.input.newUserName.label': 'Name',
|
||||
'poll.input.newUserName.placeholder': 'Dein Name',
|
||||
'poll.link.copied': 'Link in die Zwischenablage kopiert.',
|
||||
'poll.link.copy-label': 'Kopiere Link in die Zwischenablage',
|
||||
'poll.link.selected': 'Link markiert. Drücke Steuerung + C zum Kopieren.',
|
||||
'poll.modal.timezoneDiffers.title': 'In welcher Zeitzone sollen die Daten angezeigt werden?',
|
||||
'poll.modal.timezoneDiffers.body': 'Die Umfrage wurde für eine Zeitzone angelegt, die von deiner lokalen Zeit abweicht. In welcher Zeitzone sollen die Daten angezeigt werden?',
|
||||
|
|
|
@ -98,6 +98,7 @@ export default {
|
|||
'poll.input.newUserName.label': 'Name',
|
||||
'poll.input.newUserName.placeholder': 'Your Name',
|
||||
'poll.link.copied': 'Link copied to clipboard.',
|
||||
'poll.link.copy-label': 'Copy link to clipboard',
|
||||
'poll.link.selected': 'Link selected. Press Command+C to copy.',
|
||||
'poll.modal.timezoneDiffers.title': 'In which time zones should the dates be presented?',
|
||||
'poll.modal.timezoneDiffers.body': 'The poll was created for a time zone which differs from your local time. In which time zones should the dates be presented?',
|
||||
|
|
|
@ -98,6 +98,7 @@ export default {
|
|||
'poll.input.newUserName.label': 'Nombre',
|
||||
'poll.input.newUserName.placeholder': 'Tu nombre',
|
||||
'poll.link.copied': 'Enlace copiado al portapapeles.',
|
||||
'poll.link.copy-label': 'Copiar enlace al portapapeles',
|
||||
'poll.link.selected': 'Enlace seleccionado. Presiona Comando+C para copiarlo.',
|
||||
'poll.modal.timezoneDiffers.title': '¿Que zona horaria deseas utilizar para mostrar los datos?',
|
||||
'poll.modal.timezoneDiffers.body': 'La encuesta ha sido configurada para una zona horaria distinta de tu hora local. ¿Con qué zona horaria debería mostrarse la información?',
|
||||
|
|
|
@ -98,6 +98,7 @@ export default {
|
|||
'poll.input.newUserName.label': 'Nome',
|
||||
'poll.input.newUserName.placeholder': 'Il tuo nome',
|
||||
'poll.link.copied': 'Il link è stato copiato.',
|
||||
'poll.link.copy-label': 'Copia il link negli appunti',
|
||||
'poll.link.selected': 'Link selezionato. Preme Command+C per copiarlo.',
|
||||
'poll.modal.timezoneDiffers.title': 'In quale fuso orario devono essere presentate le date?',
|
||||
'poll.modal.timezoneDiffers.body': 'Il sondaggio è stato creato per un fuso orario diverso dal tuo. In quali orari devono essere presentate le date?',
|
||||
|
|
24
app/styles/_bootstrap-tweaking.scss
Normal file
24
app/styles/_bootstrap-tweaking.scss
Normal file
|
@ -0,0 +1,24 @@
|
|||
// We don't want the extra space added to jumbotron at larger screen sizes.
|
||||
.jumbotron {
|
||||
@include media-breakpoint-up(sm) {
|
||||
padding: $jumbotron-padding $jumbotron-padding;
|
||||
}
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.tab-pane {
|
||||
padding-top: map-get($spacers, 4);
|
||||
}
|
||||
|
||||
.navbar-dark .custom-select {
|
||||
background-color: gray("900");
|
||||
color: gray("500");
|
||||
border: $custom-select-border-width solid gray("500");
|
||||
}
|
||||
|
||||
h3, .h3 {
|
||||
margin-top: map-get($spacers, 5);
|
||||
}
|
|
@ -1,35 +1,20 @@
|
|||
@import "ember-power-calendar";
|
||||
|
||||
@media only screen and (max-width: $screen-sm-min) {
|
||||
.ember-power-calendar {
|
||||
@include ember-power-calendar($cell-size: 63px);
|
||||
}
|
||||
.ember-power-calendar {
|
||||
@include ember-power-calendar(30px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $screen-sm-min) {
|
||||
.ember-power-calendar {
|
||||
@include ember-power-calendar($cell-size: 47px);
|
||||
|
||||
float:left;
|
||||
|
||||
&:first-child {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.ember-power-calendar ~ .help-block {
|
||||
clear: both;
|
||||
}
|
||||
.ember-power-calendar .ember-power-calendar-day,
|
||||
.ember-power-calendar .ember-power-calendar-weekday {
|
||||
max-width: none;
|
||||
width: auto;
|
||||
max-height: none;
|
||||
height: auto;
|
||||
margin: 1px;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $screen-md-min) {
|
||||
.ember-power-calendar {
|
||||
@include ember-power-calendar($cell-size: 40px);
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (min-width: $screen-lg-min) {
|
||||
.ember-power-calendar {
|
||||
@include ember-power-calendar($cell-size: 50px);
|
||||
}
|
||||
.ember-power-calendar .ember-power-calendar-weekdays,
|
||||
.ember-power-calendar .ember-power-calendar-week {
|
||||
height: 3em;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
|
|
26
app/styles/_generic-croodle-styles.scss
Normal file
26
app/styles/_generic-croodle-styles.scss
Normal file
|
@ -0,0 +1,26 @@
|
|||
.cr-navbar {
|
||||
background: map-get($theme-colors, dark);
|
||||
}
|
||||
|
||||
.cr-logo {
|
||||
font-size: $font-size-base;
|
||||
margin-bottom: 0;
|
||||
// This is needed for cases when the h1 does not contain a link, like when JS
|
||||
// is disabled.
|
||||
color: $body-bg;
|
||||
}
|
||||
|
||||
.cr-main {
|
||||
padding-top: map-get($spacers, 5);
|
||||
}
|
||||
|
||||
.cr-claim {
|
||||
margin-bottom: map-get($spacers, 4);
|
||||
}
|
||||
|
||||
.cr-hide-on-mobile {
|
||||
display: none;
|
||||
@include media-breakpoint-up(md) {
|
||||
display: block;
|
||||
}
|
||||
}
|
7
app/styles/_option-menu.scss
Normal file
7
app/styles/_option-menu.scss
Normal file
|
@ -0,0 +1,7 @@
|
|||
.cr-option-menu {
|
||||
margin-top: map-get($spacers, 2);
|
||||
|
||||
&__button {
|
||||
margin-right: map-get($spacers, 2);
|
||||
}
|
||||
}
|
18
app/styles/_poll-link.scss
Normal file
18
app/styles/_poll-link.scss
Normal file
|
@ -0,0 +1,18 @@
|
|||
.cr-poll-link {
|
||||
&__link {
|
||||
position: relative;
|
||||
word-wrap: break-word;
|
||||
background: gray("200");
|
||||
padding: map-get($spacers, 3) map-get($spacers, 3) 3em map-get($spacers, 3);
|
||||
}
|
||||
|
||||
&__url {
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
&__copy-btn {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
98
app/styles/_steps.scss
Normal file
98
app/styles/_steps.scss
Normal file
|
@ -0,0 +1,98 @@
|
|||
$steps-border-width: 5px;
|
||||
|
||||
.cr-steps-top-nav {
|
||||
margin-bottom: map-get($spacers, 5);
|
||||
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
flex-direction: row;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
&__button {
|
||||
text-align: left;
|
||||
border-width: 0;
|
||||
border-left-width: $steps-border-width;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
text-align: inherit;
|
||||
border-left: 0;
|
||||
border-right: 0;
|
||||
}
|
||||
&:not(:disabled) {
|
||||
border-left-color: gray("300");
|
||||
@include media-breakpoint-up(md) {
|
||||
border-bottom: $steps-border-width solid gray("300");
|
||||
}
|
||||
}
|
||||
&.is-active {
|
||||
border-left-color: theme-color-level("primary", 2);
|
||||
@include media-breakpoint-up(md) {
|
||||
border-bottom: $steps-border-width solid theme-color-level("primary", 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$bottom-nav-height: 5.5em;
|
||||
|
||||
.cr-form-wrapper {
|
||||
padding-bottom: $bottom-nav-height;
|
||||
@include media-breakpoint-up(md) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cr-steps-bottom-nav {
|
||||
margin-top: map-get($spacers, 4);
|
||||
padding-top: map-get($spacers, 4);
|
||||
padding-bottom: map-get($spacers, 4);
|
||||
border-top: 2px solid gray("100");
|
||||
background: $body-bg;
|
||||
height: $bottom-nav-height;
|
||||
z-index: 9;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: $grid-gutter-width / 2;
|
||||
right: $grid-gutter-width / 2;
|
||||
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-top: map-get($spacers, 5);
|
||||
padding-top: map-get($spacers, 5);
|
||||
padding-bottom: map-get($spacers, 5);
|
||||
background: transparent;
|
||||
position: static;
|
||||
bottom: auto;
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&__button {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
@include media-breakpoint-up(md) {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
&__prev-button .cr-steps-bottom-nav__label {
|
||||
margin-left: map-get($spacers, 3);
|
||||
}
|
||||
&__next-button .cr-steps-bottom-nav__label {
|
||||
margin-right: map-get($spacers, 3);
|
||||
}
|
||||
|
||||
&__next-button {
|
||||
justify-content: flex-end;
|
||||
@include media-breakpoint-up(md) {
|
||||
justify-content: normal;
|
||||
}
|
||||
}
|
||||
}
|
9
app/styles/_variable-overrides.scss
Normal file
9
app/styles/_variable-overrides.scss
Normal file
|
@ -0,0 +1,9 @@
|
|||
$icon-font-path: '../open-iconic/font/fonts/';
|
||||
|
||||
$enable-rounded: false;
|
||||
|
||||
$font-family-base: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Oxygen-Sans,Ubuntu,Cantarell,"Helvetica Neue",sans-serif;
|
||||
|
||||
$jumbotron-padding: 2rem;
|
||||
|
||||
$btn-disabled-opacity: 0.45;
|
|
@ -1,192 +1,45 @@
|
|||
@import "ember-bootstrap/bootstrap";
|
||||
@import "calendar";
|
||||
@import "participants-table";
|
||||
// Required
|
||||
@import "ember-bootstrap/functions";
|
||||
@import "ember-bootstrap/variables";
|
||||
@import "ember-bootstrap/mixins";
|
||||
|
||||
table tr td .form-group {
|
||||
margin-bottom:0;
|
||||
}
|
||||
table tr td .help-block {
|
||||
margin-top:0;
|
||||
margin-bottom:0;
|
||||
}
|
||||
table tr td .form-control {
|
||||
min-width:6em;
|
||||
}
|
||||
body {
|
||||
background-color:#d3d3d3;
|
||||
}
|
||||
#header h1.logo {
|
||||
float: left;
|
||||
}
|
||||
#header .language-select {
|
||||
float: right;
|
||||
margin-top: 20px;
|
||||
width: auto;
|
||||
}
|
||||
#content {
|
||||
clear: both;
|
||||
}
|
||||
.box {
|
||||
padding:10px;
|
||||
margin-bottom:10px;
|
||||
background-color:#ffffff;
|
||||
border:#556b2f 1px solid;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
h2:first-child, h3:first-child {
|
||||
margin-top:0;
|
||||
}
|
||||
}
|
||||
#poll .participation {
|
||||
.radio.yes {
|
||||
color: green;
|
||||
}
|
||||
.radio.no {
|
||||
color: red;
|
||||
}
|
||||
.radio.maybe {
|
||||
color: orange;
|
||||
}
|
||||
}
|
||||
#poll {
|
||||
tbody td .yes, tfoot td option[value="yes"] {
|
||||
color:green;
|
||||
}
|
||||
tbody td .no, tfoot td option[value="no"] {
|
||||
color:red;
|
||||
}
|
||||
tbody td .maybe, tfoot td option[value="maybe"] {
|
||||
color:orange;
|
||||
}
|
||||
.index {
|
||||
.start, .have-a-try {
|
||||
font-size:150%;
|
||||
}
|
||||
}
|
||||
.meta-data .description {
|
||||
white-space:pre-wrap;
|
||||
}
|
||||
.meta-data .dates {
|
||||
color:grey;
|
||||
}
|
||||
.poll-link .link {
|
||||
color:#556b2f;
|
||||
word-break:break-all;
|
||||
// Overriding bootstrap variables
|
||||
@import "./variable-overrides";
|
||||
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -ms-flexbox;
|
||||
display: -webkit-flex;
|
||||
display: flex;
|
||||
}
|
||||
.poll-link .link button {
|
||||
-webkit-align-items: stretch;
|
||||
-moz-align-items: stretch;
|
||||
-ms-align-items: stretch;
|
||||
align-items: stretch;
|
||||
// Optional - Bootstrapping/Resetting
|
||||
@import "ember-bootstrap/root";
|
||||
@import "ember-bootstrap/print";
|
||||
@import "ember-bootstrap/reboot";
|
||||
|
||||
margin-left: 0.5em;
|
||||
}
|
||||
.poll-link .link button span {
|
||||
font-size: 1.6em;
|
||||
}
|
||||
.poll-link .notice {
|
||||
font-size:90%;
|
||||
color:grey;
|
||||
}
|
||||
.table-scroll {
|
||||
width:100%;
|
||||
margin-bottom:15px;
|
||||
overflow-y:hidden;
|
||||
overflow-x:scroll;
|
||||
-ms-overflow-style:-ms-autohiding-scrollbar;
|
||||
border:1px solid #dddddd;
|
||||
-webkit-overflow-scrolling:touch;
|
||||
}
|
||||
.evaluation-header {
|
||||
font-weight:bold;
|
||||
}
|
||||
}
|
||||
#loading {
|
||||
letter-spacing:5px;
|
||||
}
|
||||
.loading-animation-container {
|
||||
position:relative;
|
||||
display:inline-block;
|
||||
width:100%;
|
||||
}
|
||||
.loading-animation-container:before {
|
||||
content:'';
|
||||
display:block;
|
||||
margin-top:12%;
|
||||
}
|
||||
.loading-animation-circle {
|
||||
position:absolute;
|
||||
top:0;
|
||||
bottom:0;
|
||||
width:12%;
|
||||
animation-name:bounce-loading-animation-circle;
|
||||
animation-duration:1.3s;
|
||||
animation-iteration-count:infinite;
|
||||
animation-direction:linear;
|
||||
transform:scale(.3);
|
||||
border-radius:100%;
|
||||
}
|
||||
#loading-animation-circle-1 {
|
||||
left:0;
|
||||
animation-delay:0.52s;
|
||||
}
|
||||
#loading-animation-circle-2 {
|
||||
left:12.5%;
|
||||
animation-delay:0.65s;
|
||||
}
|
||||
#loading-animation-circle-3 {
|
||||
left:25%;
|
||||
animation-delay:0.78s;
|
||||
}
|
||||
#loading-animation-circle-4 {
|
||||
left:37.5%;
|
||||
animation-delay:0.91s;
|
||||
}
|
||||
#loading-animation-circle-5 {
|
||||
left:50%;
|
||||
animation-delay:1.04s;
|
||||
}
|
||||
#loading-animation-circle-6 {
|
||||
left:62.5%;
|
||||
animation-delay:1.17s;
|
||||
}
|
||||
#loading-animation-circle-7 {
|
||||
left:75%;
|
||||
animation-delay:1.3s;
|
||||
}
|
||||
#loading-animation-circle-8 {
|
||||
left:87.5%;
|
||||
animation-delay:1.43s;
|
||||
}
|
||||
#loading-animation-circle-9 {
|
||||
left:100%;
|
||||
animation-delay:1.43s;
|
||||
}
|
||||
// Open Iconic icon font
|
||||
@import "open-iconic/font/css/open-iconic-bootstrap.scss";
|
||||
|
||||
ul.nav-tabs {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
// Optional - Everything else
|
||||
@import "ember-bootstrap/utilities/screenreaders";
|
||||
@import "ember-bootstrap/type";
|
||||
@import "ember-bootstrap/tables";
|
||||
@import "ember-bootstrap/nav";
|
||||
@import "ember-bootstrap/navbar";
|
||||
@import "ember-bootstrap/grid";
|
||||
@import "ember-bootstrap/buttons";
|
||||
@import "ember-bootstrap/jumbotron";
|
||||
@import "ember-bootstrap/alert";
|
||||
@import "ember-bootstrap/button-group";
|
||||
@import "ember-bootstrap/forms";
|
||||
@import "ember-bootstrap/modal";
|
||||
@import "ember-bootstrap/input-group";
|
||||
@import "ember-bootstrap/custom-forms";
|
||||
|
||||
/*
|
||||
* Override label validation state
|
||||
*/
|
||||
.label-has-error .control-label {
|
||||
color: $state-danger-text;
|
||||
}
|
||||
.label-has-success .control-label {
|
||||
color: $state-success-text;
|
||||
}
|
||||
.label-has-no-validation .control-label {
|
||||
color: inherit;
|
||||
}
|
||||
// Overriding bootstrap selectors with properties we cannot influence by
|
||||
// changing variables.
|
||||
@import "./bootstrap-tweaking";
|
||||
|
||||
.form-steps {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
// Finally adding our own custom styles
|
||||
@import "./generic-croodle-styles";
|
||||
|
||||
// Component specific styles
|
||||
@import "./calendar";
|
||||
@import "./steps";
|
||||
@import "./participants-table";
|
||||
@import "./option-menu";
|
||||
@import "./poll-link";
|
||||
|
|
|
@ -3,4 +3,4 @@
|
|||
<p>
|
||||
The poll with you url could not be found. Perhaps it got deleted?
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,24 +2,25 @@
|
|||
|
||||
{{title "Croodle"}}
|
||||
|
||||
<div class="container">
|
||||
<div id="header">
|
||||
<h1 class="logo">{{#link-to "index"}}Croodle{{/link-to}}</h1>
|
||||
<form class="form-inline">
|
||||
{{language-select class="form-control"}}
|
||||
<nav class="cr-navbar navbar navbar-dark">
|
||||
<h1 class="cr-logo">{{#link-to "index" class="navbar-brand"}}Croodle{{/link-to}}</h1>
|
||||
<div class="collapse" id="headerNavbar">
|
||||
<form class="form-inline my-2 my-lg-0">
|
||||
{{language-select class="custom-select custom-select-sm"}}
|
||||
</form>
|
||||
</div>
|
||||
<div id="content">
|
||||
<div id="messages">
|
||||
{{#each flashMessages.queue as |flash|}}
|
||||
{{#flash-message flash=flash}}
|
||||
{{t flash.message}}
|
||||
{{/flash-message}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
{{outlet}}
|
||||
<main role="main" class="container cr-main">
|
||||
<div id="messages">
|
||||
{{#each flashMessages.queue as |flash|}}
|
||||
{{#flash-message flash=flash}}
|
||||
{{t flash.message}}
|
||||
{{/flash-message}}
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{outlet}}
|
||||
</main>
|
||||
|
||||
{{outlet "modal"}}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{{yield}}
|
||||
{{yield}}
|
||||
|
|
|
@ -3,16 +3,22 @@
|
|||
property="options"
|
||||
data-test-form-element-for="days"
|
||||
}}
|
||||
<InlineDatepicker
|
||||
@center={{calendarCenter}}
|
||||
@selectedDays={{selectedDays}}
|
||||
@onCenterChange={{action (mut calendarCenter) value="moment"}}
|
||||
@onSelect={{action "daysSelected"}}
|
||||
/>
|
||||
<InlineDatepicker
|
||||
@center={{calendarCenterNext}}
|
||||
@selectedDays={{selectedDays}}
|
||||
@onCenterChange={{action (mut calendarCenter) value="moment"}}
|
||||
@onSelect={{action "daysSelected"}}
|
||||
/>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<InlineDatepicker
|
||||
@center={{calendarCenter}}
|
||||
@selectedDays={{selectedDays}}
|
||||
@onCenterChange={{action (mut calendarCenter) value="moment"}}
|
||||
@onSelect={{action "daysSelected"}}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-md-6 cr-hide-on-mobile">
|
||||
<InlineDatepicker
|
||||
@center={{calendarCenterNext}}
|
||||
@selectedDays={{selectedDays}}
|
||||
@onCenterChange={{action (mut calendarCenter) value="moment"}}
|
||||
@onSelect={{action "daysSelected"}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{/form.element}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="box">
|
||||
<div class="cr-form-wrapper box">
|
||||
{{#if errorMessage}}
|
||||
{{#bs-alert type="warning"}}
|
||||
{{t errorMessage}}
|
||||
|
@ -41,7 +41,7 @@
|
|||
as |el|
|
||||
}}
|
||||
<div class="input-group">
|
||||
{{bs-form/element/control/input
|
||||
{{el.control
|
||||
autofocus=(unless index true false)
|
||||
id=el.id
|
||||
placeholder="00:00"
|
||||
|
@ -49,42 +49,28 @@
|
|||
value=el.value
|
||||
onChange=(action (mut el.value))
|
||||
}}
|
||||
<div class="input-group-btn">
|
||||
<div class="input-group-append">
|
||||
{{! disable delete button if there is only one option }}
|
||||
{{#bs-button
|
||||
onClick=(action "deleteOption" date)
|
||||
type=(if
|
||||
(eq el.validation "success")
|
||||
"btn-success"
|
||||
(if
|
||||
(eq el.validation "error")
|
||||
"btn-danger"
|
||||
"btn-default"
|
||||
)
|
||||
)
|
||||
type="link"
|
||||
class="delete"
|
||||
disabled=(lte dates.length 1)
|
||||
}}
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="oi oi-trash" title={{t "create.options.button.delete.label"}} aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.delete.label"}}</span>
|
||||
{{/bs-button}}
|
||||
{{#bs-button
|
||||
onClick=(action "addOption" date)
|
||||
type=(if
|
||||
(eq el.validation "success")
|
||||
"btn-success"
|
||||
(if
|
||||
(eq el.validation "error")
|
||||
"btn-danger"
|
||||
"btn-default"
|
||||
)
|
||||
)
|
||||
class="add"
|
||||
}}
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
||||
{{/bs-button}}
|
||||
</div>
|
||||
</div>
|
||||
{{#bs-button
|
||||
onClick=(action "addOption" date)
|
||||
type="link"
|
||||
size="sm"
|
||||
class="add cr-option-menu__button cr-option-menu__add-button"
|
||||
}}
|
||||
<span class="oi oi-plus" title={{t "create.options.button.add.label"}} aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
||||
{{/bs-button}}
|
||||
{{/form.element}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
@ -95,6 +81,7 @@
|
|||
{{#bs-button
|
||||
onClick=(action "adoptTimesOfFirstDay")
|
||||
class="adopt-times-of-first-day"
|
||||
size="sm"
|
||||
}}
|
||||
{{t "create.options-datetime.copy-first-line"}}
|
||||
{{/bs-button}}
|
||||
|
|
|
@ -8,48 +8,33 @@
|
|||
as |el|
|
||||
}}
|
||||
<div class="input-group">
|
||||
{{bs-form/element/control/input
|
||||
{{el.control
|
||||
autofocus=(unless index true false)
|
||||
id=el.id
|
||||
value=el.value
|
||||
onChange=(action (mut el.value))
|
||||
}}
|
||||
<div class="input-group-btn">
|
||||
<div class="input-group-append">
|
||||
{{! disable delete button if there is only one option }}
|
||||
{{#bs-button
|
||||
onClick=(action "deleteOption" option)
|
||||
type=(if
|
||||
(eq el.validation "success")
|
||||
"btn-success"
|
||||
(if
|
||||
(eq el.validation "error")
|
||||
"btn-danger"
|
||||
"btn-default"
|
||||
)
|
||||
)
|
||||
disabled=(lte options.length 1)
|
||||
type="link"
|
||||
class="delete"
|
||||
disabled=(lte options.length 1)
|
||||
}}
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="oi oi-trash" title={{t "create.options.button.delete.label"}} aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.delete.label"}}</span>
|
||||
{{/bs-button}}
|
||||
{{#bs-button
|
||||
onClick=(action "addOption" option)
|
||||
type=(if
|
||||
(eq el.validation "success")
|
||||
"btn-success"
|
||||
(if
|
||||
(eq el.validation "error")
|
||||
"btn-danger"
|
||||
"btn-default"
|
||||
)
|
||||
)
|
||||
class="add"
|
||||
}}
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
||||
{{/bs-button}}
|
||||
</div>
|
||||
</div>
|
||||
{{#bs-button
|
||||
onClick=(action "addOption" option)
|
||||
type="link"
|
||||
size="sm"
|
||||
class="add"
|
||||
}}
|
||||
<span class="oi oi-plus" title={{t "create.options.button.add.label"}} aria-hidden="true"></span>
|
||||
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
|
||||
{{/bs-button}}
|
||||
{{/form.element}}
|
||||
{{/each}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="box">
|
||||
<div class="cr-form-wrapper box">
|
||||
{{#bs-form
|
||||
action="submit"
|
||||
formLayout="horizontal"
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
<div class="row">
|
||||
<div class="row cr-steps-bottom-nav">
|
||||
{{#if renderPrevButton}}
|
||||
<div class="col-xs-6 col-md-4 text-right">
|
||||
{{bs-button
|
||||
<div class="col-6 col-md-4 text-right">
|
||||
{{#bs-button
|
||||
onClick=(action "prev")
|
||||
classNames="prev"
|
||||
classNames="cr-steps-bottom-nav__button cr-steps-bottom-nav__prev-button prev"
|
||||
disabled=disablePrevButton
|
||||
defaultText=prevButtonText
|
||||
}}
|
||||
<span class="cr-steps-bottom-nav__icon oi oi-caret-left" title={{ prevButtonText }} aria-hidden="true"></span>
|
||||
<span class="cr-steps-bottom-nav__label">
|
||||
{{ prevButtonText }}
|
||||
</span>
|
||||
{{/bs-button}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if renderNextButton}}
|
||||
<div class={{nextButtonClassesString}}>
|
||||
{{bs-button
|
||||
{{#bs-button
|
||||
buttonType="submit"
|
||||
classNames="next"
|
||||
defaultText=nextButtonText
|
||||
classNames="cr-steps-bottom-nav__button cr-steps-bottom-nav__next-button next"
|
||||
disabled=disableNextButton
|
||||
type="primary"
|
||||
}}
|
||||
<span class="cr-steps-bottom-nav__label">
|
||||
{{ nextButtonText }}
|
||||
</span>
|
||||
<span class="cr-steps-bottom-nav__icon oi oi-caret-right" title={{ nextButtonText }} aria-hidden="true"></span>
|
||||
{{/bs-button}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
{{title (t "create.title")}}
|
||||
|
||||
{{#bs-button-group justified=true classNames="form-steps"}}
|
||||
{{#bs-button-group justified=true classNames="cr-steps-top-nav form-steps"}}
|
||||
{{#each formSteps as |formStep|}}
|
||||
{{#unless formStep.hidden}}
|
||||
<div class="btn-group" role="group">
|
||||
{{#bs-button
|
||||
onClick=(transition-to formStep.route)
|
||||
type=(if formStep.active "primary" "default")
|
||||
disabled=formStep.disabled
|
||||
}}
|
||||
{{t formStep.label}}
|
||||
{{/bs-button}}
|
||||
</div>
|
||||
{{#bs-button
|
||||
onClick=(transition-to formStep.route)
|
||||
type=(if formStep.active "primary is-active" "default")
|
||||
disabled=formStep.disabled
|
||||
classNames="cr-steps-top-nav__button"
|
||||
}}
|
||||
{{t formStep.label}}
|
||||
{{/bs-button}}
|
||||
{{/unless}}
|
||||
{{/each}}
|
||||
{{/bs-button-group}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="box">
|
||||
<div class="cr-form-wrapper box">
|
||||
{{#bs-form
|
||||
formLayout="horizontal"
|
||||
model=this
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="box">
|
||||
<div class="cr-form-wrapper box">
|
||||
{{#bs-form
|
||||
formLayout="horizontal"
|
||||
model=this
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="box">
|
||||
<div class="cr-form-wrapper box">
|
||||
{{#bs-form
|
||||
formLayout="horizontal"
|
||||
model=this
|
||||
|
@ -18,7 +18,7 @@
|
|||
tagName="select"
|
||||
change=(action "updateAnswerType" value="target.value")
|
||||
id=el.id
|
||||
class="form-control"
|
||||
class="custom-select"
|
||||
}}
|
||||
{{#each answerTypes as |answerType|}}
|
||||
<option value={{answerType.id}} selected={{eq el.value answerType.id}}>
|
||||
|
@ -33,12 +33,13 @@
|
|||
property="expirationDuration"
|
||||
showValidationOn=(array "change" "focusOut")
|
||||
useIcons=false
|
||||
controlType="select"
|
||||
as |el|
|
||||
}}
|
||||
<select
|
||||
id={{el.id}}
|
||||
onchange={{action (mut el.value) value="target.value"}}
|
||||
class="form-control"
|
||||
class="custom-select"
|
||||
>
|
||||
{{#each expirationDurations as |duration|}}
|
||||
<option value={{duration.id}} selected={{eq el.value duration.id}}>
|
||||
|
|
|
@ -1,48 +1,39 @@
|
|||
<div class="index">
|
||||
<div class="index cr-screen-index">
|
||||
|
||||
<div class="box teaser">
|
||||
<h2>
|
||||
<div class="jumbotron">
|
||||
<h2 class="cr-claim">
|
||||
{{t "index.title"}}
|
||||
</h2>
|
||||
{{#link-to "create" class="btn btn-primary btn-lg"}}{{t "index.link.have-a-try"}}{{/link-to}}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
|
||||
<div class="box features">
|
||||
<h3>{{t "index.features.title"}}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
{{t "index.features.list.overview"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.options"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.answers"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.evaluation"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.privacy"}}
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<p class="have-a-try">
|
||||
<span class="glyphicon glyphicon-share-alt"></span>
|
||||
{{#link-to "create"}}{{t "index.link.have-a-try"}}{{/link-to}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<h3>{{t "index.features.title"}}</h3>
|
||||
<ul>
|
||||
<li>
|
||||
{{t "index.features.list.overview"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.options"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.answers"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.evaluation"}}
|
||||
</li>
|
||||
<li>
|
||||
{{t "index.features.list.privacy"}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<div class="box features">
|
||||
<h3>{{t "index.hoster.title"}}</h3>
|
||||
<p>
|
||||
{{t "index.hoster.text"}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-lg-5 offset-lg-1">
|
||||
<h3>{{t "index.hoster.title"}}</h3>
|
||||
<p>
|
||||
{{t "index.hoster.text"}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -29,4 +29,4 @@
|
|||
{{t "error.poll.unexpected.description"}}
|
||||
</p>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -25,25 +25,26 @@
|
|||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6 col-lg-5 col-lg-offset-2">
|
||||
<div class="box poll-link">
|
||||
<div class="col-sm-6 col-lg-6 offset-lg-1">
|
||||
<div class="box poll-link cr-poll-link">
|
||||
<p>{{t "poll.share"}}</p>
|
||||
<p class="link">
|
||||
<a href={{pollUrl}}>
|
||||
{{pollUrl}}
|
||||
</a>
|
||||
<p class="link cr-poll-link__link">
|
||||
<small>
|
||||
<code class="cr-poll-link__url">{{pollUrl}}</code>
|
||||
</small>
|
||||
{{#copy-button
|
||||
clipboardText=pollUrl
|
||||
classNames="btn btn-default"
|
||||
classNames="btn btn-secondary cr-poll-link__copy-btn btn-sm"
|
||||
success=(action "linkAction" "copied")
|
||||
error=(action "linkAction" "selected")
|
||||
}}
|
||||
<span class="glyphicon glyphicon-copy"></span>
|
||||
{{t "poll.link.copy-label"}}
|
||||
<span class="oi oi-clipboard" title={{t "poll.link.copy-label"}} aria-hidden="true"></span>
|
||||
{{/copy-button}}
|
||||
</p>
|
||||
<p class="notice">
|
||||
<small class="text-muted">
|
||||
{{t "poll.share.notice"}}
|
||||
</p>
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -61,16 +62,16 @@
|
|||
</div>
|
||||
{{/if}}
|
||||
|
||||
<div class="box container-fluid">
|
||||
<div class="box">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
{{#link-to
|
||||
"poll.participation"
|
||||
model
|
||||
tagName="li"
|
||||
activeClass="active"
|
||||
class="participation"
|
||||
class="participation nav-item"
|
||||
}}
|
||||
{{#link-to "poll.participation" model}}
|
||||
{{#link-to "poll.participation" model class="nav-link"}}
|
||||
{{t "poll.tab-title.participation"}}
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
|
@ -79,9 +80,9 @@
|
|||
model
|
||||
tagName="li"
|
||||
activeClass="active"
|
||||
class="evaluation"
|
||||
class="evaluation nav-item"
|
||||
}}
|
||||
{{#link-to "poll.evaluation" model}}
|
||||
{{#link-to "poll.evaluation" model class="nav-link"}}
|
||||
{{t "poll.tab-title.evaluation"}}
|
||||
{{/link-to}}
|
||||
{{/link-to}}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="participation">
|
||||
<div class="participation cr-form-wrapper">
|
||||
{{#bs-form
|
||||
onSubmit=(action "submit")
|
||||
formLayout="horizontal"
|
||||
|
@ -15,7 +15,7 @@
|
|||
classNames="name"
|
||||
}}
|
||||
<div class="selections">
|
||||
{{#each selections as |selection|}}
|
||||
{{#each selections as |selection index|}}
|
||||
{{#if isFreeText}}
|
||||
{{form.element
|
||||
controlType="text"
|
||||
|
@ -49,15 +49,17 @@
|
|||
as |el|
|
||||
}}
|
||||
{{#each possibleAnswers as |possibleAnswer|}}
|
||||
<div class="radio {{possibleAnswer.type}}">
|
||||
<label>
|
||||
<input
|
||||
type="radio"
|
||||
value={{possibleAnswer.type}}
|
||||
checked={{eq possibleAnswer.type el.value}}
|
||||
onchange={{action (mut el.value) possibleAnswer.type}}
|
||||
>
|
||||
<span class={{possibleAnswer.icon}} aria-hidden="true"></span>
|
||||
<div class="radio custom-control custom-radio custom-control-inline {{possibleAnswer.type}}">
|
||||
<input
|
||||
class="custom-control-input"
|
||||
type="radio"
|
||||
value={{possibleAnswer.type}}
|
||||
checked={{eq possibleAnswer.type el.value}}
|
||||
onchange={{action (mut el.value) possibleAnswer.type}}
|
||||
id={{concat possibleAnswer.type index}}
|
||||
name={{concat possibleAnswer.type index}}
|
||||
>
|
||||
<label class="custom-control-label" for={{concat possibleAnswer.type index}}>
|
||||
{{possibleAnswer.label}}
|
||||
</label>
|
||||
</div>
|
||||
|
|
|
@ -29,7 +29,7 @@ module.exports = function(environment) {
|
|||
'script-src': "'self'",
|
||||
'font-src': "'self'",
|
||||
'connect-src': "'self'",
|
||||
'img-src': "'self'",
|
||||
'img-src': "'self' data:",
|
||||
'style-src': "'self'",
|
||||
'media-src': "'none'",
|
||||
},
|
||||
|
|
|
@ -20,8 +20,8 @@ module.exports = function(defaults) {
|
|||
},
|
||||
'ember-bootstrap': {
|
||||
importBootstrapCSS: false,
|
||||
'bootstrapVersion': 3,
|
||||
'importBootstrapFont': true,
|
||||
'bootstrapVersion': 4,
|
||||
'importBootstrapFont': false,
|
||||
whitelist: ['bs-alert', 'bs-button', 'bs-button-group', 'bs-form', 'bs-modal'],
|
||||
},
|
||||
'ember-cli-babel': {
|
||||
|
@ -33,6 +33,15 @@ module.exports = function(defaults) {
|
|||
'ember-math-helpers': {
|
||||
only: ['lte', 'sub'],
|
||||
},
|
||||
autoprefixer: {
|
||||
browsers: ['last 2 ios version'],
|
||||
cascade: false,
|
||||
sourcemap: true
|
||||
},
|
||||
sassOptions: {
|
||||
sourceMapEmbed: true,
|
||||
includePaths: ['node_modules'],
|
||||
},
|
||||
});
|
||||
|
||||
// Use `app.import` to add additional libraries to the generated
|
||||
|
@ -48,5 +57,8 @@ module.exports = function(defaults) {
|
|||
// please specify an object with the list of modules as keys
|
||||
// along with the exports of each module as its value.
|
||||
|
||||
app.import('node_modules/open-iconic/font/fonts/open-iconic.ttf');
|
||||
app.import('node_modules/open-iconic/font/fonts/open-iconic.woff');
|
||||
|
||||
return app.toTree();
|
||||
};
|
||||
|
|
|
@ -18,11 +18,11 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@ember/optional-features": "^0.7.0",
|
||||
"bootstrap-sass": "^3.3.7",
|
||||
"bootstrap": "^4.3.1",
|
||||
"ember-ajax": "^5.0.0",
|
||||
"ember-auto-import": "^1.3.0",
|
||||
"ember-awesome-macros": "^5.0.0",
|
||||
"ember-bootstrap": "^2.6.1",
|
||||
"ember-bootstrap": "^2.7.1",
|
||||
"ember-bootstrap-cp-validations": "^1.0.0",
|
||||
"ember-cli": "~3.8.2",
|
||||
"ember-cli-acceptance-test-helpers": "^1.0.0",
|
||||
|
@ -72,6 +72,7 @@
|
|||
"eslint-plugin-ember": "^6.4.1",
|
||||
"fs-extra": "^8.0.0",
|
||||
"loader.js": "^4.7.0",
|
||||
"open-iconic": "^1.1.1",
|
||||
"qunit-dom": "^0.8.0",
|
||||
"sass": "^1.19.0",
|
||||
"sjcl": "^1.0.8"
|
||||
|
|
|
@ -316,15 +316,13 @@ module('Integration | Component | create options datetime', function(hooks) {
|
|||
this.set('options', poll.options);
|
||||
|
||||
await render(hbs`{{create-options-datetime dates=options}}`);
|
||||
assert.ok(
|
||||
findAll('.has-error').length === 0 && findAll('.has-success').length === 0,
|
||||
'does not show a validation error before user interaction'
|
||||
);
|
||||
assert.dom('.form-group .is-invalid').doesNotExist('does not show validation errors before user interaction');
|
||||
assert.dom('.form-group .is-valid').doesNotExist('does not show validation success before user interaction');
|
||||
|
||||
await fillIn('[data-test-day="2015-01-01"] .form-group input', '10:');
|
||||
await blur('[data-test-day="2015-01-01"] .form-group input');
|
||||
assert.ok(
|
||||
find('[data-test-day="2015-01-01"] .form-group').classList.contains('has-error') ||
|
||||
find('[data-test-day="2015-01-01"] .form-group input').classList.contains('is-invalid') ||
|
||||
// browsers with input type time support prevent non time input
|
||||
find('[data-test-day="2015-01-01"] .form-group input').value === '',
|
||||
'shows error after invalid input or prevents invalid input'
|
||||
|
@ -336,22 +334,11 @@ module('Integration | Component | create options datetime', function(hooks) {
|
|||
await fillIn(findAll('[data-test-day="2015-01-01"]')[1].querySelector('input'), '10:00');
|
||||
await fillIn('[data-test-day="2015-02-02"] .form-group input', '10:00');
|
||||
await triggerEvent('form', 'submit');
|
||||
assert.dom(findAll('[data-test-day="2015-01-01"]')[0].querySelector('.form-group')).hasClass('has-success',
|
||||
'first time shows validation success'
|
||||
);
|
||||
assert.dom(findAll('[data-test-day="2015-01-01"]')[1].querySelector('.form-group')).hasClass('has-error',
|
||||
'same time for same day shows validation error'
|
||||
);
|
||||
assert.dom('[data-test-day="2015-02-02"] .form-group').hasClass('has-success',
|
||||
'same time for different day shows validation success'
|
||||
);
|
||||
|
||||
// label reflects validation state for all times of this day
|
||||
assert.dom(find('[data-test-day="2015-01-01"]')).hasClass('label-has-error',
|
||||
'label reflects validation state for all times (error)'
|
||||
);
|
||||
assert.dom('[data-test-day="2015-02-02"]').hasClass('label-has-success',
|
||||
'label reflects validation state for all times (success)'
|
||||
);
|
||||
assert.dom(findAll('[data-test-day="2015-01-01"]')[0].querySelector('.form-group input'))
|
||||
.hasClass('is-valid', 'first time shows validation success');
|
||||
assert.dom(findAll('[data-test-day="2015-01-01"]')[1].querySelector('.form-group input'))
|
||||
.hasClass('is-invalid', 'same time for same day shows validation error');
|
||||
assert.dom('[data-test-day="2015-02-02"] .form-group input')
|
||||
.hasClass('is-valid', 'same time for different day shows validation success');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { run } from '@ember/runloop';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render, find, findAll, blur, fillIn, focus } from '@ember/test-helpers';
|
||||
import { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
|
||||
import hbs from 'htmlbars-inline-precompile';
|
||||
import hasComponent from 'croodle/tests/helpers/201-created/raw/has-component';
|
||||
|
||||
|
@ -65,35 +65,23 @@ module('Integration | Component | create options', function(hooks) {
|
|||
hbs`{{create-options options=options isDateTime=isDateTime isFindADate=isFindADate isMakeAPoll=isMakeAPoll}}`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
findAll('input').length === 2,
|
||||
'assumptions are correct'
|
||||
);
|
||||
assert.dom('.form-group').exists({ count: 2 }, 'assumption: renders two form groups');
|
||||
|
||||
await fillIn(findAll('input')[0], 'foo');
|
||||
await blur(findAll('input')[0]);
|
||||
await fillIn(findAll('input')[1], 'foo');
|
||||
await blur(findAll('input')[1]);
|
||||
assert.ok(
|
||||
findAll('.form-group')[1].classList.contains('has-error'),
|
||||
'second input field has validation error'
|
||||
);
|
||||
assert.ok(
|
||||
findAll('.form-group')[1].querySelector('.help-block'),
|
||||
'validation error is shown'
|
||||
);
|
||||
await fillIn('.form-group:nth-child(1) input', 'foo');
|
||||
await blur('.form-group:nth-child(1) input');
|
||||
await fillIn('.form-group:nth-child(2) input', 'foo');
|
||||
await blur('.form-group:nth-child(2) input');
|
||||
assert.dom('.form-group:nth-child(2) input')
|
||||
.hasClass('is-invalid', 'second input field has validation error');
|
||||
assert.dom('.form-group:nth-child(2) .invalid-feedback')
|
||||
.exists('validation error is shown');
|
||||
|
||||
await fillIn(findAll('input')[0], 'bar');
|
||||
await blur(findAll('input')[0]);
|
||||
assert.ok(
|
||||
findAll('.form-group .help-block').length === 0,
|
||||
'there is no validation error anymore after a unique value is entered'
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
findAll('.form-group.has-error').length === 0,
|
||||
'has-error classes are removed'
|
||||
);
|
||||
assert.dom('.form-group .invalid-feedback')
|
||||
.doesNotExist('there is no validation error anymore after a unique value is entered');
|
||||
assert.dom('.form-group .is-invalid')
|
||||
.doesNotExist('.is-invalid classes are removed');
|
||||
});
|
||||
|
||||
test('shows validation errors if option is empty (makeAPoll)', async function(assert) {
|
||||
|
@ -127,70 +115,14 @@ module('Integration | Component | create options', function(hooks) {
|
|||
await blur(findAll('input')[0]);
|
||||
await focus(findAll('input')[1]);
|
||||
await blur(findAll('input')[1]);
|
||||
assert.equal(
|
||||
findAll('.form-group.has-error').length, 2
|
||||
);
|
||||
assert.dom('.form-group .invalid-feedback').exists({ count: 2 });
|
||||
|
||||
await fillIn(findAll('input')[0], 'foo');
|
||||
await blur(findAll('input')[0]);
|
||||
assert.equal(
|
||||
findAll('.form-group.has-error').length, 1
|
||||
);
|
||||
assert.dom('.form-group .invalid-feedback').exists({ count: 1 });
|
||||
|
||||
await fillIn(findAll('input')[1], 'bar');
|
||||
await blur(findAll('input')[1]);
|
||||
assert.equal(
|
||||
findAll('.form-group.has-error').length, 0
|
||||
);
|
||||
});
|
||||
|
||||
test('label reflects validation state of all inputs (makeAPoll)', async function(assert) {
|
||||
this.set('isDateTime', false);
|
||||
this.set('isFindADate', false);
|
||||
this.set('isMakeAPoll', true);
|
||||
|
||||
// validation is based on validation of every option fragment
|
||||
// which validates according to poll model it belongs to
|
||||
// therefore each option needs to be pushed to poll model to have it as
|
||||
// it's owner
|
||||
let poll;
|
||||
run(() => {
|
||||
poll = this.store.createRecord('poll', {
|
||||
isFindADate: this.get('isFindADate'),
|
||||
isDateTime: this.get('isDateTime'),
|
||||
isMakeAPoll: this.get('isMakeAPoll')
|
||||
});
|
||||
});
|
||||
this.set('options', poll.get('options'));
|
||||
|
||||
await render(
|
||||
hbs`{{create-options options=options isDateTime=isDateTime isFindADate=isFindADate isMakeAPoll=isMakeAPoll}}`
|
||||
);
|
||||
|
||||
assert.ok(
|
||||
find('form').firstElementChild.classList.contains('label-has-no-validation'),
|
||||
'does not show validation state if there wasn\'t any user interaction yet'
|
||||
);
|
||||
|
||||
await focus(findAll('input')[0]);
|
||||
await blur(findAll('input')[0]);
|
||||
assert.ok(
|
||||
find('form').firstElementChild.classList.contains('label-has-error'),
|
||||
'shows as having error if atleast on field has an error'
|
||||
);
|
||||
|
||||
await fillIn(findAll('input')[0], 'foo');
|
||||
await blur(findAll('input')[0]);
|
||||
assert.ok(
|
||||
find('form').firstElementChild.classList.contains('label-has-no-validation'),
|
||||
'does not show validation state if no field has error but not all fields are showing error yet'
|
||||
);
|
||||
|
||||
await fillIn(findAll('input')[1], 'bar');
|
||||
await blur(findAll('input')[1]);
|
||||
assert.ok(
|
||||
find('form').firstElementChild.classList.contains('label-has-success'),
|
||||
'shows as having success if all fields are showing success'
|
||||
);
|
||||
assert.dom('.form-group .invalid-feedback').doesNotExist();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -43,7 +43,7 @@ export default create(assign({}, defaultsForCreate, {
|
|||
item: {
|
||||
add: clickable('button.add'),
|
||||
delete: clickable('button.delete'),
|
||||
hasError: hasClass('has-error'),
|
||||
hasError: hasClass('is-invalid', 'input'),
|
||||
title: fillable('input')
|
||||
}
|
||||
}),
|
||||
|
|
|
@ -13,7 +13,7 @@ const urlMatches = function(regExp) {
|
|||
|
||||
export const definition = {
|
||||
showsExpirationWarning: isVisible('.expiration-warning'),
|
||||
url: text('.poll-link .link a'),
|
||||
url: text('.poll-link .link code'),
|
||||
urlIsValid: urlMatches(/^\/poll\/[a-zA-Z0-9]{10}\/participation\?encryptionKey=[a-zA-Z0-9]{40}$/)
|
||||
};
|
||||
|
||||
|
|
|
@ -20,9 +20,9 @@ export default PageObject.create(assign({}, defaultsForApplication, Poll, {
|
|||
answers: text('.selections .form-group:eq(0) .radio', { multiple: true }),
|
||||
itemScope: '.selections .form-group',
|
||||
item: {
|
||||
label: text('label.control-label')
|
||||
label: text('label')
|
||||
},
|
||||
labels: text('.selections .form-group label.control-label', { multiple: true })
|
||||
labels: text('.selections .form-group > label', { multiple: true })
|
||||
}),
|
||||
title: text('h2.title'),
|
||||
// use as .visit({ encryptionKey: ??? })
|
||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -2216,10 +2216,10 @@ boom@2.x.x:
|
|||
dependencies:
|
||||
hoek "2.x.x"
|
||||
|
||||
bootstrap-sass@^3.3.7:
|
||||
version "3.4.1"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.4.1.tgz#6843c73b1c258a0ac5cb2cc6f6f5285b664a8e9a"
|
||||
integrity sha512-p5rxsK/IyEDQm2CwiHxxUi0MZZtvVFbhWmyMOt4lLkA4bujDA1TGoKT0i1FKIWiugAdP+kK8T5KMDFIKQCLYIA==
|
||||
bootstrap@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.3.1.tgz#280ca8f610504d99d7b6b4bfc4b68cec601704ac"
|
||||
integrity sha512-rXqOmH1VilAt2DyPzluTi2blhk17bO7ef+zLLPlWvG494pDxcM234pJ8wTc/6R40UWizAIIMgxjvxZg5kmsbag==
|
||||
|
||||
bower-config@^1.3.0:
|
||||
version "1.4.1"
|
||||
|
@ -4135,7 +4135,7 @@ ember-bootstrap-cp-validations@^1.0.0:
|
|||
dependencies:
|
||||
ember-cli-babel "^6.6.0"
|
||||
|
||||
ember-bootstrap@^2.6.1:
|
||||
ember-bootstrap@^2.7.1:
|
||||
version "2.7.1"
|
||||
resolved "https://registry.yarnpkg.com/ember-bootstrap/-/ember-bootstrap-2.7.1.tgz#44f4c9ad83c543f447796429d27ddb621d70b820"
|
||||
integrity sha512-qqB0GPNZa3ypwoaI11GtrIs6Ugs3KPz/0TWG6M3eHbRb6BKTLfx3mUh0Ws0SwJDgZ5pMPsdHI4m1yDBrR9/ulQ==
|
||||
|
@ -9026,6 +9026,11 @@ onetime@^2.0.0:
|
|||
dependencies:
|
||||
mimic-fn "^1.0.0"
|
||||
|
||||
open-iconic@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/open-iconic/-/open-iconic-1.1.1.tgz#9dcfc8c7cd3c61cdb4a236b1a347894c97adc0c6"
|
||||
integrity sha1-nc/Ix808Yc20ojaxo0eJTJetwMY=
|
||||
|
||||
opener@~1.4.1:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
|
|
Loading…
Reference in a new issue