mirror of
https://github.com/discourse/discourse.git
synced 2025-02-17 09:32:46 +08:00
UX: full revamp of local-dates form (#7357)
This commit is contained in:
parent
110512d4d0
commit
7226240df3
|
@ -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
|
||||
};
|
||||
});
|
||||
}
|
||||
});
|
|
@ -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");
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in New Issue
Block a user