keeweb/app/scripts/views/details/details-history-view.js

339 lines
11 KiB
JavaScript
Raw Normal View History

2019-09-16 20:42:33 +02:00
import { View } from 'framework/views/view';
2019-09-15 14:16:32 +02:00
import { Alerts } from 'comp/ui/alerts';
import { Keys } from 'const/keys';
import { DateFormat } from 'util/formatting/date-format';
import { StringFormat } from 'util/formatting/string-format';
import { Locale } from 'util/locale';
2019-09-16 20:42:33 +02:00
import { Copyable } from 'framework/views/copyable';
2019-09-15 14:16:32 +02:00
import { FieldViewReadOnly } from 'views/fields/field-view-read-only';
import { FieldViewReadOnlyRaw } from 'views/fields/field-view-read-only-raw';
2019-09-18 07:08:23 +02:00
import { escape } from 'util/fn';
2019-09-16 19:09:57 +02:00
import template from 'templates/details/details-history.hbs';
2017-01-31 07:50:28 +01:00
2019-09-16 19:09:57 +02:00
class DetailsHistoryView extends View {
template = template;
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
events = {
'click .details__subview-close': 'closeHistory',
2015-10-17 23:49:24 +02:00
'click .details__history-timeline-item': 'timelineItemClick',
'click .details__history-arrow-prev': 'timelinePrevClick',
'click .details__history-arrow-next': 'timelineNextClick',
'click .details__history-button-revert': 'revertClick',
'click .details__history-button-delete': 'deleteClick',
'click .details__history-button-discard': 'discardClick'
2019-09-16 19:09:57 +02:00
};
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
formats = [
2019-08-16 23:05:39 +02:00
{
name: 'ms',
round: 1,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dtStr(d);
2019-08-16 23:05:39 +02:00
}
},
{
name: 'sec',
round: 1000,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dtStr(d);
2019-08-16 23:05:39 +02:00
}
},
{
name: 'min',
round: 1000 * 60,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dtStr(d).replace(':00 ', ' ');
2019-08-16 23:05:39 +02:00
}
},
{
name: 'hour',
round: 1000 * 60 * 60,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dtStr(d).replace(':00', '');
2019-08-16 23:05:39 +02:00
}
},
{
name: 'day',
round: 1000 * 60 * 60 * 24,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dStr(d);
2019-08-16 23:05:39 +02:00
}
},
{
name: 'month',
round: 1000 * 60 * 60 * 24 * 31,
2019-08-18 10:17:09 +02:00
format(d) {
2019-09-15 08:11:11 +02:00
return DateFormat.dStr(d);
2019-08-16 23:05:39 +02:00
}
},
{
name: 'year',
round: 1000 * 60 * 60 * 24 * 365,
2019-08-18 10:17:09 +02:00
format(d) {
2019-08-16 23:05:39 +02:00
return d.getFullYear();
}
}
2019-09-16 19:09:57 +02:00
];
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
fieldViews = [];
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
visibleRecord = undefined;
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
constructor(model, options) {
super(model, options);
this.onKey(Keys.DOM_VK_ESCAPE, this.closeHistory);
this.once('remove', () => {
this.removeFieldViews();
});
}
render() {
super.render();
2015-10-17 23:49:24 +02:00
this.history = this.model.getHistory();
this.buildTimeline();
this.timelineEl = this.$el.find('.details__history-timeline');
this.bodyEl = this.$el.find('.details__history-body');
2020-06-01 16:53:51 +02:00
this.timeline.forEach(function (item, ix) {
2019-08-16 23:05:39 +02:00
$('<i/>')
.addClass('fa fa-circle details__history-timeline-item')
.css('left', item.pos * 100 + '%')
2015-10-17 23:49:24 +02:00
.attr('data-id', ix)
.appendTo(this.timelineEl);
}, this);
2020-06-01 16:53:51 +02:00
this.labels.forEach(function (label) {
2019-08-16 23:05:39 +02:00
$('<div/>')
.addClass('details__history-timeline-label')
.css('left', label.pos * 100 + '%')
2015-10-17 23:49:24 +02:00
.text(label.text)
.appendTo(this.timelineEl);
}, this);
2019-09-16 19:09:57 +02:00
let visibleRecord = this.visibleRecord;
2015-10-17 23:49:24 +02:00
if (visibleRecord === undefined) {
visibleRecord = this.history.length - 1;
}
this.showRecord(visibleRecord);
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
removeFieldViews() {
2020-06-01 16:53:51 +02:00
this.fieldViews.forEach((fieldView) => fieldView.remove());
2015-10-17 23:49:24 +02:00
this.fieldViews = [];
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
showRecord(ix) {
2015-10-17 23:49:24 +02:00
this.activeIx = ix;
this.record = this.timeline[ix].rec;
2019-08-18 08:05:38 +02:00
this.timelineEl
.find('.details__history-timeline-item')
.removeClass('details__history-timeline-item--active');
2019-08-16 23:05:39 +02:00
this.timelineEl
.find('.details__history-timeline-item[data-id="' + ix + '"]')
.addClass('details__history-timeline-item--active');
2015-10-17 23:49:24 +02:00
this.removeFieldViews();
2020-05-09 20:15:46 +02:00
this.bodyEl.empty();
2017-01-31 07:50:28 +01:00
const colorCls = this.record.color ? this.record.color + '-color' : '';
2019-08-16 23:05:39 +02:00
this.fieldViews.push(
2019-09-16 19:55:06 +02:00
new FieldViewReadOnly({ name: 'Rev', title: Locale.detHistoryVersion, value: ix + 1 })
2019-08-16 23:05:39 +02:00
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: 'Updated',
title: Locale.detHistorySaved,
value:
DateFormat.dtStr(this.record.updated) +
(this.record.unsaved ? ' (' + Locale.detHistoryCurUnsavedState + ')' : '') +
(ix === this.history.length - 1 && !this.record.unsaved
? ' (' + Locale.detHistoryCurState + ')'
: '')
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnlyRaw({
2019-09-16 19:55:06 +02:00
name: '$Title',
title: StringFormat.capFirst(Locale.title),
value:
'<i class="fa fa-' +
this.record.icon +
' ' +
colorCls +
'"></i> ' +
2019-09-18 07:08:23 +02:00
escape(this.record.title) || '(' + Locale.detHistoryNoTitle + ')'
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: '$UserName',
title: StringFormat.capFirst(Locale.user),
value: this.record.user
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: '$Password',
title: StringFormat.capFirst(Locale.password),
value: this.record.password
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: '$URL',
title: StringFormat.capFirst(Locale.website),
value: this.record.url
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: '$Notes',
title: StringFormat.capFirst(Locale.notes),
value: this.record.notes
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: 'Tags',
title: StringFormat.capFirst(Locale.tags),
value: this.record.tags.join(', ')
2019-08-16 23:05:39 +02:00
})
);
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: 'Expires',
title: Locale.detExpires,
value: this.record.expires ? DateFormat.dtStr(this.record.expires) : ''
2019-08-16 23:05:39 +02:00
})
);
2019-09-17 22:17:40 +02:00
for (const [field, value] of Object.entries(this.record.fields)) {
this.fieldViews.push(new FieldViewReadOnly({ name: '$' + field, title: field, value }));
}
2015-10-17 23:49:24 +02:00
if (this.record.attachments.length) {
2019-08-16 23:05:39 +02:00
this.fieldViews.push(
new FieldViewReadOnly({
2019-09-16 19:55:06 +02:00
name: 'Attachments',
title: Locale.detAttachments,
2020-06-01 16:53:51 +02:00
value: this.record.attachments.map((att) => att.title).join(', ')
2019-08-16 23:05:39 +02:00
})
);
2015-10-17 23:49:24 +02:00
}
2020-06-01 16:53:51 +02:00
this.fieldViews.forEach((fieldView) => {
2019-09-16 19:55:06 +02:00
fieldView.parent = this.bodyEl[0];
fieldView.render();
fieldView.on('copy', this.fieldCopied.bind(this));
2019-09-16 19:55:06 +02:00
});
2017-01-31 07:50:28 +01:00
const buttons = this.$el.find('.details__history-buttons');
2015-10-17 23:49:24 +02:00
buttons.find('.details__history-button-revert').toggle(ix < this.history.length - 1);
buttons.find('.details__history-button-delete').toggle(ix < this.history.length - 1);
2019-08-16 23:05:39 +02:00
buttons
.find('.details__history-button-discard')
2019-08-18 08:05:38 +02:00
.toggle(
(this.record.unsaved &&
ix === this.history.length - 1 &&
this.history.length > 1) ||
false
);
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
timelineItemClick(e) {
2020-06-01 16:53:51 +02:00
const id = $(e.target).closest('.details__history-timeline-item').data('id');
2015-10-17 23:49:24 +02:00
this.showRecord(id);
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
timelinePrevClick() {
2015-10-17 23:49:24 +02:00
if (this.activeIx > 0) {
this.showRecord(this.activeIx - 1);
}
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
timelineNextClick() {
2015-10-17 23:49:24 +02:00
if (this.activeIx < this.timeline.length - 1) {
this.showRecord(this.activeIx + 1);
}
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
buildTimeline() {
2017-01-31 07:50:28 +01:00
const firstRec = this.history[0];
const lastRec = this.history[this.history.length - 1];
2020-06-01 16:53:51 +02:00
this.timeline = this.history.map((rec) => ({
2016-07-17 13:30:38 +02:00
pos: (rec.updated - firstRec.updated) / (lastRec.updated - firstRec.updated),
2019-08-18 10:17:09 +02:00
rec
2016-07-17 13:30:38 +02:00
}));
2017-01-31 07:50:28 +01:00
const period = lastRec.updated - firstRec.updated;
const format = this.getDateFormat(period);
2019-08-18 08:05:38 +02:00
this.labels = this.getLabels(
firstRec.updated.getTime(),
lastRec.updated.getTime(),
format.round
2020-06-01 16:53:51 +02:00
).map((label) => ({
2019-08-18 08:05:38 +02:00
pos: (label - firstRec.updated) / (lastRec.updated - firstRec.updated),
val: label,
text: format.format(new Date(label))
}));
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
getDateFormat(period) {
2017-01-31 07:50:28 +01:00
for (let i = 0; i < this.formats.length; i++) {
2015-10-17 23:49:24 +02:00
if (period < this.formats[i].round * 1.2) {
return this.formats[i > 0 ? i - 1 : 0];
}
}
return this.formats[this.formats.length - 1];
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
getLabels(first, last, round) {
2017-01-31 07:50:28 +01:00
const count = Math.floor((last - first) / round);
2015-10-17 23:49:24 +02:00
if (count > 2) {
round *= Math.ceil(count / 2);
}
2017-01-31 07:50:28 +01:00
const labels = [];
let label = Math.ceil(first / round) * round;
2015-10-17 23:49:24 +02:00
while (label < last) {
labels.push(label);
label += round;
}
2019-08-16 23:05:39 +02:00
if (labels.length > 1 && (labels[0] - first) / (last - first) < 0.1) {
2015-10-17 23:49:24 +02:00
labels.shift();
}
return labels;
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
closeHistory(updated) {
2019-09-16 19:09:57 +02:00
this.emit('close', { updated });
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
revertClick() {
2015-10-17 23:49:24 +02:00
Alerts.yesno({
2017-12-17 18:22:20 +01:00
header: Locale.detHistoryRevertAlert,
body: Locale.detHistoryRevertAlertBody,
2016-07-17 13:30:38 +02:00
success: () => {
2015-10-17 23:49:24 +02:00
this.model.revertToHistoryState(this.record.entry);
this.closeHistory(true);
2016-07-17 13:30:38 +02:00
}
2015-10-17 23:49:24 +02:00
});
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
deleteClick() {
2015-10-17 23:49:24 +02:00
Alerts.yesno({
2017-12-17 18:22:20 +01:00
header: Locale.detHistoryDeleteAlert,
body: Locale.detHistoryDeleteAlertBody,
2016-07-17 13:30:38 +02:00
success: () => {
2015-10-17 23:49:24 +02:00
this.model.deleteHistory(this.record.entry);
2019-09-16 19:09:57 +02:00
this.visibleRecord = this.activeIx;
this.render();
2016-07-17 13:30:38 +02:00
}
2015-10-17 23:49:24 +02:00
});
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-08-18 10:17:09 +02:00
discardClick() {
2015-10-17 23:49:24 +02:00
Alerts.yesno({
2017-12-17 18:22:20 +01:00
header: Locale.detHistoryDiscardChangesAlert,
body: Locale.detHistoryDiscardChangesAlertBody,
2016-07-17 13:30:38 +02:00
success: () => {
2015-10-17 23:49:24 +02:00
this.model.discardUnsaved();
this.closeHistory(true);
2016-07-17 13:30:38 +02:00
}
2015-10-17 23:49:24 +02:00
});
2019-01-02 10:49:12 +01:00
}
2019-09-16 19:09:57 +02:00
}
2015-10-17 23:49:24 +02:00
2019-09-16 19:09:57 +02:00
Object.assign(DetailsHistoryView.prototype, Copyable);
2019-01-02 10:49:12 +01:00
2019-09-15 14:16:32 +02:00
export { DetailsHistoryView };