feat: 메시지 빠른 답장 대상 지정

This commit is contained in:
2023-03-20 02:12:57 +09:00
parent 1eba863cc4
commit 790c58f2b6
2 changed files with 200 additions and 114 deletions
+91 -41
View File
@@ -35,7 +35,14 @@
</div> </div>
<div class="PublicTalk"> <div class="PublicTalk">
<div class="stickyAnchor"></div> <div class="stickyAnchor"></div>
<div class="BoardHeader bg0">전체 메시지</div> <div class="BoardHeader bg0 d-flex">
<div class="flex-grow-1 align-self-center">전체 메시지</div>
<div>
<BButton size="sm" class="btn-more-small" variant="primary" @click="targetMailbox = 9999"
><i class="bi bi-reply-fill" />여기로</BButton
>
</div>
</div>
<template v-if="messagePublic.length == 0"> <template v-if="messagePublic.length == 0">
<div>메시지가 없습니다.</div> <div>메시지가 없습니다.</div>
</template> </template>
@@ -48,6 +55,7 @@
:generalName="generalName" :generalName="generalName"
:nationID="nationID" :nationID="nationID"
:permissionLevel="permissionLevel" :permissionLevel="permissionLevel"
@setTarget="setTarget"
></MessagePlate> ></MessagePlate>
<div class="d-grid Actions"> <div class="d-grid Actions">
<button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'public')">접기</button> <button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'public')">접기</button>
@@ -59,7 +67,14 @@
</div> </div>
<div class="NationalTalk"> <div class="NationalTalk">
<div class="stickyAnchor"></div> <div class="stickyAnchor"></div>
<div class="BoardHeader bg0">국가 메시지</div> <div class="BoardHeader bg0 d-flex">
<div class="flex-grow-1 align-self-center">국가 메시지</div>
<div>
<BButton size="sm" class="btn-more-small" variant="primary" @click="targetMailbox = 9000 + props.nationID"
><i class="bi bi-reply-fill" />여기로</BButton
>
</div>
</div>
<template v-if="messageNational.length == 0"> <template v-if="messageNational.length == 0">
<div>메시지가 없습니다.</div> <div>메시지가 없습니다.</div>
</template> </template>
@@ -72,6 +87,7 @@
:generalName="generalName" :generalName="generalName"
:nationID="nationID" :nationID="nationID"
:permissionLevel="permissionLevel" :permissionLevel="permissionLevel"
@setTarget="setTarget"
></MessagePlate> ></MessagePlate>
<div class="d-grid Actions"> <div class="d-grid Actions">
<button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'national')">접기</button> <button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'national')">접기</button>
@@ -87,9 +103,10 @@
<div class="flex-grow-1 align-self-center">개인 메시지</div> <div class="flex-grow-1 align-self-center">개인 메시지</div>
<div> <div>
<BButton <BButton
v-if="messagePrivate.length > 0 && latestPrivateMsgToastInfo[2] > latestPrivateMsgToastInfo[1]"
size="sm" size="sm"
class="btn-more-small"
variant="secondary" variant="secondary"
:disabled="!(messagePrivate.length > 0 && latestPrivateMsgToastInfo[2] > latestPrivateMsgToastInfo[1])"
@click="readLatestMsg('private')" @click="readLatestMsg('private')"
>모두 읽음</BButton >모두 읽음</BButton
> >
@@ -107,7 +124,12 @@
:generalName="generalName" :generalName="generalName"
:nationID="nationID" :nationID="nationID"
:permissionLevel="permissionLevel" :permissionLevel="permissionLevel"
@response="()=>{readLatestMsg('private')}" @response="
() => {
readLatestMsg('private');
}
"
@setTarget="setTarget"
></MessagePlate> ></MessagePlate>
<div class="d-grid Actions"> <div class="d-grid Actions">
<button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'private')">접기</button> <button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'private')">접기</button>
@@ -123,9 +145,10 @@
<div class="flex-grow-1 align-self-center">외교 메시지</div> <div class="flex-grow-1 align-self-center">외교 메시지</div>
<div> <div>
<BButton <BButton
v-if="messageDiplomacy.length > 0 && latestDiplomacyMsgToastInfo[2] > latestDiplomacyMsgToastInfo[1]" size="sm"
ize="sm" class="btn-more-small"
variant="secondary" variant="secondary"
:disabled="!(messagePrivate.length > 0 && latestPrivateMsgToastInfo[2] > latestPrivateMsgToastInfo[1])"
@click="readLatestMsg('diplomacy')" @click="readLatestMsg('diplomacy')"
>모두 읽음</BButton >모두 읽음</BButton
> >
@@ -143,7 +166,12 @@
:generalName="generalName" :generalName="generalName"
:nationID="nationID" :nationID="nationID"
:permissionLevel="permissionLevel" :permissionLevel="permissionLevel"
@response="()=>{readLatestMsg('diplomacy')}" @response="
() => {
readLatestMsg('diplomacy');
}
"
@setTarget="setTarget"
></MessagePlate> ></MessagePlate>
<div class="d-grid Actions"> <div class="d-grid Actions">
<button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'diplomacy')">접기</button> <button type="button" class="btn btn-dark only-mobile" @click="foldMessage($event, 'diplomacy')">접기</button>
@@ -170,7 +198,7 @@ import { onMounted, ref, toRef, watch, type Ref } from "vue";
import { delay } from "@/util/delay"; import { delay } from "@/util/delay";
import { SammoAPI } from "@/SammoAPI"; import { SammoAPI } from "@/SammoAPI";
import { isString } from "lodash-es"; import { isString } from "lodash-es";
import type { MabilboxListResponse, MsgItem, MsgResponse, MsgType } from "@/defs/API/Message"; import type { MabilboxListResponse, MsgItem, MsgResponse, MsgTarget, MsgType } from "@/defs/API/Message";
import MessagePlate from "@/components/MessagePlate.vue"; import MessagePlate from "@/components/MessagePlate.vue";
import { useToast, BFormSelect } from "bootstrap-vue-next"; import { useToast, BFormSelect } from "bootstrap-vue-next";
import { unwrap } from "@/util/unwrap"; import { unwrap } from "@/util/unwrap";
@@ -273,24 +301,20 @@ function _updateLatestMsg(msg: MsgItem) {
toasts.remove(toastID); toasts.remove(toastID);
} }
const newToastID = toasts.show( const newToastID = toasts.show(
getNewMsgToast( getNewMsgToast("새로운 개인 메시지", "새로운 개인 메시지가 도착했습니다.", (type, e) => {
"새로운 개인 메시지", if (type === "goto") {
"새로운 개인 메시지가 도착했습니다.", readLatestMsg("private");
(type, e)=>{ scrollToSelector(".PrivateTalk > .stickyAnchor");
if(type === 'goto'){ return;
readLatestMsg('private');
scrollToSelector('.PrivateTalk > .stickyAnchor');
return;
}
if(type === 'ignore'){
readLatestMsg('private');
return;
}
} }
), if (type === "ignore") {
readLatestMsg("private");
return;
}
}),
{ {
delay: 1000 * 60 * 10, delay: 1000 * 60 * 10,
variant: 'warning' variant: "warning",
} }
).options.id; ).options.id;
latestPrivateMsgToastInfo.value = [newToastID, latestMsgID, msg.id]; latestPrivateMsgToastInfo.value = [newToastID, latestMsgID, msg.id];
@@ -298,7 +322,7 @@ function _updateLatestMsg(msg: MsgItem) {
} }
if (msgType == "diplomacy") { if (msgType == "diplomacy") {
if (permissionLevel.value < 4){ if (permissionLevel.value < 4) {
return; return;
} }
const [toastID, latestMsgID] = latestDiplomacyMsgToastInfo.value; const [toastID, latestMsgID] = latestDiplomacyMsgToastInfo.value;
@@ -311,24 +335,20 @@ function _updateLatestMsg(msg: MsgItem) {
toasts.remove(toastID); toasts.remove(toastID);
} }
const newToastID = toasts.show( const newToastID = toasts.show(
getNewMsgToast( getNewMsgToast("새로운 외교 메시지", "새로운 외교 메시지가 도착했습니다.", (type, e) => {
"새로운 외교 메시지", if (type === "goto") {
"새로운 외교 메시지가 도착했습니다.", readLatestMsg("diplomacy");
(type, e)=>{ scrollToSelector(".DiplomacyTalk > .stickyAnchor");
if(type === 'goto'){ return;
readLatestMsg('diplomacy');
scrollToSelector('.DiplomacyTalk > .stickyAnchor')
return;
}
if(type === 'ignore'){
readLatestMsg('diplomacy');
return;
}
} }
), if (type === "ignore") {
readLatestMsg("diplomacy");
return;
}
}),
{ {
delay: 1000 * 60 * 10, delay: 1000 * 60 * 10,
variant: 'warning' variant: "warning",
} }
).options.id; ).options.id;
latestDiplomacyMsgToastInfo.value = [newToastID, latestMsgID, msg.id]; latestDiplomacyMsgToastInfo.value = [newToastID, latestMsgID, msg.id];
@@ -522,10 +542,31 @@ type MailboxTarget = {
disabled?: true; disabled?: true;
color?: string; color?: string;
}; };
const mailboxMap = ref(new Map<number, MailboxTarget>());
const mailboxList = ref<MailboxGroup[]>([]); const mailboxList = ref<MailboxGroup[]>([]);
const newMessageText = ref<string>(""); const newMessageText = ref<string>("");
function setTarget(type: MsgType, target: MsgTarget): void {
const item = mailboxMap.value.get(
(() => {
if (type == "diplomacy" || type == "national") {
if (target.nation_id == nationID.value) {
return target.id;
}
return target.nation_id + 9000;
}
return target.id;
})()
);
if (!item) {
return;
}
targetMailbox.value = item.value;
}
function refreshMailboxList(obj: MabilboxListResponse) { function refreshMailboxList(obj: MabilboxListResponse) {
const newMailboxMap = new Map<number, MailboxTarget>();
let myNationColor = "#000000"; let myNationColor = "#000000";
const diplomacyMailboxList: MailboxGroup = { const diplomacyMailboxList: MailboxGroup = {
label: "외교메시지", label: "외교메시지",
@@ -590,6 +631,7 @@ function refreshMailboxList(obj: MabilboxListResponse) {
target.disabled = true; target.disabled = true;
} }
nationBox.options.push(target); nationBox.options.push(target);
newMailboxMap.set(destGeneralID, target);
} }
nationMailboxList.push(nationBox); nationMailboxList.push(nationBox);
@@ -597,12 +639,14 @@ function refreshMailboxList(obj: MabilboxListResponse) {
continue; continue;
} }
diplomacyMailboxList.options.push({ const target: MailboxTarget = {
value: nation.mailbox, value: nation.mailbox,
text: nation.name, text: nation.name,
nationID: nation.nationID, nationID: nation.nationID,
color: nation.color, color: nation.color,
}); };
newMailboxMap.set(nation.mailbox, target);
diplomacyMailboxList.options.push(target);
} }
const favoriteBox: MailboxGroup = { const favoriteBox: MailboxGroup = {
@@ -624,6 +668,7 @@ function refreshMailboxList(obj: MabilboxListResponse) {
}; };
mailboxList.value = [favoriteBox, diplomacyMailboxList, ...nationMailboxList]; mailboxList.value = [favoriteBox, diplomacyMailboxList, ...nationMailboxList];
mailboxMap.value = newMailboxMap;
} }
function foldMessage($event: MouseEvent, type: MsgType) { function foldMessage($event: MouseEvent, type: MsgType) {
@@ -722,6 +767,11 @@ onMounted(async () => {
<style lang="scss" scoped> <style lang="scss" scoped>
@import "@scss/common/break_500px.scss"; @import "@scss/common/break_500px.scss";
.btn-more-small {
font-size: 0.8rem;
padding: 0.15rem 0.4rem;
}
.BoardHeader { .BoardHeader {
color: white; color: white;
outline-style: solid; outline-style: solid;
+109 -73
View File
@@ -25,13 +25,21 @@
<template v-if="src.name == generalName"> <template v-if="src.name == generalName">
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }"></span <span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }"></span
><span class="msg_from_to"></span ><span class="msg_from_to"></span
><span :class="`msg_target msg_${destColorType}`" :style="{ backgroundColor: dest.color }" ><span
>{{ dest.name }}:{{ dest.nation }}</span role="button"
> :class="`msg_target msg_${destColorType}`"
:style="{ backgroundColor: dest.color }"
@click="emit('setTarget', msg.msgType, dest)"
>{{ dest.name }}:{{ dest.nation }} | <i class="bi bi-reply-fill"
/></span>
</template> </template>
<template v-else> <template v-else>
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }" <span
>{{ src.name }}:{{ src.nation }}</span role="button"
:class="`msg_target msg_${srcColorType}`"
:style="{ backgroundColor: src.color }"
@click="emit('setTarget', msg.msgType, src)"
>{{ src.name }}:{{ src.nation }} | <i class="bi bi-reply-fill" /></span
><span class="msg_from_to"></span ><span class="msg_from_to"></span
><span :class="`msg_target msg_${destColorType}`" :style="{ backgroundColor: dest.color }"></span> ><span :class="`msg_target msg_${destColorType}`" :style="{ backgroundColor: dest.color }"></span>
</template> </template>
@@ -39,6 +47,28 @@
<template v-else-if="msg.msgType == 'national' && src.nation_id === dest.nation_id"> <template v-else-if="msg.msgType == 'national' && src.nation_id === dest.nation_id">
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span> <span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span>
</template> </template>
<template v-else-if="(msg.msgType == 'national' || msg.msgType == 'diplomacy') && permissionLevel >= 4">
<template v-if="src.nation_id == nationID">
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span
><span class="msg_from_to"></span
><span
role="button"
:class="`msg_target msg_${destColorType}`"
:style="{ backgroundColor: dest.color }"
@click="emit('setTarget', msg.msgType, dest)"
>{{ dest.nation }} | <i class="bi bi-reply-fill"
/></span>
</template>
<template v-else>
<span
role="button"
:class="`msg_target msg_${srcColorType}`"
:style="{ backgroundColor: src.color }"
@click="emit('setTarget', msg.msgType, src)"
>{{ src.name }}:{{ src.nation }} | <i class="bi bi-reply-fill" /></span
><span class="msg_from_to"></span>
</template>
</template>
<template v-else-if="msg.msgType == 'national' || msg.msgType == 'diplomacy'"> <template v-else-if="msg.msgType == 'national' || msg.msgType == 'diplomacy'">
<template v-if="src.nation_id == nationID"> <template v-if="src.nation_id == nationID">
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span <span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span
@@ -53,15 +83,24 @@
><span class="msg_from_to"></span> ><span class="msg_from_to"></span>
</template> </template>
</template> </template>
<template v-else-if="src.id != props.generalID">
<span
role="button"
:class="`msg_target msg_${srcColorType}`"
:style="{ backgroundColor: src.color }"
@click="emit('setTarget', msg.msgType, src)"
>{{ src.name }}:{{ src.nation }} | <i class="bi bi-reply-fill"
/></span>
</template>
<template v-else> <template v-else>
<span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: dest.color }" <span :class="`msg_target msg_${srcColorType}`" :style="{ backgroundColor: src.color }">{{ src.name }}</span>
>{{ src.name }}:{{ src.nation }}</span
>
</template> </template>
<span class="msg_time">&lt;{{ msg.time }}&gt;</span> <span class="msg_time">&lt;{{ msg.time }}&gt;</span>
</div> </div>
<!-- eslint-disable-next-line vue/no-v-html vue/max-attributes-per-line --> <!-- eslint-disable-next-line vue/no-v-html vue/max-attributes-per-line -->
<div :class="['msg_content', isValidMsg ? 'msg_valid' : 'msg_invalid']" v-html="isValidMsg ? linkifyStr(msg.text) : '삭제된 메시지입니다'" <div
:class="['msg_content', isValidMsg ? 'msg_valid' : 'msg_invalid']"
v-html="isValidMsg ? linkifyStr(msg.text) : '삭제된 메시지입니다'"
></div> ></div>
<div v-if="msg.option.action" class="msg_prompt"> <div v-if="msg.option.action" class="msg_prompt">
<button <button
@@ -86,7 +125,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { MsgItem, MsgTarget } from "@/defs/API/Message"; import type { MsgItem, MsgTarget, MsgType } from "@/defs/API/Message";
import { parseTime } from "@/util/parseTime"; import { parseTime } from "@/util/parseTime";
import { differenceInMilliseconds, addMinutes } from "date-fns/esm"; import { differenceInMilliseconds, addMinutes } from "date-fns/esm";
import { computed, ref, toRef, watch, type ComputedRef, type Ref } from "vue"; import { computed, ref, toRef, watch, type ComputedRef, type Ref } from "vue";
@@ -106,13 +145,14 @@ const props = defineProps<{
const emit = defineEmits<{ const emit = defineEmits<{
(event: "response"): void; (event: "response"): void;
(event: "setTarget", type: MsgType, target: MsgTarget): void;
(event: "request-refresh"): void; (event: "request-refresh"): void;
}>(); }>();
const src: Ref<MsgTarget> = ref(props.modelValue.src); const src: Ref<MsgTarget> = ref(props.modelValue.src);
const dest: Ref<MsgTarget> = ref(props.modelValue.dest ?? props.modelValue.src); const dest: Ref<MsgTarget> = ref(props.modelValue.dest ?? props.modelValue.src);
const srcColorType = computed(() => isBrightColor(src.value.color) ? "bright" : "dark"); const srcColorType = computed(() => (isBrightColor(src.value.color) ? "bright" : "dark"));
const destColorType = computed(() => isBrightColor(dest.value.color) ? "bright" : "dark"); const destColorType = computed(() => (isBrightColor(dest.value.color) ? "bright" : "dark"));
const msg = toRef(props, "modelValue"); const msg = toRef(props, "modelValue");
const defaultIcon = `${window.pathConfig.sharedIcon}/default.jpg`; const defaultIcon = `${window.pathConfig.sharedIcon}/default.jpg`;
@@ -128,23 +168,20 @@ watch(
} }
); );
watch( watch(msg, (msg) => {
msg, isValidMsg.value = testValidMsg(msg);
(msg) => { deletable.value = testDeletable(msg);
isValidMsg.value = testValidMsg(msg);
deletable.value = testDeletable(msg);
src.value = msg.src; src.value = msg.src;
dest.value = msg.dest ?? { dest.value = msg.dest ?? {
id: 0, id: 0,
name: "", name: "",
nation: "재야", nation: "재야",
nation_id: 0, nation_id: 0,
color: "#000000", color: "#000000",
icon: defaultIcon, icon: defaultIcon,
}; };
}, });
);
const deletableTimer: Ref<number | undefined> = ref(); const deletableTimer: Ref<number | undefined> = ref();
const allowButton = computed(() => { const allowButton = computed(() => {
@@ -264,101 +301,100 @@ async function tryDecline() {
<style lang="scss" scoped> <style lang="scss" scoped>
.msg_plate { .msg_plate {
width: 100%; width: 100%;
display: grid; display: grid;
grid-template-columns: 64px 1fr; grid-template-columns: 64px 1fr;
border-bottom: solid 1px gray; border-bottom: solid 1px gray;
min-height: 64px; min-height: 64px;
font-size: 12.5px; font-size: 12.5px;
word-break: break-all; word-break: break-all;
color: white; color: white;
} }
.msg_plate_private { .msg_plate_private {
background-color: #5d1e1a; background-color: #5d1e1a;
} }
.msg_plate_private.msg_plate_dest { .msg_plate_private.msg_plate_dest {
background-color: #5d461a; background-color: #5d461a;
} }
.msg_plate_public { .msg_plate_public {
background-color: #141c65; background-color: #141c65;
} }
.msg_plate_national, .msg_plate_national,
.msg_plate_diplomacy { .msg_plate_diplomacy {
background-color: #00582c; background-color: #00582c;
} }
.msg_plate_national.msg_plate_dest, .msg_plate_national.msg_plate_dest,
.msg_plate_diplomacy.msg_plate_dest { .msg_plate_diplomacy.msg_plate_dest {
background-color: #704615; background-color: #704615;
} }
.msg_plate_national.msg_plate_src, .msg_plate_national.msg_plate_src,
.msg_plate_diplomacy.msg_plate_src { .msg_plate_diplomacy.msg_plate_src {
background-color: #70153b; background-color: #70153b;
} }
.msg_icon { .msg_icon {
width: 64px; width: 64px;
height: 64px; height: 64px;
border-right: solid 1px gray; border-right: solid 1px gray;
} }
.msg_time { .msg_time {
font-size: 0.75em; font-size: 0.75em;
font-weight: normal; font-weight: normal;
} }
.msg_header { .msg_header {
font-weight: bold; font-weight: bold;
margin-bottom: 3px; margin-bottom: 3px;
color: white; color: white;
position: relative; position: relative;
} }
.msg_invalid { .msg_invalid {
color: rgba(255, 255, 255, 0.5); color: rgba(255, 255, 255, 0.5);
} }
.msg_content { .msg_content {
margin-left: 10px; margin-left: 10px;
margin-right: 5px; margin-right: 5px;
overflow: hidden; overflow: hidden;
} }
.msg_target { .msg_target {
margin: 2px 2px 0 2px; margin: 2px 2px 0 2px;
padding: 2px 3px; padding: 2px 3px;
display: inline-block; display: inline-block;
box-shadow: 2px 2px black; box-shadow: 2px 2px black;
border-radius: 3px; border-radius: 3px;
} }
.msg_target.msg_bright { .msg_target.msg_bright {
color: black; color: black;
} }
.msg_target.msg_dark { .msg_target.msg_dark {
color: white; color: white;
} }
.msg_from_to { .msg_from_to {
display: inline-block; display: inline-block;
} }
.msg_prompt { .msg_prompt {
text-align: right; text-align: right;
margin-top: 5px; margin-top: 5px;
margin-right: 5px; margin-right: 5px;
} }
.btn-delete-msg { .btn-delete-msg {
position: absolute; position: absolute;
right: 0; right: 0;
top: 0; top: 0;
margin: 2px 2px 0 2px; margin: 2px 2px 0 2px;
font-size: 8px; font-size: 8px;
} }
</style>
</style>