Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117874425
D3551.1775333403.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
77 KB
Referenced Files
None
Subscribers
None
D3551.1775333403.diff
View Options
diff --git a/src/resources/js/app.js b/src/resources/js/app.js
--- a/src/resources/js/app.js
+++ b/src/resources/js/app.js
@@ -257,7 +257,7 @@
dialog = form.$el
}
- dialog.__vue__.showDialog()
+ dialog.__vue__.show()
},
statusClass(obj) {
if (obj.isDeleted) {
diff --git a/src/resources/themes/app.scss b/src/resources/themes/app.scss
--- a/src/resources/themes/app.scss
+++ b/src/resources/themes/app.scss
@@ -277,6 +277,17 @@
}
}
+.modal {
+ .modal-dialog,
+ .modal-content {
+ max-height: calc(100vh - 3.5rem);
+ }
+
+ .modal-body {
+ overflow: auto !important;
+ }
+}
+
#status-box {
background-color: lighten($green, 35);
diff --git a/src/resources/vue/Admin/User.vue b/src/resources/vue/Admin/User.vue
--- a/src/resources/vue/Admin/User.vue
+++ b/src/resources/vue/Admin/User.vue
@@ -197,7 +197,7 @@
¹ {{ $t('user.discount-hint') }}: {{ discount }}% - {{ discount_description }}
</small>
<div class="mt-2 buttons">
- <btn class="btn-danger" id="reset2fa" v-if="has2FA" @click="reset2FADialog">{{ $t('user.reset-2fa') }}</btn>
+ <btn class="btn-danger" id="reset2fa" v-if="has2FA" @click="$refs.reset2faDialog.show()">{{ $t('user.reset-2fa') }}</btn>
<btn class="btn-secondary" id="addbetasku" v-if="!hasBeta" @click="addBetaSku">{{ $t('user.add-beta') }}</btn>
</div>
</div>
@@ -257,102 +257,50 @@
</div>
</div>
- <div id="discount-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('user.discount-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>
- <select v-model="wallet.discount_id" class="form-select">
- <option value="">- {{ $t('form.none') }} -</option>
- <option v-for="item in discounts" :value="item.id" :key="item.id">{{ item.label }}</option>
- </select>
- </p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-primary modal-action" @click="submitDiscount()" icon="check">{{ $t('btn.submit') }}</btn>
- </div>
- </div>
+ <modal-dialog id="discount-dialog" ref="discountDialog" :title="$t('user.discount-title')" @click="submitDiscount()" :buttons="['submit']">
+ <div>
+ <select v-model="wallet.discount_id" class="form-select">
+ <option value="">- {{ $t('form.none') }} -</option>
+ <option v-for="item in discounts" :value="item.id" :key="item.id">{{ item.label }}</option>
+ </select>
</div>
- </div>
+ </modal-dialog>
- <div id="email-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('user.ext-email') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>
- <input v-model="external_email" name="external_email" class="form-control">
- </p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-primary modal-action" @click="submitEmail()" icon="check">{{ $t('btn.submit') }}</btn>
- </div>
- </div>
+ <modal-dialog id="email-dialog" ref="emailDialog" :title="$t('user.ext-email')" @click="submitEmail()" :buttons="['submit']">
+ <div>
+ <input v-model="external_email" name="external_email" class="form-control">
</div>
- </div>
-
- <div id="oneoff-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t(oneoff_negative ? 'user.add-penalty-title' : 'user.add-bonus-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <form data-validation-prefix="oneoff_">
- <div class="row mb-3">
- <label for="oneoff_amount" class="col-form-label">{{ $t('form.amount') }}</label>
- <div class="input-group">
- <input type="text" class="form-control" id="oneoff_amount" v-model="oneoff_amount" required>
- <span class="input-group-text">{{ wallet.currency }}</span>
- </div>
- </div>
- <div class="row">
- <label for="oneoff_description" class="col-form-label">{{ $t('form.description') }}</label>
- <input class="form-control" id="oneoff_description" v-model="oneoff_description" required>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-primary modal-action" @click="submitOneOff()" icon="check">{{ $t('btn.submit') }}</btn>
+ </modal-dialog>
+
+ <modal-dialog id="oneoff-dialog" ref="oneoffDialog" @click="submitOneOff()" :buttons="['submit']"
+ :title="$t(oneoff_negative ? 'user.add-penalty-title' : 'user.add-bonus-title')"
+ >
+ <form data-validation-prefix="oneoff_">
+ <div class="mb-3">
+ <label for="oneoff_amount" class="form-label">{{ $t('form.amount') }}</label>
+ <div class="input-group">
+ <input type="text" class="form-control" id="oneoff_amount" v-model="oneoff_amount" required>
+ <span class="input-group-text">{{ wallet.currency }}</span>
</div>
</div>
- </div>
- </div>
-
- <div id="reset-2fa-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('user.reset-2fa-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>{{ $t('user.2fa-hint1') }}</p>
- <p>{{ $t('user.2fa-hint2') }}</p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-danger modal-action" @click="reset2FA()">{{ $t('btn.reset') }}</btn>
- </div>
+ <div>
+ <label for="oneoff_description" class="form-label">{{ $t('form.description') }}</label>
+ <input class="form-control" id="oneoff_description" v-model="oneoff_description" required>
</div>
- </div>
- </div>
+ </form>
+ </modal-dialog>
+
+ <modal-dialog id="reset-2fa-dialog" ref="reset2faDialog" :title="$t('user.reset-2fa-title')" @click="reset2FA()"
+ :buttons="[{className: 'btn-danger modal-action', label: 'btn.reset'}]"
+ >
+ <p>{{ $t('user.2fa-hint1') }}</p>
+ <p>{{ $t('user.2fa-hint2') }}</p>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
+ import ModalDialog from '../Widgets/ModalDialog'
import TransactionLog from '../Widgets/TransactionLog'
import { ListTable } from '../Widgets/ListTools'
import { default as DistlistList } from '../Distlist/ListWidget'
@@ -375,6 +323,7 @@
DistlistList,
DomainList,
ListTable,
+ ModalDialog,
ResourceList,
SharedFolderList,
TransactionLog,
@@ -535,6 +484,13 @@
},
mounted() {
$(this.$el).find('ul.nav-tabs a').on('click', this.$root.tab)
+
+ this.$refs.discountDialog.events({
+ shown: () => {
+ // Note: Vue v-model is strict, convert null to a string
+ this.wallet.discount_id = this.wallet.discount_id || ''
+ }
+ })
},
methods: {
addBetaSku() {
@@ -560,19 +516,7 @@
this.oneOffDialog(false)
},
discountEdit() {
- if (!this.discount_dialog) {
- const dialog = $('#discount-dialog')[0]
-
- dialog.addEventListener('shown.bs.modal', e => {
- $(dialog).find('select').focus()
- // Note: Vue v-model is strict, convert null to a string
- this.wallet.discount_id = this.wallet_discount_id || ''
- })
-
- this.discount_dialog = new Modal(dialog)
- }
-
- this.discount_dialog.show()
+ this.$refs.discountDialog.show()
if (!this.discounts.length) {
// Fetch discounts
@@ -585,18 +529,7 @@
emailEdit() {
this.external_email = this.user.external_email
this.$root.clearFormValidation($('#email-dialog'))
-
- if (!this.email_dialog) {
- const dialog = $('#email-dialog')[0]
-
- dialog.addEventListener('shown.bs.modal', e => {
- $(dialog).find('input').focus()
- })
-
- this.email_dialog = new Modal(dialog)
- }
-
- this.email_dialog.show()
+ this.$refs.emailDialog.show()
},
setMandateState() {
let mandate = this.wallet.mandate
@@ -610,19 +543,7 @@
},
oneOffDialog(negative) {
this.oneoff_negative = negative
-
- if (!this.oneoff_dialog) {
- const dialog = $('#oneoff-dialog')[0]
-
- dialog.addEventListener('shown.bs.modal', () => {
- this.$root.clearFormValidation(dialog)
- $(dialog).find('#oneoff_amount').focus()
- })
-
- this.oneoff_dialog = new Modal(dialog)
- }
-
- this.oneoff_dialog.show()
+ this.$refs.oneoffDialog.show()
},
penalizeDialog() {
this.oneOffDialog(true)
@@ -633,7 +554,7 @@
this.$nextTick(() => { this.walletReload = false })
},
reset2FA() {
- new Modal('#reset-2fa-dialog').hide()
+ this.$refs.reset2faDialog.hide()
axios.post('/api/v4/users/' + this.user.id + '/reset2FA')
.then(response => {
if (response.data.status == 'success') {
@@ -643,11 +564,8 @@
}
})
},
- reset2FADialog() {
- new Modal('#reset-2fa-dialog').show()
- },
submitDiscount() {
- this.discount_dialog.hide()
+ this.$refs.discountDialog.hide()
axios.put('/api/v4/wallets/' + this.user.wallets[0].id, { discount: this.wallet.discount_id })
.then(response => {
@@ -671,7 +589,7 @@
axios.put('/api/v4/users/' + this.user.id, { external_email: this.external_email })
.then(response => {
if (response.data.status == 'success') {
- this.email_dialog.hide()
+ this.$refs.emailDialog.hide()
this.$toast.success(response.data.message)
this.user.external_email = this.external_email
this.external_email = null // required because of Vue
@@ -694,7 +612,7 @@
axios.post('/api/v4/wallets/' + wallet_id + '/one-off', post)
.then(response => {
if (response.data.status == 'success') {
- this.oneoff_dialog.hide()
+ this.$refs.oneoffDialog.hide()
this.$toast.success(response.data.message)
this.wallet = Object.assign({}, this.wallet, {balance: response.data.balance})
this.oneoff_amount = ''
diff --git a/src/resources/vue/Domain/Info.vue b/src/resources/vue/Domain/Info.vue
--- a/src/resources/vue/Domain/Info.vue
+++ b/src/resources/vue/Domain/Info.vue
@@ -6,7 +6,7 @@
<div class="card-body">
<div class="card-title" v-if="domain_id === 'new'">{{ $t('domain.new') }}</div>
<div class="card-title" v-else>{{ $t('form.domain') }}
- <btn class="btn-outline-danger button-delete float-end" @click="showDeleteConfirmation()" icon="trash-can">{{ $t('domain.delete') }}</btn>
+ <btn class="btn-outline-danger button-delete float-end" @click="$refs.deleteDialog.show()" icon="trash-can">{{ $t('domain.delete') }}</btn>
</div>
<div class="card-text">
<ul class="nav nav-tabs mt-3" role="tablist">
@@ -93,29 +93,18 @@
</div>
</div>
</div>
- <div id="delete-warning" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('domain.delete-domain', { domain: domain.namespace }) }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>{{ $t('domain.delete-text') }}</p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-danger modal-action" @click="deleteDomain()" icon="trash-can">{{ $t('btn.delete') }}</btn>
- </div>
- </div>
- </div>
- </div>
+
+ <modal-dialog id="delete-warning" ref="deleteDialog" @click="deleteDomain()" :buttons="['delete']" :cancel-focus="true"
+ :title="$t('domain.delete-domain', { domain: domain.namespace })"
+ >
+ <p>{{ $t('domain.delete-text') }}</p>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
import ListInput from '../Widgets/ListInput'
+ import ModalDialog from '../Widgets/ModalDialog'
import PackageSelect from '../Widgets/PackageSelect'
import StatusComponent from '../Widgets/Status'
import SubscriptionSelect from '../Widgets/SubscriptionSelect'
@@ -129,6 +118,7 @@
export default {
components: {
ListInput,
+ ModalDialog,
PackageSelect,
StatusComponent,
SubscriptionSelect
@@ -161,9 +151,6 @@
},
mounted() {
$('#namespace').focus()
- $('#delete-warning')[0].addEventListener('shown.bs.modal', event => {
- $(event.target).find('button.modal-cancel').focus()
- })
},
methods: {
confirm() {
@@ -189,10 +176,6 @@
}
})
},
- showDeleteConfirmation() {
- // Display the warning
- new Modal('#delete-warning').show()
- },
statusUpdate(domain) {
this.domain = Object.assign({}, this.domain, domain)
},
diff --git a/src/resources/vue/Meet/Room.vue b/src/resources/vue/Meet/Room.vue
--- a/src/resources/vue/Meet/Room.vue
+++ b/src/resources/vue/Meet/Room.vue
@@ -39,7 +39,7 @@
<button class="btn link-fullscreen open hidden" @click="switchFullscreen" :title="$t('meet.menu-fullscreen-exit')">
<svg-icon icon="compress"></svg-icon>
</button>
- <button class="btn link-options" v-if="isRoomOwner()" @click="roomOptions" :title="$t('meet.options')">
+ <button class="btn link-options" v-if="isRoomOwner()" @click="$refs.optionsDialog.show()" :title="$t('meet.options')">
<svg-icon icon="gear"></svg-icon>
</button>
<button class="btn link-logout" @click="logout" :title="$t('meet.menu-leave')">
@@ -121,70 +121,45 @@
<logon-form id="meet-auth" class="hidden" :dashboard="false" @success="authSuccess"></logon-form>
- <div id="leave-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('meet.leave-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>{{ $t('meet.leave-body') }}</p>
- </div>
- <div class="modal-footer">
- <btn class="btn-danger modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</btn>
- </div>
+ <modal-dialog id="leave-dialog" ref="leaveDialog" :title="$t('meet.leave-title')">
+ <p>{{ $t('meet.leave-body') }}</p>
+ </modal-dialog>
+
+ <modal-dialog id="media-setup-dialog" ref="setupDialog" :title="$t('meet.media-title')">
+ <form class="media-setup-form">
+ <div class="media-setup-preview"></div>
+ <div class="input-group mt-2">
+ <label for="setup-mic" class="input-group-text mb-0" :title="$t('meet.mic')">
+ <svg-icon icon="microphone"></svg-icon>
+ </label>
+ <select class="form-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange">
+ <option value="">{{ $t('form.none') }}</option>
+ <option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
+ </select>
</div>
- </div>
- </div>
-
- <div id="media-setup-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('meet.media-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <form class="media-setup-form">
- <div class="media-setup-preview"></div>
- <div class="input-group mt-2">
- <label for="setup-mic" class="input-group-text mb-0" :title="$t('meet.mic')">
- <svg-icon icon="microphone"></svg-icon>
- </label>
- <select class="form-select" id="setup-mic" v-model="microphone" @change="setupMicrophoneChange">
- <option value="">{{ $t('form.none') }}</option>
- <option v-for="mic in setup.microphones" :value="mic.deviceId" :key="mic.deviceId">{{ mic.label }}</option>
- </select>
- </div>
- <div class="input-group mt-2">
- <label for="setup-cam" class="input-group-text mb-0" :title="$t('meet.cam')">
- <svg-icon icon="video"></svg-icon>
- </label>
- <select class="form-select" id="setup-cam" v-model="camera" @change="setupCameraChange">
- <option value="">{{ $t('form.none') }}</option>
- <option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
- </select>
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</btn>
- </div>
+ <div class="input-group mt-2">
+ <label for="setup-cam" class="input-group-text mb-0" :title="$t('meet.cam')">
+ <svg-icon icon="video"></svg-icon>
+ </label>
+ <select class="form-select" id="setup-cam" v-model="camera" @change="setupCameraChange">
+ <option value="">{{ $t('form.none') }}</option>
+ <option v-for="cam in setup.cameras" :value="cam.deviceId" :key="cam.deviceId">{{ cam.label }}</option>
+ </select>
</div>
- </div>
- </div>
+ </form>
+ </modal-dialog>
- <room-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate"></room-options>
- <room-stats ref="roomStatsDialog" :stats="stats" :room="room"></room-stats>
+ <room-options v-if="session.config" :config="session.config" :room="room" @config-update="configUpdate" ref="optionsDialog"></room-options>
+ <room-stats ref="statsDialog" :room="room"></room-stats>
</div>
</template>
<script>
- import { Modal, Dropdown } from 'bootstrap'
+ import { Dropdown } from 'bootstrap'
import { Media } from '../../js/meet/media.js'
import { Room as Meet } from '../../js/meet/room.js'
import { Roles } from '../../js/meet/constants.js'
+ import ModalDialog from '../Widgets/ModalDialog'
import StatusMessage from '../Widgets/StatusMessage'
import LogonForm from '../Login'
import RoomOptions from './RoomOptions'
@@ -214,12 +189,12 @@
)
let roomRequest
- let statsRequest
const authHeader = 'X-Meet-Auth-Token'
export default {
components: {
LogonForm,
+ ModalDialog,
RoomOptions,
RoomStats,
StatusMessage
@@ -257,7 +232,6 @@
500: 'meet.status-500'
},
session: {},
- stats: {},
audioActive: false,
videoActive: false,
chatActive: false,
@@ -279,22 +253,21 @@
this.setupSession()
// Configure dialog events
- $('#leave-dialog')[0].addEventListener('hide.bs.modal', () => {
- // FIXME: Where exactly the user should land? Currently he'll land
- // on dashboard (if he's logged in) or login form (if he's not).
-
- this.$router.push({ name: 'dashboard' })
+ this.$refs.leaveDialog.events({
+ hide: () => {
+ // FIXME: Where exactly the user should land? Currently he'll land
+ // on dashboard (if he's logged in) or login form (if he's not).
+ this.$router.push({ name: 'dashboard' })
+ }
})
- const dialog = $('#media-setup-dialog')[0]
- dialog.addEventListener('show.bs.modal', () => { this.setupSession() })
- dialog.addEventListener('hide.bs.modal', () => { this.meet.setupStop() })
-
- this.roomStatsDialog = new Modal('#room-stats-dialog')
+ this.$refs.setupDialog.events({
+ show: () => { this.setupSession() },
+ hide: () => { this.meet.setupStop() }
+ })
},
beforeDestroy() {
clearTimeout(roomRequest)
- clearInterval(statsRequest)
$('#app').removeClass('meet')
@@ -317,10 +290,6 @@
configUpdate(config) {
this.session.config = Object.assign({}, this.session.config, config)
},
- async refreshStats() {
- let stats = await this.meet.getStats()
- this.stats = stats
- },
initSession(init) {
const button = $('#join-button').prop('disabled', true)
@@ -453,7 +422,6 @@
}
clearTimeout(roomRequest)
- clearInterval(statsRequest)
this.session.nickname = this.nickname
this.session.languages = this.languages
@@ -474,7 +442,7 @@
this.session.onDestroy = event => {
// TODO: Display different message for every other reason
if (event.reason == 'session-closed' && !this.isRoomOwner()) {
- new Modal('#leave-dialog').show()
+ this.$refs.leaveDialog.show()
}
}
this.session.onUpdate = data => { this.updateSession(data) }
@@ -482,8 +450,6 @@
this.meet.joinRoom(this.session)
- this.refreshStats()
-
this.keyboardShortcuts()
},
keyboardShortcuts() {
@@ -500,7 +466,7 @@
}
// Show stats with '?' key
if (e.key == '?') {
- this.roomStats()
+ this.$refs.statsDialog.toggle(this.meet)
}
})
},
@@ -537,18 +503,6 @@
roomOptions() {
new Modal('#room-options-dialog').show()
},
- roomStats() {
- clearInterval(statsRequest)
- if (this.roomStatsDialog.visible) {
- this.roomStatsDialog.hide()
- } else {
- this.refreshStats()
- statsRequest = setInterval(() => {
- this.refreshStats()
- }, 3000)
- this.roomStatsDialog.show()
- }
- },
setupMedia() {
const dialog = $('#media-setup-dialog')[0]
@@ -556,7 +510,7 @@
$('#meet-setup').find('video,div.volume').appendTo($('.media-setup-preview', dialog))
}
- new Modal(dialog).show()
+ this.$refs.setupDialog.show()
},
async setupSession() {
this.meet.setupStart({
diff --git a/src/resources/vue/Meet/RoomOptions.vue b/src/resources/vue/Meet/RoomOptions.vue
--- a/src/resources/vue/Meet/RoomOptions.vue
+++ b/src/resources/vue/Meet/RoomOptions.vue
@@ -1,59 +1,49 @@
<template>
- <div v-if="config">
- <div id="room-options-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('meet.options') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <form id="room-options-password">
- <div id="password-input" class="input-group input-group-activable mb-2">
- <span class="input-group-text label">{{ $t('meet.password') }}:</span>
- <span v-if="config.password" id="password-input-text" class="input-group-text">{{ config.password }}</span>
- <span v-else id="password-input-text" class="input-group-text text-muted">{{ $t('meet.password-none') }}</span>
- <input type="text" :value="config.password" name="password" class="form-control rounded-start activable">
- <btn @click="passwordSave" id="password-save-btn" class="btn-outline-primary activable rounded-end">{{ $t('btn.save') }}</btn>
- <btn v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn-outline-danger rounded">{{ $t('meet.password-clear') }}</btn>
- <btn v-else @click="passwordSet" id="password-set-btn" class="btn-outline-primary rounded">{{ $t('meet.password-set') }}</btn>
- </div>
- <small class="text-muted">
- {{ $t('meet.password-text') }}
- </small>
- </form>
- <hr>
- <form id="room-options-lock">
- <div id="room-lock" class="mb-2">
- <label for="room-lock-input">{{ $t('meet.lock') }}:</label>
- <input type="checkbox" id="room-lock-input" name="lock" value="1" :checked="config.locked" @click="lockSave">
- </div>
- <small class="text-muted">
- {{ $t('meet.lock-text') }}
- </small>
- </form>
- <hr>
- <form id="room-options-nomedia">
- <div id="room-nomedia" class="mb-2">
- <label for="room-nomedia-input">{{ $t('meet.nomedia') }}:</label>
- <input type="checkbox" id="room-nomedia-input" name="lock" value="1" :checked="config.nomedia" @click="nomediaSave">
- </div>
- <small class="text-muted">
- {{ $t('meet.nomedia-text') }}
- </small>
- </form>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-action" data-bs-dismiss="modal">{{ $t('btn.close') }}</btn>
- </div>
- </div>
+ <modal-dialog v-if="config" id="room-options-dialog" ref="dialog" :title="$t('meet.options')">
+ <form id="room-options-password">
+ <div id="password-input" class="input-group input-group-activable mb-2">
+ <span class="input-group-text label">{{ $t('meet.password') }}:</span>
+ <span v-if="config.password" id="password-input-text" class="input-group-text">{{ config.password }}</span>
+ <span v-else id="password-input-text" class="input-group-text text-muted">{{ $t('meet.password-none') }}</span>
+ <input type="text" :value="config.password" name="password" class="form-control rounded-start activable">
+ <btn @click="passwordSave" id="password-save-btn" class="btn-outline-primary activable rounded-end">{{ $t('btn.save') }}</btn>
+ <btn v-if="config.password" id="password-clear-btn" @click="passwordClear" class="btn-outline-danger rounded">{{ $t('meet.password-clear') }}</btn>
+ <btn v-else @click="passwordSet" id="password-set-btn" class="btn-outline-primary rounded">{{ $t('meet.password-set') }}</btn>
</div>
- </div>
- </div>
+ <small class="text-muted">
+ {{ $t('meet.password-text') }}
+ </small>
+ </form>
+ <hr>
+ <form id="room-options-lock">
+ <div id="room-lock" class="mb-2">
+ <label for="room-lock-input">{{ $t('meet.lock') }}:</label>
+ <input type="checkbox" id="room-lock-input" name="lock" value="1" :checked="config.locked" @click="lockSave">
+ </div>
+ <small class="text-muted">
+ {{ $t('meet.lock-text') }}
+ </small>
+ </form>
+ <hr>
+ <form id="room-options-nomedia">
+ <div id="room-nomedia" class="mb-2">
+ <label for="room-nomedia-input">{{ $t('meet.nomedia') }}:</label>
+ <input type="checkbox" id="room-nomedia-input" name="lock" value="1" :checked="config.nomedia" @click="nomediaSave">
+ </div>
+ <small class="text-muted">
+ {{ $t('meet.nomedia-text') }}
+ </small>
+ </form>
+ </modal-dialog>
</template>
<script>
+ import ModalDialog from '../Widgets/ModalDialog'
+
export default {
+ components: {
+ ModalDialog
+ },
props: {
config: { type: Object, default: () => null },
room: { type: String, default: () => null }
@@ -106,6 +96,9 @@
}
})
.focus()
+ },
+ show() {
+ this.$refs.dialog.show()
}
}
}
diff --git a/src/resources/vue/Meet/RoomStats.vue b/src/resources/vue/Meet/RoomStats.vue
--- a/src/resources/vue/Meet/RoomStats.vue
+++ b/src/resources/vue/Meet/RoomStats.vue
@@ -1,61 +1,78 @@
<template>
- <div id="room-stats-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content" style="max-height: 90vh">
- <div class="modal-header">
- <h5 class="modal-title">Statistics</h5>
- <button type="button" class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></button>
- </div>
- <div class="modal-body overflow-auto">
- <ul class="list-group list-group-flush">
- <li class="list-group-item">
- <span class="fw-bold">Room Id:</span>
- <pre class="text-muted m-0">{{ room }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Mediaserver Room Id:</span>
- <pre class="text-muted m-0">{{ stats.roomId }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Send Transport state:</span>
- <pre class="text-muted m-0">{{ stats.sendTransportState }}</pre>
- <pre class="text-muted m-0">{{ toText(stats.sendTransportStats) }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Receive Transport state:</span>
- <pre class="text-muted m-0">{{ stats.receiveTransportState }}</pre>
- <pre class="text-muted m-0">{{ toText(stats.receiveTransportStats) }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Consumers:</span>
- <pre class="text-muted m-0">{{ toText(stats.consumerStats) }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Camera Producer:</span>
- <pre class="text-muted m-0">{{ toText(stats.camProducerStats) }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Mic Producer:</span>
- <pre class="text-muted m-0">{{ toText(stats.micProducerStats) }}</pre>
- </li>
- <li class="list-group-item">
- <span class="fw-bold">Screen Producer:</span>
- <pre class="text-muted m-0">{{ toText(stats.screenProducerStats) }}</pre>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </div>
+ <modal-dialog id="room-stats-dialog" ref="dialog" title="Statistics">
+ <p>
+ <span class="fw-bold">Room Id:</span>
+ <pre class="text-muted m-0">{{ room }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Mediaserver Room Id:</span>
+ <pre class="text-muted m-0">{{ stats.roomId }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Sender Transport:</span>
+ <pre class="text-muted m-0">{{ stats.sendTransportState }}</pre>
+ <pre class="text-muted m-0">{{ toText(stats.sendTransportStats) }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Receiver Transport:</span>
+ <pre class="text-muted m-0">{{ stats.receiveTransportState }}</pre>
+ <pre class="text-muted m-0">{{ toText(stats.receiveTransportStats) }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Consumers:</span>
+ <pre class="text-muted m-0">{{ toText(stats.consumerStats) }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Camera Producer:</span>
+ <pre class="text-muted m-0">{{ toText(stats.camProducerStats) }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Mic Producer:</span>
+ <pre class="text-muted m-0">{{ toText(stats.micProducerStats) }}</pre>
+ </p>
+ <p>
+ <span class="fw-bold">Screen Producer:</span>
+ <pre class="text-muted m-0">{{ toText(stats.screenProducerStats) }}</pre>
+ </p>
+ </modal-dialog>
</template>
<script>
+ import ModalDialog from '../Widgets/ModalDialog'
+
export default {
+ components: {
+ ModalDialog
+ },
props: {
- stats: { type: Object, default: () => null },
room: { type: String, default: () => null }
},
+ data() {
+ return {
+ stats: {}
+ }
+ },
+ mounted() {
+ this.$refs.dialog.events({
+ show: () => {
+ clearInterval(this.statsRequest)
+ this.refreshStats()
+ this.statsRequest = setInterval(() => { this.refreshStats() }, 3000)
+ },
+ hide: () => {
+ clearInterval(this.statsRequest)
+ }
+ })
+ },
methods: {
+ async refreshStats() {
+ let stats = await this.meet.getStats()
+ this.stats = stats
+ },
+ toggle(meet) {
+ this.meet = meet
+ this.$refs.dialog[this.statsRequest ? 'hide' : 'show']()
+ },
toText(o) {
return JSON.stringify(o, null, ' ')
}
diff --git a/src/resources/vue/Reseller/Invitations.vue b/src/resources/vue/Reseller/Invitations.vue
--- a/src/resources/vue/Reseller/Invitations.vue
+++ b/src/resources/vue/Reseller/Invitations.vue
@@ -8,7 +8,7 @@
<div class="card-text">
<div class="mb-2 d-flex">
<list-search :placeholder="$t('invitation.search')" :on-search="searchInvitations"></list-search>
- <btn class="btn-success create-invite ms-1" @click="inviteUserDialog" icon="envelope-open-text">{{ $t('invitation.create') }}</btn>
+ <btn class="btn-success create-invite ms-1" @click="$refs.createDialog.show()" icon="envelope-open-text">{{ $t('invitation.create') }}</btn>
</div>
<list-table id="invitations-list" :list="invitations" :setup="setup">
@@ -30,39 +30,27 @@
</div>
</div>
- <div id="invite-create" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('invitation.create-title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <form>
- <p>{{ $t('invitation.create-email') }}</p>
- <div>
- <input id="email" type="text" class="form-control" name="email">
- </div>
- <div class="form-separator"><hr><span>{{ $t('form.or') }}</span></div>
- <p>{{ $t('invitation.create-csv') }}</p>
- <div>
- <input id="file" type="file" class="form-control" name="csv">
- </div>
- </form>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-primary modal-action" icon="paper-plane" @click="inviteUser()">{{ $t('invitation.send') }}</btn>
- </div>
+ <modal-dialog id="invite-create" ref="createDialog" :title="$t('invitation.create-title')" @click="inviteUser()"
+ :buttons="[{ className: 'btn-primary modal-action', icon: 'paper-plane', label: 'invitation.send' }]"
+ >
+ <form>
+ <p>{{ $t('invitation.create-email') }}</p>
+ <div>
+ <input id="email" type="text" class="form-control" name="email">
</div>
- </div>
- </div>
+ <div class="form-separator"><hr><span>{{ $t('form.or') }}</span></div>
+ <p>{{ $t('invitation.create-csv') }}</p>
+ <div>
+ <input id="file" type="file" class="form-control" name="csv">
+ </div>
+ </form>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
import ListTools from '../Widgets/ListTools'
+ import ModalDialog from '../Widgets/ModalDialog'
import { library } from '@fortawesome/fontawesome-svg-core'
@@ -73,6 +61,9 @@
)
export default {
+ components: {
+ ModalDialog
+ },
mixins: [ ListTools ],
data() {
return {
@@ -98,8 +89,12 @@
mounted() {
this.loadInvitations({ init: true })
- $('#invite-create')[0].addEventListener('shown.bs.modal', event => {
- $('input', event.target).first().focus()
+ this.$refs.createDialog.events({
+ show: (event) => {
+ const form = $(event.target).find('form')
+ form.get(0).reset()
+ this.fileChange({ target: form.find('#file')[0] }) // resets file input label
+ }
})
},
methods: {
@@ -146,7 +141,7 @@
axios.post('/api/v4/invitations', post, params)
.then(response => {
if (response.data.status == 'success') {
- this.dialog.hide()
+ this.$refs.createDialog.hide()
this.$toast.success(response.data.message)
if (response.data.count) {
this.loadInvitations({ reset: true })
@@ -154,17 +149,6 @@
}
})
},
- inviteUserDialog() {
- const dialog = $('#invite-create')[0]
- const form = $('form', dialog)
-
- form.get(0).reset()
- this.fileChange({ target: form.find('#file')[0] }) // resets file input label
- this.$root.clearFormValidation(form)
-
- this.dialog = new Modal(dialog)
- this.dialog.show()
- },
loadInvitations(params) {
this.listSearch('invitations', '/api/v4/invitations', params)
},
diff --git a/src/resources/vue/User/Info.vue b/src/resources/vue/User/Info.vue
--- a/src/resources/vue/User/Info.vue
+++ b/src/resources/vue/User/Info.vue
@@ -111,29 +111,17 @@
</div>
</div>
</div>
- <div id="delete-warning" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('user.delete-email', { email: user.email }) }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>{{ $t('user.delete-text') }}</p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-danger modal-action" icon="trash-can" @click="deleteUser()">{{ $t('btn.delete') }}</btn>
- </div>
- </div>
- </div>
- </div>
+ <modal-dialog id="delete-warning" ref="deleteWarning" :buttons="['delete']" :cancel-focus="true" @click="deleteUser()"
+ :title="$t('user.delete-email', { email: user.email })"
+ >
+ <p>{{ $t('user.delete-text') }}</p>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
import ListInput from '../Widgets/ListInput'
+ import ModalDialog from '../Widgets/ModalDialog'
import PackageSelect from '../Widgets/PackageSelect'
import PasswordInput from '../Widgets/PasswordInput'
import StatusComponent from '../Widgets/Status'
@@ -148,6 +136,7 @@
export default {
components: {
ListInput,
+ ModalDialog,
PackageSelect,
PasswordInput,
StatusComponent,
@@ -195,9 +184,6 @@
},
mounted() {
$('#first_name').focus()
- $('#delete-warning')[0].addEventListener('shown.bs.modal', event => {
- $(event.target).find('button.modal-cancel').focus()
- })
},
methods: {
passwordLinkCopy() {
@@ -314,7 +300,7 @@
this.$router.push({ name: 'profile-delete' })
} else {
// Display the warning
- new Modal('#delete-warning').show()
+ this.$refs.deleteWarning.show()
}
}
}
diff --git a/src/resources/vue/Wallet.vue b/src/resources/vue/Wallet.vue
--- a/src/resources/vue/Wallet.vue
+++ b/src/resources/vue/Wallet.vue
@@ -89,103 +89,75 @@
</div>
</div>
- <div id="payment-dialog" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ paymentDialogTitle }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
+ <modal-dialog id="payment-dialog" ref="paymentDialog" :title="paymentDialogTitle" @click="payment" :buttons="dialogButtons">
+ <div id="payment-method" v-if="paymentForm == 'method'">
+ <form data-validation-prefix="mandate_">
+ <div id="payment-method-selection">
+ <a v-for="method in paymentMethods" :key="method.id" @click="selectPaymentMethod(method)" href="#" :class="'card link-' + method.id">
+ <svg-icon v-if="method.icon" :icon="[method.icon.prefix, method.icon.name]" />
+ <img v-if="method.image" :src="method.image" />
+ <span class="name">{{ method.name }}</span>
+ </a>
</div>
- <div class="modal-body">
- <div id="payment-method" v-if="paymentForm == 'method'">
- <form data-validation-prefix="mandate_">
- <div id="payment-method-selection">
- <a v-for="method in paymentMethods" :key="method.id" @click="selectPaymentMethod(method)" href="#" :class="'card link-' + method.id">
- <svg-icon v-if="method.icon" :icon="[method.icon.prefix, method.icon.name]" />
- <img v-if="method.image" :src="method.image" />
- <span class="name">{{ method.name }}</span>
- </a>
- </div>
- </form>
- </div>
- <div id="manual-payment" v-if="paymentForm == 'manual'">
- <p v-if="wallet.currency != selectedPaymentMethod.currency">
- {{ $t('wallet.currency-conv', { wc: wallet.currency, pc: selectedPaymentMethod.currency }) }}
- </p>
- <p v-if="selectedPaymentMethod.id == 'banktransfer'">
- {{ $t('wallet.banktransfer-hint') }}
- </p>
- <p>
- {{ $t('wallet.payment-amount-hint') }}
- </p>
- <form id="payment-form" @submit.prevent="payment">
- <div class="input-group">
- <input type="text" class="form-control" id="amount" v-model="amount" required>
- <span class="input-group-text">{{ wallet.currency }}</span>
- </div>
- <div v-if="wallet.currency != selectedPaymentMethod.currency && !isNaN(amount)" class="alert alert-warning m-0 mt-3">
- {{ $t('wallet.payment-warning', { price: $root.price(amount * selectedPaymentMethod.exchangeRate * 100, selectedPaymentMethod.currency) }) }}
- </div>
- </form>
+ </form>
+ </div>
+ <div id="manual-payment" v-if="paymentForm == 'manual'">
+ <p v-if="wallet.currency != selectedPaymentMethod.currency">
+ {{ $t('wallet.currency-conv', { wc: wallet.currency, pc: selectedPaymentMethod.currency }) }}
+ </p>
+ <p v-if="selectedPaymentMethod.id == 'banktransfer'">
+ {{ $t('wallet.banktransfer-hint') }}
+ </p>
+ <p>
+ {{ $t('wallet.payment-amount-hint') }}
+ </p>
+ <form id="payment-form" @submit.prevent="payment">
+ <div class="input-group">
+ <input type="text" class="form-control" id="amount" v-model="amount" required>
+ <span class="input-group-text">{{ wallet.currency }}</span>
+ </div>
+ <div v-if="wallet.currency != selectedPaymentMethod.currency && !isNaN(amount)" class="alert alert-warning m-0 mt-3">
+ {{ $t('wallet.payment-warning', { price: $root.price(amount * selectedPaymentMethod.exchangeRate * 100, selectedPaymentMethod.currency) }) }}
+ </div>
+ </form>
+ </div>
+ <div id="auto-payment" v-if="paymentForm == 'auto'">
+ <form data-validation-prefix="mandate_">
+ <p>
+ {{ $t('wallet.auto-payment-hint') }}
+ </p>
+ <div class="row mb-3">
+ <label for="mandate_amount" class="col-sm-6 col-form-label">{{ $t('wallet.fill-up') }}</label>
+ <div class="col-sm-6">
+ <div class="input-group">
+ <input type="text" class="form-control" id="mandate_amount" v-model="mandate.amount" required>
+ <span class="input-group-text">{{ wallet.currency }}</span>
+ </div>
</div>
- <div id="auto-payment" v-if="paymentForm == 'auto'">
- <form data-validation-prefix="mandate_">
- <p>
- {{ $t('wallet.auto-payment-hint') }}
- </p>
- <div class="row mb-3">
- <label for="mandate_amount" class="col-sm-6 col-form-label">{{ $t('wallet.fill-up') }}</label>
- <div class="col-sm-6">
- <div class="input-group">
- <input type="text" class="form-control" id="mandate_amount" v-model="mandate.amount" required>
- <span class="input-group-text">{{ wallet.currency }}</span>
- </div>
- </div>
- </div>
- <div class="row mb-3">
- <label for="mandate_balance" class="col-sm-6 col-form-label">{{ $t('wallet.when-below') }}</label>
- <div class="col-sm-6">
- <div class="input-group">
- <input type="text" class="form-control" id="mandate_balance" v-model="mandate.balance" required>
- <span class="input-group-text">{{ wallet.currency }}</span>
- </div>
- </div>
- </div>
- <p v-if="!mandate.isValid">
- {{ $t('wallet.auto-payment-next') }}
- </p>
- <div v-if="mandate.isValid && mandate.isDisabled" class="disabled-mandate alert alert-danger m-0">
- {{ $t('wallet.auto-payment-disabled-next') }}
- </div>
- </form>
+ </div>
+ <div class="row mb-3">
+ <label for="mandate_balance" class="col-sm-6 col-form-label">{{ $t('wallet.when-below') }}</label>
+ <div class="col-sm-6">
+ <div class="input-group">
+ <input type="text" class="form-control" id="mandate_balance" v-model="mandate.balance" required>
+ <span class="input-group-text">{{ wallet.currency }}</span>
+ </div>
</div>
</div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-primary modal-action" icon="check" @click="autoPayment"
- v-if="paymentForm == 'auto' && (mandate.isValid || mandate.isPending)"
- >
- {{ $t('btn.submit') }}
- </btn>
- <btn class="btn btn-primary modal-action" icon="check" @click="autoPayment"
- v-if="paymentForm == 'auto' && !mandate.isValid && !mandate.isPending"
- >
- {{ $t('btn.continue') }}
- </btn>
- <btn class="btn-primary modal-action" icon="check" @click="payment"
- v-if="paymentForm == 'manual'"
- >
- {{ $t('btn.continue') }}
- </btn>
+ <p v-if="!mandate.isValid">
+ {{ $t('wallet.auto-payment-next') }}
+ </p>
+ <div v-if="mandate.isValid && mandate.isDisabled" class="disabled-mandate alert alert-danger m-0">
+ {{ $t('wallet.auto-payment-disabled-next') }}
</div>
- </div>
+ </form>
</div>
- </div>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
+ import ModalDialog from './Widgets/ModalDialog'
import TransactionLog from './Widgets/TransactionLog'
import PaymentLog from './Widgets/PaymentLog'
import { downloadFile } from '../js/utils'
@@ -200,6 +172,7 @@
export default {
components: {
+ ModalDialog,
TransactionLog,
PaymentLog
},
@@ -221,6 +194,27 @@
selectedPaymentMethod: null
}
},
+ computed: {
+ dialogButtons() {
+ if (this.paymentForm == 'method') {
+ return []
+ }
+
+ const button = {
+ className: 'btn-primary modal-action',
+ icon: 'check',
+ label: 'btn.submit'
+ }
+
+ if (this.paymentForm == 'manual'
+ || (this.paymentForm == 'auto' && !this.mandate.isValid && !this.mandate.isPending)
+ ) {
+ button.label = 'btn.continue'
+ }
+
+ return [ button ]
+ }
+ },
mounted() {
$('#wallet button').focus()
@@ -280,6 +274,10 @@
setTimeout(() => { $('#payment-dialog').find('#amount,#mandate_amount').focus() }, 10)
},
payment() {
+ if (this.paymentForm == 'auto') {
+ return this.autoPayment()
+ }
+
if (this.formLock) {
return
}
@@ -341,7 +339,7 @@
} else {
// an update
if (response.data.status == 'success') {
- this.dialog.hide();
+ this.$refs.paymentDialog.hide();
this.mandate = response.data
this.$toast.success(response.data.message)
}
@@ -367,8 +365,7 @@
this.nextForm = nextForm
this.paymentDialogTitle = this.$t(nextForm == 'auto' ? 'wallet.auto-payment-setup' : 'wallet.top-up')
- this.dialog = new Modal('#payment-dialog')
- this.dialog.show()
+ this.$refs.paymentDialog.show()
this.$nextTick().then(() => {
const type = nextForm == 'manual' ? 'oneoff' : 'recurring'
@@ -385,8 +382,7 @@
this.paymentDialogTitle = title
this.formLock = false
- this.dialog = new Modal('#payment-dialog')
- this.dialog.show()
+ this.$refs.paymentDialog.show()
},
receiptDownload() {
const receipt = $('#receipt-id').val()
diff --git a/src/resources/vue/Widgets/CompanionappList.vue b/src/resources/vue/Widgets/CompanionappList.vue
--- a/src/resources/vue/Widgets/CompanionappList.vue
+++ b/src/resources/vue/Widgets/CompanionappList.vue
@@ -1,35 +1,26 @@
<template>
<div>
- <btn icon="trash-can" class="btn-outline-danger button-delete float-end" @click="showDeleteConfirmation()">
+ <btn icon="trash-can" class="btn-outline-danger button-delete float-end" @click="$refs.deleteDialog.show()">
{{ $t('companion.delete') }}
</btn>
+
<list-table class="m-0" :list="entries" :setup="setup"></list-table>
<list-more v-if="hasMore" :on-click="loadMore"></list-more>
- <div id="delete-warning" class="modal" tabindex="-1" role="dialog">
- <div class="modal-dialog" role="document">
- <div class="modal-content">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('companion.remove-devices') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <p>{{ $t('companion.remove-devices-text') }}</p>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn class="btn-danger modal-action" data-bs-dismiss="modal" @click="removeDevices()" icon="trash-can">{{ $t('btn.delete') }}</btn>
- </div>
- </div>
- </div>
- </div>
+
+ <modal-dialog id="delete-warning" ref="deleteDialog" :title="$t('companion.remove-devices')" @click="removeDevices()" :buttons="['delete']" :cancel-focus="true">
+ <p>{{ $t('companion.remove-devices-text') }}</p>
+ </modal-dialog>
</div>
</template>
<script>
- import { Modal } from 'bootstrap'
import ListTools from './ListTools'
+ import ModalDialog from './ModalDialog'
export default {
+ components: {
+ ModalDialog
+ },
mixins: [ ListTools ],
data() {
return {
@@ -51,9 +42,6 @@
},
mounted() {
this.loadMore({ reset: true })
- $('#delete-warning')[0].addEventListener('shown.bs.modal', event => {
- $(event.target).find('button.modal-cancel').focus()
- })
},
methods: {
loadMore(params) {
@@ -68,11 +56,7 @@
this.loadMore({ reset: true })
})
.catch(this.$root.errorHandler)
- },
- showDeleteConfirmation() {
- // Display the warning
- new Modal('#delete-warning').show()
- },
+ }
}
}
</script>
diff --git a/src/resources/vue/Widgets/ModalDialog.vue b/src/resources/vue/Widgets/ModalDialog.vue
new file mode 100644
--- /dev/null
+++ b/src/resources/vue/Widgets/ModalDialog.vue
@@ -0,0 +1,90 @@
+<template>
+ <div class="modal" tabindex="-1" role="dialog" aria-hidden="true">
+ <div class="modal-dialog" role="document">
+ <div class="modal-content" style="max-height: 90vh">
+ <div class="modal-header">
+ <h5 class="modal-title">{{ title }}</h5>
+ <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
+ </div>
+ <div class="modal-body overflow-auto">
+ <slot></slot>
+ </div>
+ <div class="modal-footer">
+ <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t(buttons.length ? 'btn.cancel' : 'btn.close') }}</btn>
+ <btn v-for="(button, index) in buttons" :key="index"
+ :class="btnProperty(button, 'className')"
+ :icon="btnProperty(button, 'icon')"
+ :data-bs-dismiss="btnProperty(button, 'dismiss')"
+ @click="$emit('click', $event)"
+ >
+ {{ $t(btnProperty(button, 'label')) }}
+ </btn>
+ </div>
+ </div>
+ </div>
+ </div>
+</template>
+
+<script>
+ import { Modal } from 'bootstrap'
+ import { clearFormValidation } from '../../js/utils'
+
+ // Common buttons
+ const buttonTypes = {
+ 'delete': {
+ className: 'btn-danger modal-action',
+ dismiss: 'modal',
+ label: 'btn.delete',
+ icon: 'trash-can'
+ },
+ submit: {
+ className: 'btn-primary modal-action',
+ label: 'btn.submit',
+ icon: 'check'
+ },
+ }
+
+ export default {
+ props: {
+ buttons: { type: Array, default: () => [] },
+ cancelFocus: { type: Boolean, default: false },
+ title: { type: String, default: '' }
+ },
+ mounted() {
+ this.$el.addEventListener('shown.bs.modal', event => {
+ clearFormValidation(event.target)
+
+ if (this.cancelFocus) {
+ $(event.target).find('button.modal-cancel').focus()
+ } else {
+ $(event.target).find('input,select').first().focus()
+ }
+ })
+
+ this.dialog = new Modal(this.$el)
+ },
+ methods: {
+ btnProperty(button, property) {
+ const isString = typeof button == 'string'
+ if (!isString && property in button) {
+ return button[property]
+ }
+
+ if (isString && button in buttonTypes && property in buttonTypes[button]) {
+ return buttonTypes[button][property]
+ }
+ },
+ events(events) {
+ for (const name in events) {
+ this.$el.addEventListener(name + '.bs.modal', events[name])
+ }
+ },
+ hide() {
+ this.dialog.hide()
+ },
+ show() {
+ this.dialog.show()
+ }
+ }
+ }
+</script>
diff --git a/src/resources/vue/Widgets/SupportForm.vue b/src/resources/vue/Widgets/SupportForm.vue
--- a/src/resources/vue/Widgets/SupportForm.vue
+++ b/src/resources/vue/Widgets/SupportForm.vue
@@ -1,47 +1,38 @@
<template>
- <div class="modal" id="support-dialog" tabindex="-1" role="dialog" aria-hidden="true">
- <div class="modal-dialog" role="document">
- <form class="modal-content" @submit.prevent="submit">
- <div class="modal-header">
- <h5 class="modal-title">{{ $t('support.title') }}</h5>
- <btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn>
- </div>
- <div class="modal-body">
- <div class="mb-3">
- <label for="support-user" class="form-label">{{ $t('support.id') }}</label>
- <input id="support-user" type="text" class="form-control" :placeholder="$t('support.id-pl')" v-model="user" />
- <small class="text-muted">{{ $t('support.id-hint') }}</small>
- </div>
- <div class="mb-3">
- <label for="support-name" class="form-label">{{ $t('support.name') }}</label>
- <input id="support-name" type="text" class="form-control" :placeholder="$t('support.name-pl')" v-model="name" />
- </div>
- <div class="mb-3">
- <label for="support-email" class="form-label">{{ $t('support.email') }}</label>
- <input id="support-email" type="email" class="form-control" :placeholder="$t('support.email-pl')" v-model="email" required />
- </div>
- <div class="mb-3">
- <label for="support-summary" class="form-label">{{ $t('support.summary') }}</label>
- <input id="support-summary" type="text" class="form-control" :placeholder="$t('support.summary-pl')" v-model="summary" required />
- </div>
- <div>
- <label for="support-body" class="form-label">{{ $t('support.expl') }}</label>
- <textarea id="support-body" class="form-control" rows="5" v-model="body" required></textarea>
- </div>
- </div>
- <div class="modal-footer">
- <btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn>
- <btn type="submit" class="btn-primary modal-action" icon="check">{{ $t('btn.submit') }}</btn>
- </div>
- </form>
- </div>
- </div>
+ <modal-dialog id="support-dialog" ref="dialog" :title="$t('support.title')" @click="submit" :buttons="['submit']">
+ <form>
+ <div class="mb-3">
+ <label for="support-user" class="form-label">{{ $t('support.id') }}</label>
+ <input id="support-user" type="text" class="form-control" :placeholder="$t('support.id-pl')" v-model="user" />
+ <small class="text-muted">{{ $t('support.id-hint') }}</small>
+ </div>
+ <div class="mb-3">
+ <label for="support-name" class="form-label">{{ $t('support.name') }}</label>
+ <input id="support-name" type="text" class="form-control" :placeholder="$t('support.name-pl')" v-model="name" />
+ </div>
+ <div class="mb-3">
+ <label for="support-email" class="form-label">{{ $t('support.email') }}</label>
+ <input id="support-email" type="email" class="form-control" :placeholder="$t('support.email-pl')" v-model="email" required />
+ </div>
+ <div class="mb-3">
+ <label for="support-summary" class="form-label">{{ $t('support.summary') }}</label>
+ <input id="support-summary" type="text" class="form-control" :placeholder="$t('support.summary-pl')" v-model="summary" required />
+ </div>
+ <div>
+ <label for="support-body" class="form-label">{{ $t('support.expl') }}</label>
+ <textarea id="support-body" class="form-control" rows="5" v-model="body" required></textarea>
+ </div>
+ </form>
+ </modal-dialog>
</template>
<script>
- import { Modal } from 'bootstrap'
+ import ModalDialog from '../Widgets/ModalDialog'
export default {
+ components: {
+ ModalDialog
+ },
data() {
return {
body: '',
@@ -52,32 +43,25 @@
}
},
mounted() {
- const dialog = this.$el
-
- dialog.addEventListener('hide.bs.modal', () => {
- this.lockForm(false)
- if (this.cancelToken) {
- this.cancelToken()
+ this.$refs.dialog.events({
+ hide: () => {
+ this.lockForm(false)
+ if (this.cancelToken) {
+ this.cancelToken()
+ this.cancelToken = null
+ }
+ },
+ show: () => {
this.cancelToken = null
}
})
-
- dialog.addEventListener('show.bs.modal', () => {
- this.cancelToken = null
- })
-
- dialog.addEventListener('shown.bs.modal', () => {
- $(dialog).find('input').first().focus()
- })
-
- this.dialog = new Modal(dialog)
},
methods: {
lockForm(lock) {
$(this.$el).find('input,textarea,.modal-action').prop('disabled', lock)
},
- showDialog() {
- this.dialog.show()
+ show() {
+ this.$refs.dialog.show()
},
submit() {
this.lockForm(true)
@@ -103,7 +87,7 @@
this.summary = ''
this.body = ''
this.lockForm(false)
- this.dialog.hide()
+ this.$refs.dialog.hide()
this.$toast.success(response.data.message)
})
.catch(error => {
diff --git a/src/tests/Browser.php b/src/tests/Browser.php
--- a/src/tests/Browser.php
+++ b/src/tests/Browser.php
@@ -279,6 +279,7 @@
if (
$entry['level'] != 'SEVERE'
|| strpos($entry['message'], 'Failed to load resource: the server responded with a status of')
+ || preg_match('/^\S+\.js [0-9]+:[0-9]+\s*$/', $entry['message'])
) {
$console[$idx] = null;
}
diff --git a/src/tests/Browser/Meet/RoomOptionsTest.php b/src/tests/Browser/Meet/RoomOptionsTest.php
--- a/src/tests/Browser/Meet/RoomOptionsTest.php
+++ b/src/tests/Browser/Meet/RoomOptionsTest.php
@@ -49,7 +49,7 @@
->click('@menu button.link-options')
->with(new Dialog('#room-options-dialog'), function (Browser $browser) use ($room) {
$browser->assertSeeIn('@title', 'Room options')
- ->assertSeeIn('@button-action', 'Close')
+ ->assertSeeIn('@button-cancel', 'Close')
->assertElementsCount('.modal-footer button', 1)
->assertSeeIn('#password-input .label', 'Password:')
->assertSeeIn('#password-input-text.text-muted', 'none')
@@ -71,7 +71,7 @@
->assertSeeIn('#password-input-text:not(.text-muted)', 'pass')
->assertSeeIn('#password-clear-btn.btn-outline-danger', 'Clear password')
->assertElementsCount('#password-input button', 1)
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame('pass', $room->fresh()->getSetting('password'));
});
@@ -107,7 +107,7 @@
->assertSeeIn('#password-input-text.text-muted', 'none')
->assertSeeIn('#password-set-btn', 'Set password')
->assertElementsCount('#password-input button', 1)
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame(null, $room->fresh()->getSetting('password'));
});
@@ -143,7 +143,7 @@
// Test setting the lock
->click('#room-lock input')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame('true', $room->fresh()->getSetting('locked'));
});
@@ -217,7 +217,7 @@
// Test setting the lock
->click('#room-lock input')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame('true', $room->fresh()->getSetting('locked'));
});
@@ -260,8 +260,8 @@
$guest->with(new Dialog('#leave-dialog'), function (Browser $browser) {
$browser->assertSeeIn('@title', 'Room closed')
->assertSeeIn('@body', "The session has been closed by the room owner.")
- ->assertMissing('@button-cancel')
- ->assertSeeIn('@button-action', 'Close');
+ ->assertMissing('@button-action')
+ ->assertSeeIn('@button-cancel', 'Close');
});
});
}
@@ -295,7 +295,7 @@
// Test enabling the option
->click('#room-nomedia input')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame('true', $room->fresh()->getSetting('nomedia'));
});
@@ -317,7 +317,7 @@
// Test enabling the option
->click('#room-nomedia input')
->assertToast(Toast::TYPE_SUCCESS, "Room configuration updated successfully.")
- ->click('@button-action');
+ ->click('@button-cancel');
$this->assertSame(null, $room->fresh()->getSetting('nomedia'));
});
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Sat, Apr 4, 8:10 PM (19 h, 40 m ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18826297
Default Alt Text
D3551.1775333403.diff (77 KB)
Attached To
Mode
D3551: Modal dialog component
Attached
Detach File
Event Timeline