diff --git a/resources/js/app.js b/resources/js/app.js index a36fd937..9f29b65d 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -20,7 +20,11 @@ const app = new Vue({ userPreferences: window.userPreferences, isDemoApp: window.isDemoApp, isTestingApp: window.isTestingApp, - prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)').matches + prefersDarkScheme: window.matchMedia('(prefers-color-scheme: dark)').matches, + spinner: { + active: false, + message: 'loading' + }, }, computed: { @@ -44,8 +48,18 @@ const app = new Vue({ methods: { setDarkScheme ({ matches }) { this.prefersDarkScheme = matches + }, + + showSpinner(message) { + this.spinner.message = message; + this.spinner.active = true; + }, + + hideSpinner() { + this.spinner.active = false; + this.spinner.message = 'loading'; } }, i18n, router, -}); \ No newline at end of file +}); diff --git a/resources/js/components/App.vue b/resources/js/components/App.vue index c3249fb5..52930527 100644 --- a/resources/js/components/App.vue +++ b/resources/js/components/App.vue @@ -7,6 +7,8 @@
{{ $t('commons.testing_do_not_post_sensitive_data') }}
+ +
@@ -15,9 +17,14 @@ \ No newline at end of file + diff --git a/resources/js/components/OtpDisplayer.vue b/resources/js/components/OtpDisplayer.vue index 2e3118d3..cc7db4f3 100644 --- a/resources/js/components/OtpDisplayer.vue +++ b/resources/js/components/OtpDisplayer.vue @@ -144,7 +144,7 @@ this.internal_account = data.account this.internal_icon = data.icon this.internal_otp_type = data.otp_type - + if( this.isHMacBased(data.otp_type) && data.counter ) { this.internal_counter = data.counter } @@ -165,13 +165,18 @@ await this.getHOTP() } else this.$router.push({ name: 'genericError', params: { err: this.$t('errors.not_a_supported_otp_type') } }); - + this.$parent.isActive = true this.focusOnOTP() } catch(error) { this.clearOTP() } + finally { + this.$root.hideSpinner(); + } + } else { + this.$root.hideSpinner(); } }, @@ -232,7 +237,7 @@ }, startTotpLoop: async function() { - + let otp = await this.getOtp() this.internal_password = otp.password @@ -258,7 +263,7 @@ // ● ● ● ● ●|● ◌ ◌ ◌ ◌ | // | | || | // | | |<-------->|--remainingTimeBeforeEndOfPeriod (for remainingTimeout) - // durationBetweenTwoDots-->|-|< || + // durationBetweenTwoDots-->|-|< || // (for dotToDotInterval) | | >||<---durationFromFirstToNextDot (for firstDotToNextOneTimeout) // | // | @@ -276,7 +281,7 @@ // We determine the position of the closest dot next to the generated_at timestamp let relativePosition = (elapsedTimeInCurrentPeriod * 10) / period let dotIndex = (Math.floor(relativePosition) +1) - + // We switch the dot on this.lastActiveDot = dots.querySelector('li:nth-child(' + dotIndex + ')'); this.lastActiveDot.setAttribute('data-is-active', true); @@ -365,4 +370,4 @@ this.stopLoop() } } - \ No newline at end of file + diff --git a/resources/js/components/Spinner.vue b/resources/js/components/Spinner.vue new file mode 100644 index 00000000..7352356b --- /dev/null +++ b/resources/js/components/Spinner.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/resources/js/views/Accounts.vue b/resources/js/views/Accounts.vue index 08abb9f2..1fb9c2c2 100644 --- a/resources/js/views/Accounts.vue +++ b/resources/js/views/Accounts.vue @@ -141,7 +141,7 @@ -
+
{{ displayService(account.service) }} @@ -186,9 +186,9 @@

-

- @@ -223,9 +223,9 @@ /** * Accounts view - * + * * route: '/account' (alias: '/') - * + * * The main view of 2FAuth that list all existing account recorded in DB. * Available feature in this view : * - {{OTP}} generation @@ -248,9 +248,9 @@ * ~ 'userPreferences.showAccountsIcons' toggle the icon visibility * ~ 'userPreferences.displayMode' change the account appearance * - * Input : + * Input : * - The 'initialEditMode' props : allows to load the view directly in Edit mode - * + * */ import Modal from '../components/Modal' @@ -289,12 +289,12 @@ return this.accounts.filter( item => { if( parseInt(this.$root.userPreferences.activeGroup) > 0 ) { - return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) || - item.account.toLowerCase().includes(this.search.toLowerCase())) && + return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) || + item.account.toLowerCase().includes(this.search.toLowerCase())) && (item.group_id == parseInt(this.$root.userPreferences.activeGroup)) } else { - return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) || + return ((item.service ? item.service.toLowerCase().includes(this.search.toLowerCase()) : false) || item.account.toLowerCase().includes(this.search.toLowerCase())) } } @@ -360,7 +360,7 @@ components: { Modal, OtpDisplayer, - draggable, + draggable }, methods: { @@ -417,11 +417,11 @@ this.selectAccount(account.id) } else { - this.$refs.OtpDisplayer.show(account.id) + this.$root.showSpinner(this.$t('commons.generating_otp')); + this.$refs.OtpDisplayer.show(account.id); } }, - /** * Select an account while in edit mode */ @@ -471,7 +471,7 @@ }) this.$notify({ type: 'is-success', text: this.$t('twofaccounts.accounts_deleted') }) }) - + // we fetch the accounts again to prevent the js collection being // desynchronize from the backend php collection this.fetchAccounts(true) @@ -529,7 +529,7 @@ if ( !objectEquals(groups, this.groups) ) { this.groups = groups - } + } this.$storage.set('groups', this.groups) }) @@ -550,7 +550,7 @@ // everything's fine }) .catch(error => { - + this.$router.push({ name: 'genericError', params: { err: error.response } }) }); } @@ -593,21 +593,21 @@ }, /** - * + * */ displayService(service) { return service ? service : this.$t('twofaccounts.no_service') }, /** - * + * */ clearSelected() { this.selectedAccounts = [] }, /** - * + * */ selectAll() { if(this.editMode) { @@ -621,7 +621,7 @@ }, /** - * + * */ sortAsc() { this.accounts.sort((a, b) => a.service > b.service ? 1 : -1) @@ -629,7 +629,7 @@ }, /** - * + * */ sortDesc() { this.accounts.sort((a, b) => a.service < b.service ? 1 : -1) @@ -637,7 +637,7 @@ }, /** - * + * */ keyListener : function(e) { if (e.key === "f" && (e.ctrlKey || e.metaKey)) { @@ -662,4 +662,4 @@ opacity: 1; /*background: hsl(0, 0%, 21%);*/ } - \ No newline at end of file + diff --git a/resources/lang/en/commons.php b/resources/lang/en/commons.php index aff6bdee..b4a8c893 100644 --- a/resources/lang/en/commons.php +++ b/resources/lang/en/commons.php @@ -49,6 +49,7 @@ 'reload' => 'Reload', 'some_data_have_changed' => 'Some data have changed. You should', 'generate' => 'Generate', + 'generating_otp' => 'Generating OTP', 'open_in_browser' => 'Open in browser', 'continue' => 'Continue', 'discard' => 'Discard', @@ -70,4 +71,4 @@ 'file' => 'File', 'or' => 'OR', 'close_the_x_page' => 'Close the {pagetitle} page', -]; \ No newline at end of file +]; diff --git a/resources/sass/app.scss b/resources/sass/app.scss index b9d42908..ba85e22c 100644 --- a/resources/sass/app.scss +++ b/resources/sass/app.scss @@ -1204,6 +1204,28 @@ footer .field.is-grouped { } } +// Themed Spinner +.spinner-container { + background-color: $white-ter; + .spinner { + color: $dark; + } +} +:root[data-theme="dark"] .spinner-container { + background-color: $black-ter; + .spinner { + color: $light; + } +} +@media (prefers-color-scheme: dark) { + :root[data-theme="system"] .spinner-container { + background-color: $black-ter; + .spinner { + color: $light; + } + } +} + .fadeInOut-enter-active { animation: fadeIn 500ms } @@ -1374,4 +1396,4 @@ footer .field.is-grouped { to { transform: translateY(-2rem); } -} \ No newline at end of file +}