Vue modals to replace window.alert/window.confirm
Apparently some iPad browsers were having their local webcam freeze after a window.confirm prompt was shown. This replaces all uses of window.confirm/window.alert with an in-app modal.
This commit is contained in:
parent
d4b69311ae
commit
9b8e7dc440
294
src/App.vue
294
src/App.vue
|
@ -5,6 +5,7 @@ import 'floating-vue/dist/style.css';
|
||||||
import { Mentionable } from 'vue-mention';
|
import { Mentionable } from 'vue-mention';
|
||||||
import EmojiPicker from 'vue3-emoji-picker';
|
import EmojiPicker from 'vue3-emoji-picker';
|
||||||
|
|
||||||
|
import AlertModal from './components/AlertModal.vue';
|
||||||
import LoginModal from './components/LoginModal.vue';
|
import LoginModal from './components/LoginModal.vue';
|
||||||
import ExplicitOpenModal from './components/ExplicitOpenModal.vue';
|
import ExplicitOpenModal from './components/ExplicitOpenModal.vue';
|
||||||
import ReportModal from './components/ReportModal.vue';
|
import ReportModal from './components/ReportModal.vue';
|
||||||
|
@ -50,6 +51,7 @@ export default {
|
||||||
EmojiPicker,
|
EmojiPicker,
|
||||||
|
|
||||||
// My components
|
// My components
|
||||||
|
AlertModal,
|
||||||
LoginModal,
|
LoginModal,
|
||||||
ExplicitOpenModal,
|
ExplicitOpenModal,
|
||||||
ReportModal,
|
ReportModal,
|
||||||
|
@ -320,6 +322,17 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Generic Alert/Confirm modal to replace native browser events.
|
||||||
|
// See also: modalAlert, modalConfirm functions.
|
||||||
|
alertModal: {
|
||||||
|
visible: false,
|
||||||
|
isConfirm: false,
|
||||||
|
title: "Alert",
|
||||||
|
icon: "",
|
||||||
|
message: "",
|
||||||
|
callback() {},
|
||||||
|
},
|
||||||
|
|
||||||
loginModal: {
|
loginModal: {
|
||||||
visible: false,
|
visible: false,
|
||||||
},
|
},
|
||||||
|
@ -1320,23 +1333,25 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mute) {
|
if (mute) {
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
`Do you want to mute ${username}? If muted, you will no longer see their ` +
|
title: `Mute ${username}`,
|
||||||
`chat messages or any DMs they send you going forward. Also, ${username} will ` +
|
icon: "fa fa-comment-slash",
|
||||||
`not be able to see whether your webcam is active until you unmute them.`
|
message: `Do you want to mute ${username}? If muted, you will no longer see their ` +
|
||||||
)) {
|
`chat messages or any DMs they send you going forward. Also, ${username} will ` +
|
||||||
return;
|
`not be able to see whether your webcam is active until you unmute them.`,
|
||||||
}
|
}).then(() => {
|
||||||
this.muted[username] = true;
|
this.muted[username] = true;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
`Do you want to remove your mute on ${username}? If you un-mute them, you ` +
|
title: `Un-mute ${username}`,
|
||||||
`will be able to see their chat messages or DMs going forward, but most importantly, ` +
|
icon: "fa fa-comment",
|
||||||
`they may be able to watch your webcam now if you are broadcasting!`,
|
message: `Do you want to remove your mute on ${username}? If you un-mute them, you ` +
|
||||||
)) {
|
`will be able to see their chat messages or DMs going forward, but most importantly, ` +
|
||||||
return;
|
`they may be able to watch your webcam now if you are broadcasting!`,
|
||||||
}
|
}).then(() => {
|
||||||
delete this.muted[username];
|
delete this.muted[username];
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hang up videos both ways.
|
// Hang up videos both ways.
|
||||||
|
@ -1871,6 +1886,31 @@ export default {
|
||||||
* Front-end web app concerns.
|
* Front-end web app concerns.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Generic window.alert replacement modal.
|
||||||
|
async modalAlert({ message, title="Alert", icon="", isConfirm=false }) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.alertModal.isConfirm = isConfirm;
|
||||||
|
this.alertModal.title = title;
|
||||||
|
this.alertModal.icon = icon;
|
||||||
|
this.alertModal.message = message;
|
||||||
|
this.alertModal.callback = () => {
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
this.alertModal.visible = true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async modalConfirm({ message, title="Confirmation", icon=""}) {
|
||||||
|
return this.modalAlert({
|
||||||
|
isConfirm: true,
|
||||||
|
message,
|
||||||
|
title,
|
||||||
|
icon,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
modalClose() {
|
||||||
|
this.alertModal.visible = false;
|
||||||
|
},
|
||||||
|
|
||||||
// Settings modal.
|
// Settings modal.
|
||||||
showSettings() {
|
showSettings() {
|
||||||
this.settingsModal.visible = true;
|
this.settingsModal.visible = true;
|
||||||
|
@ -2002,39 +2042,42 @@ export default {
|
||||||
// Validate we're in a DM currently.
|
// Validate we're in a DM currently.
|
||||||
if (this.channel.indexOf("@") !== 0) return;
|
if (this.channel.indexOf("@") !== 0) return;
|
||||||
|
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
"Do you want to close this chat thread? Your conversation history will " +
|
title: "Close conversation thread",
|
||||||
"be forgotten on your computer, but your chat partner may still have " +
|
icon: "fa fa-trash",
|
||||||
"your chat thread open on their end."
|
message: "Do you want to close this chat thread? This will remove the conversation from your view, but " +
|
||||||
)) {
|
"your chat partner may still have the conversation open on their device.",
|
||||||
return;
|
}).then(() => {
|
||||||
}
|
let channel = this.channel;
|
||||||
|
this.setChannel(this.config.channels[0].ID);
|
||||||
let channel = this.channel;
|
delete (this.channels[channel]);
|
||||||
this.setChannel(this.config.channels[0].ID);
|
delete (this.directMessageHistory[channel]);
|
||||||
delete (this.channels[channel]);
|
});
|
||||||
delete (this.directMessageHistory[channel]);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/* Take back messages (for everyone) or remove locally */
|
/* Take back messages (for everyone) or remove locally */
|
||||||
takeback(msg) {
|
takeback(msg) {
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
"Do you want to take this message back? Doing so will remove this message from everybody's view in the chat room."
|
title: "Take back message",
|
||||||
)) return;
|
icon: "fa fa-rotate-left",
|
||||||
|
message: "Do you want to take this message back? Doing so will remove this message from everybody's view in the chat room."
|
||||||
this.client.send({
|
}).then(() => {
|
||||||
|
this.client.send({
|
||||||
action: "takeback",
|
action: "takeback",
|
||||||
msgID: msg.msgID,
|
msgID: msg.msgID,
|
||||||
});
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
removeMessage(msg) {
|
removeMessage(msg) {
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
"Do you want to remove this message from your view? This will delete the message only for you, but others in this chat thread may still see it."
|
title: "Hide this message",
|
||||||
)) return;
|
icon: "fa fa-trash",
|
||||||
|
message: "Do you want to remove this message from your view? This will delete the message only for you, but others in this chat thread may still see it."
|
||||||
this.onTakeback({
|
}).then(() => {
|
||||||
msgID: msg.msgID,
|
this.onTakeback({
|
||||||
});
|
msgID: msg.msgID,
|
||||||
|
});
|
||||||
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
/* message reaction emojis */
|
/* message reaction emojis */
|
||||||
|
@ -2586,41 +2629,43 @@ export default {
|
||||||
bootUser(username) {
|
bootUser(username) {
|
||||||
// Un-boot?
|
// Un-boot?
|
||||||
if (this.isBooted(username)) {
|
if (this.isBooted(username)) {
|
||||||
if (!window.confirm(`Allow ${username} to watch your webcam again?`)) {
|
this.modalConfirm({
|
||||||
return;
|
title: "Unboot user",
|
||||||
}
|
icon: "fa fa-user-xmark",
|
||||||
|
message: `Allow ${username} to watch your webcam again?`
|
||||||
this.sendUnboot(username);
|
}).then(() => {
|
||||||
delete (this.WebRTC.booted[username]);
|
this.sendUnboot(username);
|
||||||
|
delete (this.WebRTC.booted[username]);
|
||||||
|
})
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Boot them off our webcam.
|
// Boot them off our webcam.
|
||||||
if (!window.confirm(
|
this.modalConfirm({
|
||||||
`Kick ${username} off your camera? This will also prevent them ` +
|
title: "Boot user",
|
||||||
`from seeing that your camera is active for the remainder of your ` +
|
icon: "fa fa-user-xmark",
|
||||||
`chat session.`)) {
|
message: `Kick ${username} off your camera? This will also prevent them ` +
|
||||||
return;
|
`from seeing that your camera is active for the remainder of your ` +
|
||||||
}
|
`chat session.`
|
||||||
|
}).then(() => {
|
||||||
|
this.sendBoot(username);
|
||||||
|
this.WebRTC.booted[username] = true;
|
||||||
|
|
||||||
this.sendBoot(username);
|
// Close the WebRTC peer connections.
|
||||||
this.WebRTC.booted[username] = true;
|
if (this.WebRTC.pc[username] != undefined) {
|
||||||
|
this.closeVideo(username);
|
||||||
|
}
|
||||||
|
|
||||||
// Close the WebRTC peer connections.
|
// Remove them from our list.
|
||||||
if (this.WebRTC.pc[username] != undefined) {
|
delete (this.webcam.watching[username]);
|
||||||
this.closeVideo(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove them from our list.
|
this.ChatClient(
|
||||||
delete (this.webcam.watching[username]);
|
`You have booted ${username} off your camera. They will no longer be able ` +
|
||||||
|
`to connect to your camera, or even see that your camera is active at all -- ` +
|
||||||
this.ChatClient(
|
`to them it appears as though you had turned yours off.<br><br>This will be ` +
|
||||||
`You have booted ${username} off your camera. They will no longer be able ` +
|
`in place for the remainder of your current chat session.`
|
||||||
`to connect to your camera, or even see that your camera is active at all -- ` +
|
);
|
||||||
`to them it appears as though you had turned yours off.<br><br>This will be ` +
|
});
|
||||||
`in place for the remainder of your current chat session.`
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
isBooted(username) {
|
isBooted(username) {
|
||||||
return this.WebRTC.booted[username] === true;
|
return this.WebRTC.booted[username] === true;
|
||||||
|
@ -3499,12 +3544,13 @@ export default {
|
||||||
this.directMessageHistory[channel].busy = false;
|
this.directMessageHistory[channel].busy = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async clearMessageHistory(prompt = false) {
|
async clearMessageHistory() {
|
||||||
if (!this.jwt.valid || this.clearDirectMessages.busy) return;
|
if (!this.jwt.valid || this.clearDirectMessages.busy) return;
|
||||||
|
|
||||||
if (prompt) {
|
this.modalConfirm({
|
||||||
if (!window.confirm(
|
title: "Clear all DMs",
|
||||||
"This will delete all of your DMs history stored on the server. People you have " +
|
icon: "fa fa-exclamation-triangle",
|
||||||
|
message: "This will delete all of your DMs history stored on the server. People you have " +
|
||||||
"chatted with will have their past messages sent to you erased as well.\n\n" +
|
"chatted with will have their past messages sent to you erased as well.\n\n" +
|
||||||
"Note: messages that are currently displayed on your chat partner's screen will " +
|
"Note: messages that are currently displayed on your chat partner's screen will " +
|
||||||
"NOT be removed by this action -- if this is a concern and you want to 'take back' " +
|
"NOT be removed by this action -- if this is a concern and you want to 'take back' " +
|
||||||
|
@ -3512,50 +3558,48 @@ export default {
|
||||||
"message you sent to them. The 'clear history' button only clears the database, but " +
|
"message you sent to them. The 'clear history' button only clears the database, but " +
|
||||||
"does not send takebacks to pull the message from everybody else's screen.\n\n" +
|
"does not send takebacks to pull the message from everybody else's screen.\n\n" +
|
||||||
"Are you sure you want to clear your stored DMs history on the server?",
|
"Are you sure you want to clear your stored DMs history on the server?",
|
||||||
)) {
|
}).then(async () => {
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.clearDirectMessages.timeout !== null) {
|
if (this.clearDirectMessages.timeout !== null) {
|
||||||
clearTimeout(this.clearDirectMessages.timeout);
|
clearTimeout(this.clearDirectMessages.timeout);
|
||||||
}
|
|
||||||
|
|
||||||
this.clearDirectMessages.busy = true;
|
|
||||||
return fetch("/api/message/clear", {
|
|
||||||
method: "POST",
|
|
||||||
mode: "same-origin",
|
|
||||||
cache: "no-cache",
|
|
||||||
credentials: "same-origin",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
"JWTToken": this.jwt.token,
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
if (data.Error) {
|
|
||||||
console.error("ClearMessageHistory: ", data.Error);
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.clearDirectMessages.ok = true;
|
this.clearDirectMessages.busy = true;
|
||||||
this.clearDirectMessages.messagesErased = data.MessagesErased;
|
return fetch("/api/message/clear", {
|
||||||
this.clearDirectMessages.timeout = setTimeout(() => {
|
method: "POST",
|
||||||
this.clearDirectMessages.ok = false;
|
mode: "same-origin",
|
||||||
}, 15000);
|
cache: "no-cache",
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
"JWTToken": this.jwt.token,
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
.then((response) => response.json())
|
||||||
|
.then((data) => {
|
||||||
|
if (data.Error) {
|
||||||
|
console.error("ClearMessageHistory: ", data.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.ChatClient(
|
this.clearDirectMessages.ok = true;
|
||||||
"Your direct message history has been cleared from the server's database. "+
|
this.clearDirectMessages.messagesErased = data.MessagesErased;
|
||||||
"(" + data.MessagesErased + " messages erased)",
|
this.clearDirectMessages.timeout = setTimeout(() => {
|
||||||
);
|
this.clearDirectMessages.ok = false;
|
||||||
}).catch(resp => {
|
}, 15000);
|
||||||
console.error("DirectMessageHistory: ", resp);
|
|
||||||
this.ChatClient("Error clearing your chat history: " + resp);
|
this.ChatClient(
|
||||||
}).finally(() => {
|
"Your direct message history has been cleared from the server's database. "+
|
||||||
this.clearDirectMessages.busy = false;
|
"(" + data.MessagesErased + " messages erased)",
|
||||||
|
);
|
||||||
|
}).catch(resp => {
|
||||||
|
console.error("DirectMessageHistory: ", resp);
|
||||||
|
this.ChatClient("Error clearing your chat history: " + resp);
|
||||||
|
}).finally(() => {
|
||||||
|
this.clearDirectMessages.busy = false;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -3571,10 +3615,17 @@ export default {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
reportMessage(message) {
|
reportMessage(message, force=false) {
|
||||||
// User is reporting a message on chat.
|
// User is reporting a message on chat.
|
||||||
if (message.reported) {
|
if (message.reported && !force) {
|
||||||
if (!window.confirm("You have already reported this message. Do you want to report it again?")) return;
|
this.modalConfirm({
|
||||||
|
title: "Report Message",
|
||||||
|
icon: "fa fa-info-circle",
|
||||||
|
message: "You have already reported this message. Do you want to report it again?",
|
||||||
|
}).then(() => {
|
||||||
|
this.reportMessage(message, true);
|
||||||
|
});
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the user object.
|
// Clone the user object.
|
||||||
|
@ -3622,6 +3673,15 @@ export default {
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
<!-- Alert/Confirm modal: to avoid blocking the page with native calls. -->
|
||||||
|
<AlertModal :visible="alertModal.visible"
|
||||||
|
:is-confirm="alertModal.isConfirm"
|
||||||
|
:title="alertModal.title"
|
||||||
|
:icon="alertModal.icon"
|
||||||
|
:message="alertModal.message"
|
||||||
|
@callback="alertModal.callback"
|
||||||
|
@close="modalClose()"></AlertModal>
|
||||||
|
|
||||||
<!-- Sign In modal -->
|
<!-- Sign In modal -->
|
||||||
<LoginModal :visible="loginModal.visible" @sign-in="signIn"></LoginModal>
|
<LoginModal :visible="loginModal.visible" @sign-in="signIn"></LoginModal>
|
||||||
|
|
||||||
|
@ -3641,7 +3701,7 @@ export default {
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header has-background-info">
|
<header class="card-header has-background-info">
|
||||||
<p class="card-header-title has-text-light">Chat Settings</p>
|
<p class="card-header-title">Chat Settings</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
|
|
||||||
|
@ -4090,7 +4150,7 @@ export default {
|
||||||
|
|
||||||
<!-- Clear DMs history on server -->
|
<!-- Clear DMs history on server -->
|
||||||
<div class="field" v-if="this.jwt.valid">
|
<div class="field" v-if="this.jwt.valid">
|
||||||
<a href="#" @click.prevent="clearMessageHistory(true)" class="button is-small has-text-danger">
|
<a href="#" @click.prevent="clearMessageHistory()" class="button is-small has-text-danger">
|
||||||
<i class="fa fa-trash mr-1"></i> Clear direct message history
|
<i class="fa fa-trash mr-1"></i> Clear direct message history
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
|
|
76
src/components/AlertModal.vue
Normal file
76
src/components/AlertModal.vue
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
visible: Boolean,
|
||||||
|
isConfirm: Boolean,
|
||||||
|
title: String,
|
||||||
|
icon: String,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
username: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
callback() {
|
||||||
|
this.$emit('close');
|
||||||
|
this.$emit('callback');
|
||||||
|
},
|
||||||
|
close() {
|
||||||
|
this.$emit('close');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="modal" :class="{ 'is-active': visible }">
|
||||||
|
<div class="modal-background"></div>
|
||||||
|
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="card">
|
||||||
|
<header class="card-header has-background-info">
|
||||||
|
<p class="card-header-title">
|
||||||
|
<i v-if="icon" :class="icon" class="mr-2"></i>
|
||||||
|
{{ title }}
|
||||||
|
</p>
|
||||||
|
<button class="delete mr-3 mt-3" aria-label="close" @click.prevent="close"></button>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="card-content">
|
||||||
|
<form @submit.prevent="callback()">
|
||||||
|
|
||||||
|
<p class="literal mb-4">{{ message }}</p>
|
||||||
|
|
||||||
|
<div class="columns is-centered">
|
||||||
|
<div class="column is-narrow">
|
||||||
|
<button type="submit"
|
||||||
|
class="button is-success px-5">
|
||||||
|
OK
|
||||||
|
</button>
|
||||||
|
<button v-if="isConfirm"
|
||||||
|
type="button"
|
||||||
|
class="button is-link ml-3 px-5"
|
||||||
|
@click="close">
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.modal {
|
||||||
|
/* a high priority modal over other modals. note: bulma's default z-index is 40 for modals */
|
||||||
|
z-index: 42;
|
||||||
|
}
|
||||||
|
|
||||||
|
.literal {
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -30,7 +30,7 @@ export default {
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header has-background-info">
|
<header class="card-header has-background-info">
|
||||||
<p class="card-header-title has-text-light">This camera may contain Explicit content</p>
|
<p class="card-header-title">This camera may contain Explicit content</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<p class="block">
|
<p class="block">
|
||||||
|
|
|
@ -22,7 +22,7 @@ export default {
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<header class="card-header has-background-info">
|
<header class="card-header has-background-info">
|
||||||
<p class="card-header-title has-text-light">Sign In</p>
|
<p class="card-header-title">Sign In</p>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content">
|
<div class="card-content">
|
||||||
<form @submit.prevent="signIn()">
|
<form @submit.prevent="signIn()">
|
||||||
|
|
Loading…
Reference in New Issue
Block a user