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
|
||||
} from "@/dynmap";
|
||||
import {useStore} from "@/store";
|
||||
import ChatError from "@/errors/ChatError";
|
||||
|
||||
function buildServerConfig(response: any): DynmapServerConfig {
|
||||
return {
|
||||
@ -669,5 +670,43 @@ export default {
|
||||
|
||||
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>
|
||||
<li v-if="!messages.length" class="message message--skeleton">No chat messages yet...</li>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import {defineComponent, computed} from "@vue/runtime-core";
|
||||
import {defineComponent, ref, computed, watch} from "@vue/runtime-core";
|
||||
import {useStore} from "@/store";
|
||||
import ChatMessage from "@/components/chat/ChatMessage.vue";
|
||||
import {ActionTypes} from "@/store/action-types";
|
||||
import ChatError from "@/errors/ChatError";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
@ -35,16 +43,68 @@
|
||||
setup() {
|
||||
const store = useStore(),
|
||||
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(() => {
|
||||
if(componentSettings.value!.messageHistory) {
|
||||
return store.state.chat.messages.slice(0, componentSettings.value!.messageHistory);
|
||||
} else {
|
||||
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 {
|
||||
chatInput,
|
||||
enteredMessage,
|
||||
sendMessage,
|
||||
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) {
|
||||
bottom: 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;
|
||||
position: relative;
|
||||
transition: color 0.2s ease-in, background-color 0.2s ease-in;
|
||||
font-size: 1.6rem;
|
||||
|
||||
&:hover, &.active {
|
||||
background-color: $global-focus-color;
|
||||
|
@ -115,6 +115,28 @@ 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 {
|
||||
display: flex;
|
||||
position: relative;
|
||||
@ -406,26 +428,6 @@ button {
|
||||
* 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 {
|
||||
color: #000;
|
||||
font-family: sans-serif;
|
||||
@ -438,48 +440,6 @@ button {
|
||||
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 {
|
||||
font-style: italic;
|
||||
}
|
||||
|
@ -24,4 +24,5 @@ export enum ActionTypes {
|
||||
POP_CIRCLE_UPDATES = "popCircleUpdates",
|
||||
POP_LINE_UPDATES = "popLineUpdates",
|
||||
POP_TILE_UPDATES = "popTileUpdates",
|
||||
SEND_CHAT_MESSAGE = "sendChatMessage",
|
||||
}
|
@ -70,6 +70,10 @@ export interface Actions {
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: number
|
||||
): Promise<DynmapTileUpdate[]>
|
||||
[ActionTypes.SEND_CHAT_MESSAGE](
|
||||
{commit}: AugmentedActionContext,
|
||||
payload: string
|
||||
): Promise<void>
|
||||
}
|
||||
|
||||
export const actions: ActionTree<State, State> & Actions = {
|
||||
@ -239,4 +243,8 @@ export const actions: ActionTree<State, State> & Actions = {
|
||||
|
||||
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;
|
||||
components: DynmapComponentConfig;
|
||||
|
||||
loggedIn: boolean;
|
||||
|
||||
worlds: Map<string, DynmapWorld>;
|
||||
maps: Map<string, DynmapWorldMap>;
|
||||
players: Map<string, DynmapPlayer>;
|
||||
@ -95,6 +97,8 @@ export const state: State = {
|
||||
anonymousQuit: '',
|
||||
},
|
||||
|
||||
loggedIn: false,
|
||||
|
||||
worlds: new Map(), //Defined (loaded) worlds with maps from configuration.json
|
||||
maps: new Map(), //Defined maps from configuration.json
|
||||
players: new Map(), //Online players from world.json
|
||||
|
Loading…
Reference in New Issue
Block a user