Add support for sending chat messages
This commit is contained in:
parent
15360b83df
commit
6aba097c85
39
src/api.ts
39
src/api.ts
@ -35,6 +35,7 @@ import {
|
|||||||
DynmapWorldMap
|
DynmapWorldMap
|
||||||
} from "@/dynmap";
|
} from "@/dynmap";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
|
import ChatError from "@/errors/ChatError";
|
||||||
|
|
||||||
function buildServerConfig(response: any): DynmapServerConfig {
|
function buildServerConfig(response: any): DynmapServerConfig {
|
||||||
return {
|
return {
|
||||||
@ -669,5 +670,43 @@ export default {
|
|||||||
|
|
||||||
return sets;
|
return sets;
|
||||||
});
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
sendChatMessage(message: string) {
|
||||||
|
const store = useStore();
|
||||||
|
|
||||||
|
if(!store.state.components.chatSending) {
|
||||||
|
return Promise.reject(new ChatError("Chat is not enabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(window.config.url.sendmessage, {
|
||||||
|
method: 'POST',
|
||||||
|
body: JSON.stringify({
|
||||||
|
name: null,
|
||||||
|
message: message,
|
||||||
|
})
|
||||||
|
}).then((response) => {
|
||||||
|
if(response.status === 403) { //Rate limited
|
||||||
|
throw new ChatError(store.state.messages.chatCooldown
|
||||||
|
.replace('%interval%', store.state.components.chatSending!.cooldown.toString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Network request failed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json();
|
||||||
|
}).then(response => {
|
||||||
|
if (response.error !== 'none') {
|
||||||
|
throw new ChatError(store.state.messages.chatNotAllowed);
|
||||||
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
if(!(e instanceof ChatError)) {
|
||||||
|
console.error('Unexpected error while sending chat message');
|
||||||
|
console.trace(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw e;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,13 +20,21 @@
|
|||||||
<ChatMessage v-for="message in messages" :key="message.timestamp" :message="message"></ChatMessage>
|
<ChatMessage v-for="message in messages" :key="message.timestamp" :message="message"></ChatMessage>
|
||||||
<li v-if="!messages.length" class="message message--skeleton">No chat messages yet...</li>
|
<li v-if="!messages.length" class="message message--skeleton">No chat messages yet...</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<form v-if="sendingEnabled" class="chat__form" @submit.prevent="sendMessage">
|
||||||
|
<div v-if="sendingError" class="chat__error">{{ sendingError }}</div>
|
||||||
|
<input ref="chatInput" v-model="enteredMessage" class="chat__input" type="text" :maxlength="messageMaxLength"
|
||||||
|
placeholder="Type your chat message here..." :disabled="sendingMessage">
|
||||||
|
<button class="chat__send" :disabled="!enteredMessage || sendingMessage">Send</button>
|
||||||
|
</form>
|
||||||
</section>
|
</section>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent, computed} from "@vue/runtime-core";
|
import {defineComponent, ref, computed, watch} from "@vue/runtime-core";
|
||||||
import {useStore} from "@/store";
|
import {useStore} from "@/store";
|
||||||
import ChatMessage from "@/components/chat/ChatMessage.vue";
|
import ChatMessage from "@/components/chat/ChatMessage.vue";
|
||||||
|
import {ActionTypes} from "@/store/action-types";
|
||||||
|
import ChatError from "@/errors/ChatError";
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
@ -35,16 +43,68 @@
|
|||||||
setup() {
|
setup() {
|
||||||
const store = useStore(),
|
const store = useStore(),
|
||||||
componentSettings = computed(() => store.state.components.chatBox),
|
componentSettings = computed(() => store.state.components.chatBox),
|
||||||
|
chatBoxVisible = computed(() => store.state.ui.visibleElements.has('chat')),
|
||||||
|
|
||||||
|
sendingEnabled = computed(() => {
|
||||||
|
return store.state.components.chatSending &&
|
||||||
|
(!store.state.components.chatSending.loginRequired || store.state.loggedIn);
|
||||||
|
}),
|
||||||
|
messageMaxLength = computed(() => store.state.components.chatSending?.maxLength),
|
||||||
|
|
||||||
|
chatInput = ref<HTMLInputElement | null>(null),
|
||||||
|
enteredMessage = ref<string>(""),
|
||||||
|
sendingMessage = ref<boolean>(false),
|
||||||
|
sendingError = ref<string | null>(null),
|
||||||
|
|
||||||
messages = computed(() => {
|
messages = computed(() => {
|
||||||
if(componentSettings.value!.messageHistory) {
|
if(componentSettings.value!.messageHistory) {
|
||||||
return store.state.chat.messages.slice(0, componentSettings.value!.messageHistory);
|
return store.state.chat.messages.slice(0, componentSettings.value!.messageHistory);
|
||||||
} else {
|
} else {
|
||||||
return store.state.chat.messages;
|
return store.state.chat.messages;
|
||||||
}
|
}
|
||||||
|
}),
|
||||||
|
|
||||||
|
sendMessage = () => {
|
||||||
|
const message = enteredMessage.value.trim().substring(0, messageMaxLength.value);
|
||||||
|
|
||||||
|
if(!message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendingMessage.value = true;
|
||||||
|
sendingError.value = null;
|
||||||
|
|
||||||
|
store.dispatch(ActionTypes.SEND_CHAT_MESSAGE, message).then(() => {
|
||||||
|
enteredMessage.value = "";
|
||||||
|
sendingError.value = null;
|
||||||
|
}).catch(e => {
|
||||||
|
if(e instanceof ChatError) {
|
||||||
|
sendingError.value = e.message;
|
||||||
|
} else {
|
||||||
|
sendingError.value = `An unexpected error occurred. See console for details.`;
|
||||||
|
}
|
||||||
|
}).finally(() => {
|
||||||
|
sendingMessage.value = false;
|
||||||
|
|
||||||
|
requestAnimationFrame(() => chatInput.value!.focus());
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(chatBoxVisible, newValue => {
|
||||||
|
if(newValue && sendingEnabled.value) {
|
||||||
|
requestAnimationFrame(() => chatInput.value!.focus());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
chatInput,
|
||||||
|
enteredMessage,
|
||||||
|
sendMessage,
|
||||||
messages,
|
messages,
|
||||||
|
sendingEnabled,
|
||||||
|
sendingMessage,
|
||||||
|
sendingError,
|
||||||
|
messageMaxLength
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -88,6 +148,33 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chat__form {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: stretch;
|
||||||
|
margin: 1.5rem -1.5rem -1.5rem;
|
||||||
|
|
||||||
|
.chat__input {
|
||||||
|
border-bottom-left-radius: $global-border-radius;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat__send {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
border-radius: 0 0 $global-border-radius 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chat__error {
|
||||||
|
background-color: #771616;
|
||||||
|
color: #eeeeee;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
line-height: 2rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 25rem), (max-height: 30rem) {
|
@media (max-width: 25rem), (max-height: 30rem) {
|
||||||
bottom: 6.5rem;
|
bottom: 6.5rem;
|
||||||
left: 6.5rem;
|
left: 6.5rem;
|
||||||
|
22
src/errors/ChatError.ts
Normal file
22
src/errors/ChatError.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2021 James Lyne
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default class ChatError extends Error {
|
||||||
|
constructor(message: string) {
|
||||||
|
super(message);
|
||||||
|
this.name = "ChatError";
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
transition: color 0.2s ease-in, background-color 0.2s ease-in;
|
transition: color 0.2s ease-in, background-color 0.2s ease-in;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
|
||||||
&:hover, &.active {
|
&:hover, &.active {
|
||||||
background-color: $global-focus-color;
|
background-color: $global-focus-color;
|
||||||
|
@ -115,6 +115,28 @@ button {
|
|||||||
@extend %button;
|
@extend %button;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
appearance: none;
|
||||||
|
background-color: #333333;
|
||||||
|
box-shadow: none;
|
||||||
|
color: #cccccc;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border: 0.2rem solid #333333;
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
color: #eeeeee;
|
||||||
|
outline-color: #eeeeee;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[disabled] {
|
||||||
|
background-color: #333333;
|
||||||
|
border-color: #333333;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.checkbox {
|
.checkbox {
|
||||||
display: flex;
|
display: flex;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -406,26 +428,6 @@ button {
|
|||||||
* Chat
|
* Chat
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.chatinput {
|
|
||||||
|
|
||||||
width: 608px;
|
|
||||||
height: 16px;
|
|
||||||
|
|
||||||
outline: none;
|
|
||||||
color: #fff;
|
|
||||||
border: 0;
|
|
||||||
//background: rgba(0, 0, 0, 0.6) url(../assets/images/chat_cursor.png) no-repeat 1px center;
|
|
||||||
|
|
||||||
margin: 4px;
|
|
||||||
padding: 1px 1px 1px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chatsendbutton {
|
|
||||||
background-color: #bbb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loginbutton {
|
.loginbutton {
|
||||||
color: #000;
|
color: #000;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
@ -438,48 +440,6 @@ button {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.messagelist {
|
|
||||||
color: white;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
width: 622px;
|
|
||||||
max-height: 6em;
|
|
||||||
|
|
||||||
margin: 4px 4px 0 4px;
|
|
||||||
padding: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scrollback:hover {
|
|
||||||
overflow-y: auto !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messagerow {
|
|
||||||
position: relative;
|
|
||||||
max-height: 200px;
|
|
||||||
|
|
||||||
left: 0;
|
|
||||||
bottom: 0;
|
|
||||||
|
|
||||||
color: #fff;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messageicon {
|
|
||||||
position: relative;
|
|
||||||
top: 1px;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messagetext {
|
|
||||||
position: relative;
|
|
||||||
top: -3px;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.balloonmessage {
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dynmap .mapMarker .markerName_offline_players {
|
.dynmap .mapMarker .markerName_offline_players {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,5 @@ export enum ActionTypes {
|
|||||||
POP_CIRCLE_UPDATES = "popCircleUpdates",
|
POP_CIRCLE_UPDATES = "popCircleUpdates",
|
||||||
POP_LINE_UPDATES = "popLineUpdates",
|
POP_LINE_UPDATES = "popLineUpdates",
|
||||||
POP_TILE_UPDATES = "popTileUpdates",
|
POP_TILE_UPDATES = "popTileUpdates",
|
||||||
|
SEND_CHAT_MESSAGE = "sendChatMessage",
|
||||||
}
|
}
|
@ -70,6 +70,10 @@ export interface Actions {
|
|||||||
{commit}: AugmentedActionContext,
|
{commit}: AugmentedActionContext,
|
||||||
payload: number
|
payload: number
|
||||||
): Promise<DynmapTileUpdate[]>
|
): Promise<DynmapTileUpdate[]>
|
||||||
|
[ActionTypes.SEND_CHAT_MESSAGE](
|
||||||
|
{commit}: AugmentedActionContext,
|
||||||
|
payload: string
|
||||||
|
): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions: ActionTree<State, State> & Actions = {
|
export const actions: ActionTree<State, State> & Actions = {
|
||||||
@ -239,4 +243,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
|||||||
|
|
||||||
return Promise.resolve(updates);
|
return Promise.resolve(updates);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
[ActionTypes.SEND_CHAT_MESSAGE]({commit, state}, message: string): Promise<void> {
|
||||||
|
return API.sendChatMessage(message);
|
||||||
|
},
|
||||||
}
|
}
|
@ -30,6 +30,8 @@ export type State = {
|
|||||||
messages: DynmapMessageConfig;
|
messages: DynmapMessageConfig;
|
||||||
components: DynmapComponentConfig;
|
components: DynmapComponentConfig;
|
||||||
|
|
||||||
|
loggedIn: boolean;
|
||||||
|
|
||||||
worlds: Map<string, DynmapWorld>;
|
worlds: Map<string, DynmapWorld>;
|
||||||
maps: Map<string, DynmapWorldMap>;
|
maps: Map<string, DynmapWorldMap>;
|
||||||
players: Map<string, DynmapPlayer>;
|
players: Map<string, DynmapPlayer>;
|
||||||
@ -95,6 +97,8 @@ export const state: State = {
|
|||||||
anonymousQuit: '',
|
anonymousQuit: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loggedIn: false,
|
||||||
|
|
||||||
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
|
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
|
||||||
maps: new Map(), //Defined maps from configuration.json
|
maps: new Map(), //Defined maps from configuration.json
|
||||||
players: new Map(), //Online players from world.json
|
players: new Map(), //Online players from world.json
|
||||||
|
Loading…
Reference in New Issue
Block a user