Refactor some modals and features into components
Move some chat modals into external components: * LoginModal * ExplicitOpenModal * ReportModal * The Photo Modal was hoisted into the main index.html page, because it is not a Vue component and relied on global onclick handlers and the DOM. Spin off some external JS modules: * isAppleWebkit moved to lib/browsers.js * Local Storage management centralized and moved to lib/LocalStorage.js
This commit is contained in:
parent
e728644a77
commit
8906e89a51
21
index.html
21
index.html
|
@ -12,6 +12,18 @@
|
|||
<title>{{.Config.Title}}</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Photo Detail Modal -->
|
||||
<div class="modal" id="photo-modal">
|
||||
<div class="modal-background" onclick="document.querySelector('#photo-modal').classList.remove('is-active')"></div>
|
||||
<div class="modal-content photo-modal">
|
||||
<div class="image is-fullwidth">
|
||||
<img id="modalImage">
|
||||
</div>
|
||||
</div>
|
||||
<button class="modal-close is-large" aria-label="close" onclick="document.querySelector('#photo-modal').classList.remove('is-active')"></button>
|
||||
</div>
|
||||
|
||||
<div id="app"></div>
|
||||
|
||||
<!-- BareRTC constants injected by IndexPage route -->
|
||||
|
@ -27,6 +39,15 @@
|
|||
const UserJWTValid = {{if .JWTAuthOK}}true{{else}}false{{end}};
|
||||
const UserJWTClaims = {{.JWTClaims.ToJSON}};
|
||||
const CachedBlocklist = {{.CachedBlocklist}};
|
||||
|
||||
// Show the photo detail modal.
|
||||
function setModalImage(url) {
|
||||
let $modalImg = document.querySelector("#modalImage"),
|
||||
$modal = document.querySelector("#photo-modal");
|
||||
$modalImg.src = url;
|
||||
$modal.classList.add("is-active");
|
||||
return false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
|
|
1258
src/App.vue
1258
src/App.vue
File diff suppressed because it is too large
Load Diff
71
src/components/ExplicitOpenModal.vue
Normal file
71
src/components/ExplicitOpenModal.vue
Normal file
|
@ -0,0 +1,71 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: Boolean,
|
||||
user: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dontShowAgain: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
accept() {
|
||||
if (this.dontShowAgain) {
|
||||
this.$emit('dont-show-again');
|
||||
}
|
||||
this.$emit('accept');
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- NSFW Modal: before user views a NSFW camera the first time -->
|
||||
<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 has-text-light">This camera may contain Explicit content</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<p class="block">
|
||||
This camera has been marked as "Explicit/<abbr title="Not Safe For Work">NSFW</abbr>" and may
|
||||
contain displays of sexuality. If you do not want to see this, look for cameras with
|
||||
a <span class="button is-small is-info is-outlined px-1"><i class="fa fa-video"></i></span>
|
||||
blue icon rather than the <span class="button is-small is-danger is-outlined px-1"><i
|
||||
class="fa fa-video"></i></span>
|
||||
red ones.
|
||||
</p>
|
||||
|
||||
<div class="field">
|
||||
<label class="checkbox">
|
||||
<input type="checkbox" v-model="dontShowAgain">
|
||||
Don't show this message again
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control has-text-centered">
|
||||
<button type="button" class="button is-link mr-4"
|
||||
@click="accept()">
|
||||
Open webcam
|
||||
</button>
|
||||
<button type="button" class="button"
|
||||
@click="cancel()">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
65
src/components/LoginModal.vue
Normal file
65
src/components/LoginModal.vue
Normal file
|
@ -0,0 +1,65 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
username: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
signIn() {
|
||||
this.$emit('signIn', this.username);
|
||||
}
|
||||
}
|
||||
}
|
||||
</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 has-text-light">Sign In</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
<form @submit.prevent="signIn()">
|
||||
|
||||
<div v-if="autoLogin" class="content">
|
||||
<p>
|
||||
Welcome to <span v-html="config.branding"></span>! Please just click on the "Enter Chat"
|
||||
button below to log on. Your username has been pre-filled from the website that
|
||||
sent you here.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
This dialog box is added as an experiment to see whether it
|
||||
helps iOS devices (iPads and iPhones) to log in to the chat more reliably, by
|
||||
having you interact with the page before it connects to the server. Let us
|
||||
know in chat if your iPhone or iPad is able to log in this way!
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label">Username</label>
|
||||
<input class="input" v-model="username" placeholder="Username" autocomplete="off" autofocus
|
||||
:disabled="autoLogin" required>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control">
|
||||
<button class="button is-link">Enter Chat</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
113
src/components/ReportModal.vue
Normal file
113
src/components/ReportModal.vue
Normal file
|
@ -0,0 +1,113 @@
|
|||
<script>
|
||||
export default {
|
||||
props: {
|
||||
visible: Boolean,
|
||||
busy: Boolean,
|
||||
user: Object,
|
||||
message: Object,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// Configuration
|
||||
reportClassifications: [
|
||||
"It's spam",
|
||||
"It's abusive (racist, homophobic, etc.)",
|
||||
"It's malicious (e.g. link to a malware website, phishing)",
|
||||
"It's illegal (e.g. controlled substances, violence)",
|
||||
"It's child abuse (CP, CSAM, pedophilia, etc.)",
|
||||
"Other (please describe)",
|
||||
],
|
||||
|
||||
// Our settings.
|
||||
classification: "It's spam",
|
||||
comment: "",
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
accept() {
|
||||
this.$emit('accept', {
|
||||
classification: this.classification,
|
||||
comment: this.comment,
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- Report Modal -->
|
||||
<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-warning">
|
||||
<p class="card-header-title has-text-dark">Report a message</p>
|
||||
</header>
|
||||
<div class="card-content">
|
||||
|
||||
<!-- Message preview we are reporting on
|
||||
TODO: make it DRY: style copied/referenced from chat history cards -->
|
||||
<div class="box mb-2 px-4 pt-3 pb-1 position-relative">
|
||||
<div class="media mb-0">
|
||||
<div class="media-left">
|
||||
<figure class="image is-48x48">
|
||||
<img v-if="user?.avatar"
|
||||
:src="user?.avatar">
|
||||
<img v-else src="/static/img/shy.png">
|
||||
</figure>
|
||||
</div>
|
||||
<div class="media-content">
|
||||
<div>
|
||||
<strong>
|
||||
<!-- User nickname/display name -->
|
||||
{{ user?.nickname }}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<!-- User @username below it which may link to a profile URL if JWT -->
|
||||
<div>
|
||||
<small class="has-text-grey">
|
||||
@{{ message.username }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Message copy -->
|
||||
<div class="content pl-5 py-3 mb-5 report-modal-message" v-html="message.message">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field mb-1">
|
||||
<label class="label" for="classification">Report classification:</label>
|
||||
<div class="select is-fullwidth">
|
||||
<select id="classification" v-model="classification" :disabled="busy">
|
||||
<option v-for="i in reportClassifications" :value="i">{{ i }}</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<label class="label" for="reportComment">Comment:</label>
|
||||
<textarea class="textarea" v-model="comment" :disabled="busy" cols="80"
|
||||
rows="2" placeholder="Optional: describe the issue"></textarea>
|
||||
</div>
|
||||
|
||||
<div class="field">
|
||||
<div class="control has-text-centered">
|
||||
<button type="button" class="button is-link mr-4" :disabled="busy"
|
||||
@click="accept()">Submit report</button>
|
||||
<button type="button" class="button" @click="cancel()">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
79
src/lib/LocalStorage.js
Normal file
79
src/lib/LocalStorage.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
// All the distinct localStorage keys used.
|
||||
const keys = {
|
||||
'fontSizeClass': String, // Text magnification
|
||||
'videoScale': String, // Video magnification (CSS classnames)
|
||||
'imageDisplaySetting': String, // Show/hide/expand image preference
|
||||
'scrollback': Number, // Scrollback buffer (int)
|
||||
'preferredDeviceNames': Object, // Webcam/mic device names (object, keys video,audio)
|
||||
|
||||
// Webcam settings (booleans)
|
||||
'videoMutual': Boolean,
|
||||
'videoMutualOpen': Boolean,
|
||||
'videoAutoMute': Boolean,
|
||||
'videoVipOnly': Boolean,
|
||||
|
||||
// Booleans
|
||||
'joinMessages': Boolean,
|
||||
'exitMessages': Boolean,
|
||||
'watchNotif': Boolean,
|
||||
'muteSounds': Boolean,
|
||||
'closeDMs': Boolean, // close unsolicited DMs
|
||||
|
||||
// Don't Show Again on NSFW modals.
|
||||
'skip-nsfw-modal': Boolean,
|
||||
}
|
||||
|
||||
// UserSettings centralizes browser settings for the chat room.
|
||||
class UserSettings {
|
||||
constructor() {
|
||||
// Recall stored settings. Only set the keys that were
|
||||
// found in localStorage on page load.
|
||||
for (let key of Object.keys(keys)) {
|
||||
if (localStorage[key] != undefined) {
|
||||
switch (keys[key]) {
|
||||
case String:
|
||||
this[key] = localStorage[key];
|
||||
case Number:
|
||||
this[key] = parseInt(localStorage[key]);
|
||||
case Boolean:
|
||||
this[key] = localStorage[key] === "true";
|
||||
case Object:
|
||||
this[key] = JSON.parse(localStorage[key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log("LocalStorage: Loaded settings", this);
|
||||
}
|
||||
|
||||
// Return all of the current settings where the user had actually
|
||||
// left a preference on them (was in localStorage).
|
||||
getSettings() {
|
||||
let result = {};
|
||||
for (let key of Object.keys(keys)) {
|
||||
if (this[key] != undefined) {
|
||||
result[key] = this[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Get a value from localStorage, if set.
|
||||
get(key) {
|
||||
return this[key];
|
||||
}
|
||||
|
||||
// Generic setter.
|
||||
set(key, value) {
|
||||
if (keys[key] == undefined) {
|
||||
throw `${key}: not a supported localStorage setting`;
|
||||
}
|
||||
|
||||
localStorage[key] = JSON.stringify(value);
|
||||
this[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// LocalStorage is a global singleton to access and update user settings.
|
||||
const LocalStorage = new UserSettings();
|
||||
export default LocalStorage;
|
18
src/lib/browsers.js
Normal file
18
src/lib/browsers.js
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Try and detect whether the user is on an Apple Safari browser, which has
|
||||
// special nuances in their WebRTC video sharing support. This is intended to
|
||||
// detect: iPads, iPhones, and Safari on macOS.
|
||||
function isAppleWebkit() {
|
||||
// By User-Agent.
|
||||
if (/iPad|iPhone|iPod/.test(navigator.userAgent)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// By (deprecated) navigator.platform.
|
||||
if (navigator.platform === 'iPad' || navigator.platform === 'iPhone' || navigator.platform === 'iPod') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export { isAppleWebkit };
|
Loading…
Reference in New Issue
Block a user