From bce60cc45d7c478fc829eb3ef1eec9163a2d0a43 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Thu, 27 Sep 2018 11:13:43 +0200 Subject: [PATCH 1/8] confirm dialog for closing modal --- jsapp/js/components/modal.es6 | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/jsapp/js/components/modal.es6 b/jsapp/js/components/modal.es6 index 010c8548bc..d61fe86d34 100644 --- a/jsapp/js/components/modal.es6 +++ b/jsapp/js/components/modal.es6 @@ -2,22 +2,17 @@ import React from 'react'; import reactMixin from 'react-mixin'; import autoBind from 'react-autobind'; import Reflux from 'reflux'; -import {hashHistory} from 'react-router'; - +import alertify from 'alertifyjs'; import {dataInterface} from '../dataInterface'; import actions from '../actions'; import bem from '../bem'; import ui from '../ui'; import stores from '../stores'; -import mixins from '../mixins'; - -import {t, notify} from '../utils'; - +import {t} from '../utils'; import { PROJECT_SETTINGS_CONTEXTS, MODAL_TYPES } from '../constants'; - import ProjectSettings from '../components/modalForms/projectSettings'; import SharingForm from '../components/modalForms/sharingForm'; import Submission from '../components/modalForms/submission'; @@ -149,11 +144,33 @@ class Modal extends React.Component { return title; } + displaySafeCloseConfirm(title, message) { + const dialog = alertify.dialog('confirm'); + const opts = { + title: title, + message: message, + labels: {ok: t('Close'), cancel: t('Cancel')}, + onok: stores.pageState.hideModal, + oncancel: dialog.destroy + }; + dialog.set(opts).show(); + } + onModalClose(evt) { + if (this.props.params.type === MODAL_TYPES.FORM_TRANSLATIONS_TABLE) { + this.displaySafeCloseConfirm( + t('Close Translations table?'), + t('You will lose all unsaved changes.') + ); + return false; + } else { + stores.pageState.hideModal(); + } + } render() { return ( {stores.pageState.hideModal()}} + onClose={this.onModalClose} title={this.state.title} className={this.state.modalClass} > From b65925c7f46425a1f7a14841494db7da6b293637 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Thu, 27 Sep 2018 11:20:40 +0200 Subject: [PATCH 2/8] move close modal Esc shortcut to ui code --- jsapp/js/app.es6 | 3 --- jsapp/js/keymap.js | 3 +-- jsapp/js/ui.es6 | 11 +++++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/jsapp/js/app.es6 b/jsapp/js/app.es6 index ad75c38562..422baddfc5 100644 --- a/jsapp/js/app.es6 +++ b/jsapp/js/app.es6 @@ -102,9 +102,6 @@ class App extends React.Component { case 'EDGE': document.body.classList.toggle('hide-edge') break - case 'CLOSE_MODAL': - stores.pageState.hideModal() - break } } getChildContext() { diff --git a/jsapp/js/keymap.js b/jsapp/js/keymap.js index ce7a12da6e..1a9c1649a3 100644 --- a/jsapp/js/keymap.js +++ b/jsapp/js/keymap.js @@ -1,6 +1,5 @@ export default { APP_SHORTCUTS: { - EDGE: 'alt+e', - CLOSE_MODAL: 'esc', + EDGE: 'alt+e' }, } diff --git a/jsapp/js/ui.es6 b/jsapp/js/ui.es6 index f0ba5a8698..8bbb6ee23e 100644 --- a/jsapp/js/ui.es6 +++ b/jsapp/js/ui.es6 @@ -50,6 +50,17 @@ class Modal extends React.Component { super(props); autoBind(this); } + componentDidMount() { + document.addEventListener('keydown', this.escFunction); + } + componentWillUnmount() { + document.removeEventListener('keydown', this.escFunction); + } + escFunction (evt) { + if (evt.keyCode === 27 || evt.key === 'Escape') { + this.props.onClose.call(evt); + } + } backdropClick (evt) { if (evt.currentTarget === evt.target) { this.props.onClose.call(evt); From bea516f6c6ea651f28df079239565a31cbac991f Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:01:37 +0200 Subject: [PATCH 3/8] back button confirm and text changes --- jsapp/js/components/modal.es6 | 6 +-- .../modalForms/translationTable.es6 | 42 ++++++++++++------- 2 files changed, 29 insertions(+), 19 deletions(-) diff --git a/jsapp/js/components/modal.es6 b/jsapp/js/components/modal.es6 index d61fe86d34..34a4f49fa4 100644 --- a/jsapp/js/components/modal.es6 +++ b/jsapp/js/components/modal.es6 @@ -81,12 +81,12 @@ class Modal extends React.Component { break; case MODAL_TYPES.FORM_LANGUAGES: - this.setModalTitle(t('Manage languages')); + this.setModalTitle(t('Manage Languages')); break; case MODAL_TYPES.FORM_TRANSLATIONS_TABLE: this.setState({ - title: t('Translations table'), + title: t('Translations Table'), modalClass: 'modal--large' }); break; @@ -158,7 +158,7 @@ class Modal extends React.Component { onModalClose(evt) { if (this.props.params.type === MODAL_TYPES.FORM_TRANSLATIONS_TABLE) { this.displaySafeCloseConfirm( - t('Close Translations table?'), + t('Close Translations Table?'), t('You will lose all unsaved changes.') ); return false; diff --git a/jsapp/js/components/modalForms/translationTable.es6 b/jsapp/js/components/modalForms/translationTable.es6 index 84db79622c..e25dfd8ddc 100644 --- a/jsapp/js/components/modalForms/translationTable.es6 +++ b/jsapp/js/components/modalForms/translationTable.es6 @@ -1,11 +1,10 @@ import React from 'react' import ReactTable from 'react-table' import TextareaAutosize from 'react-autosize-textarea' - +import alertify from 'alertifyjs' import bem from 'js/bem' import actions from 'js/actions' import stores from 'js/stores' - import {MODAL_TYPES} from 'js/constants' import {t} from 'utils' @@ -59,7 +58,7 @@ export class TranslationTable extends React.Component { minWidth: 130, Cell: cellInfo => cellInfo.original.original },{ - Header: `${translations[langIndex]} ${t('Translation')}`, + Header: t('##lang## translation').replace('##lang##', translations[langIndex]), accessor: 'translation', className: 'translation', Cell: cellInfo => ( @@ -106,23 +105,31 @@ export class TranslationTable extends React.Component { this.markSaveButtonPending(); actions.resources.updateAsset( this.props.asset.uid, - {content: JSON.stringify(content)}, { - onComplete: () => { - this.markSaveButtonIdle(); - }, - onFailed: () => { - this.markSaveButtonIdle(); - } + content: JSON.stringify(content) + }, + { + onComplete: this.markSaveButtonIdle.bind(this), + onFailed: this.markSaveButtonIdle.bind(this) } ); } - showManageLanguagesModal() { - stores.pageState.switchModal({ - type: MODAL_TYPES.FORM_LANGUAGES, - asset: this.props.asset - }); + showManageLanguagesModalConfirm() { + const dialog = alertify.dialog('confirm'); + const opts = { + title: t('Go back?'), + message: t('You will lose all unsaved changes.'), + labels: {ok: t('Go back'), cancel: t('Cancel')}, + onok: () => { + stores.pageState.switchModal({ + type: MODAL_TYPES.FORM_LANGUAGES, + asset: this.props.asset + }); + }, + oncancel: dialog.destroy + }; + dialog.set(opts).show(); } render () { @@ -147,7 +154,10 @@ export class TranslationTable extends React.Component { - + {t('Back')} From c1bc845304ba87ff91b47d2248f451f97a72c6f4 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:19:38 +0200 Subject: [PATCH 4/8] remove unnecessary return --- jsapp/js/components/modal.es6 | 1 - 1 file changed, 1 deletion(-) diff --git a/jsapp/js/components/modal.es6 b/jsapp/js/components/modal.es6 index 34a4f49fa4..5885e50f5a 100644 --- a/jsapp/js/components/modal.es6 +++ b/jsapp/js/components/modal.es6 @@ -161,7 +161,6 @@ class Modal extends React.Component { t('Close Translations Table?'), t('You will lose all unsaved changes.') ); - return false; } else { stores.pageState.hideModal(); } From 9483f2a69732be72beb24962715722e8a4c2d3a7 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:22:45 +0200 Subject: [PATCH 5/8] custom save button label for unsaved changes --- .../modalForms/translationTable.es6 | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/jsapp/js/components/modalForms/translationTable.es6 b/jsapp/js/components/modalForms/translationTable.es6 index e25dfd8ddc..feb2198521 100644 --- a/jsapp/js/components/modalForms/translationTable.es6 +++ b/jsapp/js/components/modalForms/translationTable.es6 @@ -8,13 +8,17 @@ import stores from 'js/stores' import {MODAL_TYPES} from 'js/constants' import {t} from 'utils' -const SAVE_CHANGES_BUTTON_TEXT_DEFAULT = t('Save Changes'); +const SAVE_BUTTON_TEXT = { + DEFAULT: t('Save Changes'), + UNSAVED: t('* Save Changes'), + PENDING: t('Saving…') +} export class TranslationTable extends React.Component { constructor(props){ super(props); this.state = { - saveChangesButtonText: SAVE_CHANGES_BUTTON_TEXT_DEFAULT, + saveChangesButtonText: SAVE_BUTTON_TEXT.DEFAULT, isSaveChangesButtonPending: false, tableData: [] }; @@ -67,6 +71,7 @@ export class TranslationTable extends React.Component { const data = [...this.state.tableData]; data[cellInfo.index].value = e.target.value; this.setState({ data }); + this.markSaveButtonUnsaved(); }} value={this.state.tableData[cellInfo.index].value || ''}/> ) @@ -74,18 +79,25 @@ export class TranslationTable extends React.Component { ]; } + markSaveButtonUnsaved() { + this.setState({ + saveChangesButtonText: SAVE_BUTTON_TEXT.UNSAVED, + isSaveChangesButtonPending: false + }); + } + markSaveButtonPending() { this.setState({ - saveChangesButtonText: t('Saving…'), - isSaveChangesButtonPending: true, - }) + saveChangesButtonText: SAVE_BUTTON_TEXT.PENDING, + isSaveChangesButtonPending: true + }); } markSaveButtonIdle() { this.setState({ - saveChangesButtonText: SAVE_CHANGES_BUTTON_TEXT_DEFAULT, - isSaveChangesButtonPending: false, - }) + saveChangesButtonText: SAVE_BUTTON_TEXT.DEFAULT, + isSaveChangesButtonPending: false + }); } saveChanges() { @@ -110,7 +122,7 @@ export class TranslationTable extends React.Component { }, { onComplete: this.markSaveButtonIdle.bind(this), - onFailed: this.markSaveButtonIdle.bind(this) + onFailed: this.markSaveButtonUnsaved.bind(this) } ); } From 79d6c72bbb7e12769fbdca976be0a99f2d3e1ad7 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:27:14 +0200 Subject: [PATCH 6/8] add back confirm only for unsaved changes --- .../modalForms/translationTable.es6 | 42 ++++++++++++------- 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/jsapp/js/components/modalForms/translationTable.es6 b/jsapp/js/components/modalForms/translationTable.es6 index feb2198521..f9dddd7f5b 100644 --- a/jsapp/js/components/modalForms/translationTable.es6 +++ b/jsapp/js/components/modalForms/translationTable.es6 @@ -19,6 +19,7 @@ export class TranslationTable extends React.Component { super(props); this.state = { saveChangesButtonText: SAVE_BUTTON_TEXT.DEFAULT, + hasUnsavedChanges: false, isSaveChangesButtonPending: false, tableData: [] }; @@ -82,6 +83,7 @@ export class TranslationTable extends React.Component { markSaveButtonUnsaved() { this.setState({ saveChangesButtonText: SAVE_BUTTON_TEXT.UNSAVED, + hasUnsavedChanges: true, isSaveChangesButtonPending: false }); } @@ -89,6 +91,7 @@ export class TranslationTable extends React.Component { markSaveButtonPending() { this.setState({ saveChangesButtonText: SAVE_BUTTON_TEXT.PENDING, + hasUnsavedChanges: true, isSaveChangesButtonPending: true }); } @@ -96,6 +99,7 @@ export class TranslationTable extends React.Component { markSaveButtonIdle() { this.setState({ saveChangesButtonText: SAVE_BUTTON_TEXT.DEFAULT, + hasUnsavedChanges: false, isSaveChangesButtonPending: false }); } @@ -127,21 +131,27 @@ export class TranslationTable extends React.Component { ); } - showManageLanguagesModalConfirm() { - const dialog = alertify.dialog('confirm'); - const opts = { - title: t('Go back?'), - message: t('You will lose all unsaved changes.'), - labels: {ok: t('Go back'), cancel: t('Cancel')}, - onok: () => { - stores.pageState.switchModal({ - type: MODAL_TYPES.FORM_LANGUAGES, - asset: this.props.asset - }); - }, - oncancel: dialog.destroy - }; - dialog.set(opts).show(); + onBack() { + if (this.state.hasUnsavedChanges) { + const dialog = alertify.dialog('confirm'); + const opts = { + title: t('Go back?'), + message: t('You will lose all unsaved changes.'), + labels: {ok: t('Go back'), cancel: t('Cancel')}, + onok: this.showManageLanguagesModal.bind(this), + oncancel: dialog.destroy + }; + dialog.set(opts).show(); + } else { + this.showManageLanguagesModal(); + } + } + + showManageLanguagesModal() { + stores.pageState.switchModal({ + type: MODAL_TYPES.FORM_LANGUAGES, + asset: this.props.asset + }); } render () { @@ -168,7 +178,7 @@ export class TranslationTable extends React.Component { {t('Back')} From 9a80eed9faee67983a5cf0c126b6f087456b64dc Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:46:35 +0200 Subject: [PATCH 7/8] fix capitalization --- jsapp/js/components/assetrow.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsapp/js/components/assetrow.es6 b/jsapp/js/components/assetrow.es6 index e3f82c550d..ef717a1f2d 100644 --- a/jsapp/js/components/assetrow.es6 +++ b/jsapp/js/components/assetrow.es6 @@ -361,7 +361,7 @@ class AssetRow extends React.Component { data-asset-uid={this.props.uid} > - {t('Manage translations')} + {t('Manage Translations')} } {this.props.downloads.map((dl)=>{ From 2b92e341f00ec951003329afea8a88d0812fef82 Mon Sep 17 00:00:00 2001 From: Leszek Pietrzak Date: Fri, 28 Sep 2018 09:48:21 +0200 Subject: [PATCH 8/8] fix undefined asset header error --- jsapp/js/components/header.es6 | 6 +++++- jsapp/js/components/modalForms/translationSettings.es6 | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/jsapp/js/components/header.es6 b/jsapp/js/components/header.es6 index 6e27957f92..7c278da12c 100644 --- a/jsapp/js/components/header.es6 +++ b/jsapp/js/components/header.es6 @@ -228,7 +228,11 @@ class MainHeader extends Reflux.Component { userCanEditAsset = this.userCan('change_asset', this.state.asset); const formTitleNameMods = []; - if (typeof this.state.asset.name === 'string' && this.state.asset.name.length > 125) { + if ( + this.state.asset && + typeof this.state.asset.name === 'string' && + this.state.asset.name.length > 125 + ) { formTitleNameMods.push('long'); } diff --git a/jsapp/js/components/modalForms/translationSettings.es6 b/jsapp/js/components/modalForms/translationSettings.es6 index ad4c399aa9..f2a9620de7 100644 --- a/jsapp/js/components/modalForms/translationSettings.es6 +++ b/jsapp/js/components/modalForms/translationSettings.es6 @@ -41,7 +41,11 @@ export class TranslationSettings extends React.Component { this.listenTo(stores.asset, this.onAssetsChange); if (!this.state.asset && this.state.assetUid) { - stores.allAssets.whenLoaded(this.props.assetUid, this.onAssetChange); + if (stores.asset.data[this.state.assetUid]) { + this.onAssetChange(stores.asset.data[this.state.assetUid]); + } else { + stores.allAssets.whenLoaded(this.props.assetUid, this.onAssetChange); + } } } onAssetChange(asset) {