UX: full revamp of local-dates form (#7357)

This commit is contained in:
Joffrey JAFFEUX 2019-04-11 11:14:34 +02:00 committed by GitHub
parent 110512d4d0
commit 7226240df3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 352 additions and 167 deletions

View File

@ -0,0 +1,31 @@
import ComboBoxComponent from "select-kit/components/combo-box";
import { default as computed } from "ember-addons/ember-computed-decorators";
export default ComboBoxComponent.extend({
pluginApiIdentifiers: ["timezone-input"],
classNames: "timezone-input",
allowAutoSelectFirst: false,
fullWidthOnMobile: true,
filterable: true,
allowAny: false,
@computed
content() {
let timezones;
if (
moment.locale() !== "en" &&
typeof moment.tz.localizedNames === "function"
) {
timezones = moment.tz.localizedNames();
}
timezones = moment.tz.names();
return timezones.map(t => {
return {
id: t,
name: t
};
});
}
});

View File

@ -1,3 +1,6 @@
/* global Pikaday:true */
import { propertyNotEqual } from "discourse/lib/computed";
import loadScript from "discourse/lib/load-script";
import { default as computed } from "ember-addons/ember-computed-decorators";
import { cookAsync } from "discourse/lib/text";
import debounce from "discourse/lib/debounce";
@ -16,11 +19,16 @@ export default Ember.Component.extend({
advancedMode: false,
isValid: true,
timezone: null,
timezones: null,
fromSelected: null,
fromFilled: Ember.computed.notEmpty("date"),
toSelected: null,
toFilled: Ember.computed.notEmpty("toDate"),
init() {
this._super(...arguments);
this._picker = null;
this.setProperties({
timezones: [],
formats: (this.siteSettings.discourse_local_dates_default_formats || "")
@ -34,7 +42,10 @@ export default Ember.Component.extend({
didInsertElement() {
this._super(...arguments);
this._renderPreview();
this._setupPicker().then(picker => {
this._picker = picker;
this.send("focusFrom");
});
},
_renderPreview: debounce(function() {
@ -167,6 +178,11 @@ export default Ember.Component.extend({
return moment.tz.guess();
},
timezoneIsDifferentFromUserTimezone: propertyNotEqual(
"currentUserTimezone",
"options.timezone"
),
@computed("currentUserTimezone")
formatedCurrentUserTimezone(timezone) {
return timezone
@ -225,17 +241,6 @@ export default Ember.Component.extend({
];
},
@computed()
allTimezones() {
if (
moment.locale() !== "en" &&
typeof moment.tz.localizedNames === "function"
) {
return moment.tz.localizedNames();
}
return moment.tz.names();
},
_generateDateMarkup(config, options, isRange) {
let text = `[date=${config.date}`;
@ -287,7 +292,36 @@ export default Ember.Component.extend({
return text;
},
@computed("fromConfig.dateTime")
formattedFrom(dateTime) {
return dateTime.format("LLLL");
},
@computed("toConfig.dateTime", "toSelected")
formattedTo(dateTime, toSelected) {
const emptyText = toSelected
? " "
: I18n.t("discourse_local_dates.create.form.until");
return dateTime.isValid() ? dateTime.format("LLLL") : emptyText;
},
actions: {
eraseToDateTime() {
this.setProperties({ toDate: null, toTime: null });
this._setPickerDate(null);
},
focusFrom() {
this.setProperties({ fromSelected: true, toSelected: false });
this._setPickerDate(this.get("fromConfig.date"));
},
focusTo() {
this.setProperties({ toSelected: true, fromSelected: false });
this._setPickerDate(this.get("toConfig.date"));
},
advancedMode() {
this.toggleProperty("advancedMode");
},
@ -306,6 +340,53 @@ export default Ember.Component.extend({
}
},
_setupPicker() {
return new Ember.RSVP.Promise(resolve => {
loadScript("/javascripts/pikaday.js").then(() => {
const options = {
field: this.$(`.fake-input`)[0],
container: this.$(`#picker-container-${this.elementId}`)[0],
bound: false,
format: "YYYY-MM-DD",
reposition: false,
firstDay: 1,
defaultDate: moment(this.get("date"), this.dateFormat).toDate(),
setDefaultDate: true,
i18n: {
previousMonth: I18n.t("dates.previous_month"),
nextMonth: I18n.t("dates.next_month"),
months: moment.months(),
weekdays: moment.weekdays(),
weekdaysShort: moment.weekdaysShort()
},
onSelect: date => {
const formattedDate = moment(date).format("YYYY-MM-DD");
if (this.get("fromSelected")) {
this.set("date", formattedDate);
}
if (this.get("toSelected")) {
this.set("toDate", formattedDate);
}
}
};
resolve(new Pikaday(options));
});
});
},
_setPickerDate(date) {
if (date && !moment(date, this.dateFormat).isValid()) {
date = null;
}
Ember.run.schedule("afterRender", () => {
this._picker.setDate(date, true);
});
},
_closeModal() {
const composer = Discourse.__container__.lookup("controller:composer");
composer.send("closeModal");

View File

@ -9,83 +9,71 @@
{{i18n "discourse_local_dates.create.form.invalid_date"}}
</div>
{{else}}
<div class="preview alert alert-info">
<b>{{formatedCurrentUserTimezone}} </b>{{currentPreview}}
</div>
{{#if timezoneIsDifferentFromUserTimezone}}
<div class="preview alert alert-info">
<b>{{formatedCurrentUserTimezone}} </b>{{currentPreview}}
</div>
{{/if}}
{{/unless}}
{{computeDate}}
<div class="date-time-configuration">
<div class="range">
<div class="from">
<div class="control-group date">
<label class="control-label">
{{i18n "discourse_local_dates.create.form.date_title"}}
</label>
<div class="controls">
{{date-picker
onSelect=(action (mut date))
class="date-input"
value=date
defaultDate="DD-MM-YYYY"}}
</div>
</div>
<div class="control-group time">
<label class="control-label">
{{i18n "discourse_local_dates.create.form.time_title"}}
</label>
<div class="controls">
{{input input=(mut time) type="time" value=time class="time-input"}}
</div>
</div>
<div class="inputs-panel">
<div class="date-time-control from {{if fromSelected 'is-selected'}} {{if fromFilled 'is-filled'}}">
{{d-icon "calendar-alt"}}
{{d-button
action=(action "focusFrom")
translatedLabel=formattedFrom
class="date-time"}}
</div>
<div class="to-indicator">
{{if site.mobileView "↓" "→"}}
<div class="date-time-control to {{if toSelected 'is-selected'}} {{if toFilled 'is-filled'}}">
{{d-icon "calendar-alt"}}
{{d-button
action=(action "focusTo")
translatedLabel=formattedTo
class="date-time"}}
{{#if toFilled}}
{{d-button icon="times" action=(action "eraseToDateTime") class="delete-to-date"}}
{{/if}}
</div>
<div class="to">
<div class="control-group date">
<label class="control-label">
{{i18n "discourse_local_dates.create.form.date_title"}}
</label>
<div class="controls">
{{date-picker
onSelect=(action (mut toDate))
class="date-input"
value=toDate
defaultDate="DD-MM-YYYY"}}
</div>
</div>
<div class="control-group time">
<label class="control-label">
{{i18n "discourse_local_dates.create.form.time_title"}}
</label>
<div class="controls">
{{input input=(mut toTime) type="time" value=toTime class="time-input"}}
</div>
</div>
</div>
{{#unless site.mobileView}}
{{timezone-input
headerIcon="globe"
value=timezone
onSelect=(action (mut timezone))}}
{{/unless}}
</div>
<div class="timezone">
<div class="control-group time">
<label class="control-label">
{{i18n "discourse_local_dates.create.form.timezone"}}
</label>
<div class="controls">
{{combo-box
class="timezone-input"
allowAny=false
content=allTimezones
value=timezone
onSelect=(action (mut timezone))}}
<div class="picker-panel">
{{input class="fake-input"}}
<div class="date-picker" id="picker-container-{{elementId}}"></div>
{{#if fromSelected}}
<div class="time-pickers">
{{d-icon "far-clock"}}
{{input input=(mut time) type="time" value=time class="time-picker"}}
</div>
</div>
{{/if}}
{{#if toSelected}}
{{#if toDate}}
<div class="time-pickers">
{{d-icon "far-clock"}}
{{input input=(mut toTime) type="time" value=toTime class="time-picker"}}
</div>
{{/if}}
{{/if}}
</div>
{{#if site.mobileView}}
{{timezone-input
headerIcon="globe"
value=timezone
onSelect=(action (mut timezone))}}
{{/if}}
</div>
{{#if advancedMode}}
@ -148,7 +136,7 @@
<div class="modal-footer discourse-local-dates-create-modal-footer">
{{#if isValid}}
{{d-button class="btn btn-default"
{{d-button class="btn btn-primary"
action=(action "save")
label="discourse_local_dates.create.form.insert"}}
{{/if}}

View File

@ -57,11 +57,30 @@
}
.discourse-local-dates-create-modal {
min-height: 250px;
min-height: 320px;
display: flex;
flex-direction: row;
padding: 0.5em;
.picker-panel {
padding: 5px;
border: 1px solid $primary-low;
}
.date-picker {
display: flex;
flex-direction: column;
width: auto;
box-sizing: border-box;
.pika-single {
position: relative !important;
flex: 1;
display: flex;
border: 0;
}
}
.form {
flex: 1 0 0px;
@ -72,60 +91,149 @@
.date-time-configuration {
display: flex;
.range,
.timezone {
display: flex;
justify-content: flex-start;
flex: 1;
align-items: center;
.from {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.to {
display: flex;
flex-direction: row;
justify-content: space-between;
}
.to-indicator {
display: flex;
flex-direction: row;
padding-top: 0.5em;
margin: 0 0.5em;
font-size: $font-up-2;
}
.fake-input {
display: none;
}
.date {
margin-right: 0.5em;
.date-input {
width: 100px;
text-align: center;
.date-picker {
padding: 0;
margin: 0;
width: 100%;
.timezone-input {
width: 100%;
&.is-expanded {
.select-kit-header {
border: 1px solid $primary-medium;
}
}
.select-kit-header {
padding: 0.5em 0.5em;
border: 1px solid $primary-low;
.d-icon {
margin-right: 1em;
}
.caret-icon {
margin-right: 0;
}
}
}
.timezone {
margin-left: 1em;
.timezone-input {
width: 160px;
.date-time-control {
position: relative;
display: flex;
border: 1px solid $primary-low;
&.is-filled,
&.is-selected {
.date-time {
color: $primary;
background: $secondary;
}
.d-icon {
color: $primary-high;
}
}
&.from {
border-radius: 5px 5px 0 0;
.date-time {
border-radius: 5px 5px 0 0;
}
&.is-selected {
border-color: $tertiary;
}
}
&.to {
border-radius: 0 0 5px 5px;
margin-bottom: 1em;
.date-time {
border-radius: 0 0 5px 5px;
padding-right: 3em;
}
&.is-selected {
border-color: $tertiary;
}
}
.date-time {
color: $primary-medium;
background: $primary-very-low;
}
.d-icon {
position: absolute;
margin-top: auto;
margin-bottom: auto;
left: 0.5em;
top: 0;
bottom: 0;
color: $primary-medium;
}
.delete-to-date {
position: absolute;
margin-top: auto;
margin-bottom: auto;
right: 0;
width: 30px;
top: 0;
bottom: 0;
color: $primary-high;
border-radius: 0 0 5px 0;
}
.date-time {
padding: 1em 0.5em 1em 2em;
border: 0;
outline: none;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
text-align: left;
}
}
.time {
.time-input {
width: auto;
.inputs-panel {
flex: 1;
}
.picker-panel {
z-index: 1;
background: $secondary;
width: 200px;
box-sizing: border-box;
margin-left: 1em;
}
.time-pickers {
display: flex;
justify-content: center;
flex: 1;
margin-top: 1em;
align-items: center;
padding: 0.25em;
border-top: 1px solid $primary-low-mid;
box-sizing: border-box;
.d-icon {
color: $primary-medium;
}
.time-picker {
outline: none;
box-shadow: none;
margin: 0;
padding: 0;
text-align: center;
box-sizing: border-box;
width: 100%;
border: 0;
}
}
}
@ -188,54 +296,30 @@
}
}
@media (max-width: 700px) {
.discourse-local-dates-create-modal {
.from,
.to {
width: 100%;
html:not(.mobile-view) {
.discourse-local-dates-create-modal.modal-body {
width: 600px;
max-height: 400px;
}
}
html.mobile-view {
.discourse-local-dates-create-modal.modal-body {
.date-time-configuration {
flex-direction: column;
}
.form {
.date-time-configuration {
flex-direction: column;
.range,
.timezone {
display: flex;
flex: 1;
flex-direction: column;
.picker-panel {
width: 100%;
margin: 0 0 1em 0;
.controls,
.control-group {
width: 100%;
}
.to-indicator {
margin: 0 0.5em;
padding: 0;
}
}
.date {
.date-input {
width: 100%;
}
}
.time {
.time-input {
width: 100%;
}
}
.timezone {
margin: 0.5em 0 0 0;
padding: 0.5em 0 0 0;
border-top: 1px solid $primary-low;
.timezone-input {
width: 100%;
}
}
.pika-single {
justify-content: center;
}
}
.time-picker {
padding-top: 6px;
}
}
}

View File

@ -22,6 +22,7 @@ en:
time_title: Time
format_title: Date format
timezone: Timezone
until: Until...
recurring:
every_day: "Every day"
every_week: "Every week"