wip: MessagePanel
This commit is contained in:
@@ -27,7 +27,7 @@
|
||||
<div class="subOnlineUserCnt">전체 접속자 수: {{globalInfo.onlineUserCnt.toLocaleString()}}명</div>
|
||||
<div class="subAPILimit">턴당 갱신횟수: {{globalInfo.apiLimit.toLocaleString()}}회</div>
|
||||
<div class="subGeneralCnt">
|
||||
등록 장수: 유저
|
||||
등록 장수: 유저
|
||||
{{globalInfo.genCount.toLocaleString()}} / {{globalInfo.generalCntLimit.toLocaleString()}}
|
||||
+ NPC TODO:NPC Cnt 명
|
||||
</div>
|
||||
@@ -86,11 +86,7 @@
|
||||
</template>
|
||||
</div>
|
||||
<div class="commonToolbar">툴바?</div>
|
||||
<div class="MessageInputForm">메시지 입력</div>
|
||||
<div class="PublicTalk">전체 메시지</div>
|
||||
<div class="NationalTalk">국가 메시지</div>
|
||||
<div class="PrivateTalk">개인 메시지</div>
|
||||
<div class="DiplomacyTalk">외교 메시지</div>
|
||||
<MessagePanel v-if="generalInfo" :generalID="generalInfo.no" :generalName="generalInfo.name" :nationID="generalInfo.nation" :permissionLevel="generalInfo.permission" ref="msgPanel" />
|
||||
<div class="commonToolbar">툴바?</div>
|
||||
</main>
|
||||
</BContainer>
|
||||
@@ -126,6 +122,7 @@ import { delay } from "./util/delay";
|
||||
import CityBasicCard from "./components/CityBasicCard.vue";
|
||||
import NationBasicCard from "./components/NationBasicCard.vue";
|
||||
import GeneralBasicCard from "./components/GeneralBasicCard.vue";
|
||||
import MessagePanel from "./components/MessagePanel.vue";
|
||||
import type { GeneralListItemP1 } from "./defs/API/Nation";
|
||||
import type { NationStaticItem } from "./defs";
|
||||
import { formatTime } from "./util/formatTime";
|
||||
@@ -150,7 +147,12 @@ const storeP = getGameConstStore().then((store) => {
|
||||
|
||||
let responseLock = false;
|
||||
|
||||
const msgPanel = ref<InstanceType<typeof MessagePanel>>();
|
||||
|
||||
async function tryRefresh() {
|
||||
|
||||
msgPanel.value?.tryRefresh();
|
||||
|
||||
if (responseLock) {
|
||||
return;
|
||||
}
|
||||
@@ -293,7 +295,7 @@ watch(refreshCounter, async () => {
|
||||
|
||||
const frontInfo = ref<GetFrontInfoResponse>();
|
||||
const globalInfo = ref<GetFrontInfoResponse['global']>({} as GetFrontInfoResponse['global'])
|
||||
const generalInfo = ref<GeneralListItemP1>();
|
||||
const generalInfo = ref<GetFrontInfoResponse['general']>();
|
||||
const nationStaticInfo = ref<NationStaticItem>();
|
||||
|
||||
let generalInfoLock = false;
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
<template>
|
||||
<div class="MessageInputForm">메시지 입력</div>
|
||||
<div class="PublicTalk">
|
||||
<div>전체 메시지</div>
|
||||
<template v-if="messagePublic.length == 0">
|
||||
<div>메시지가 없습니다.</div>
|
||||
</template>
|
||||
<MessagePlate
|
||||
v-for="msg of messagePublic"
|
||||
:key="msg.id"
|
||||
:modelValue="msg"
|
||||
:generalID="generalID"
|
||||
:generalName="generalName"
|
||||
:nationID="nationID"
|
||||
:permissionLevel="permissionLevel"
|
||||
></MessagePlate>
|
||||
</div>
|
||||
<div class="NationalTalk">
|
||||
<div>국가 메시지</div>
|
||||
<template v-if="messageNational.length == 0">
|
||||
<div>메시지가 없습니다.</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="PrivateTalk">
|
||||
<div>개인 메시지</div>
|
||||
<template v-if="messagePrivate.length == 0">
|
||||
<div>메시지가 없습니다.</div>
|
||||
</template>
|
||||
</div>
|
||||
<div class="DiplomacyTalk">
|
||||
<div>외교 메시지</div>
|
||||
<template v-if="messageDiplomacy.length == 0">
|
||||
<div>메시지가 없습니다.</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, toRef, type Ref } from "vue";
|
||||
import { delay } from "@/util/delay";
|
||||
import { SammoAPI } from "@/SammoAPI";
|
||||
import { isString } from "lodash";
|
||||
import type { MsgItem, MsgResponse, MsgType } from "@/defs/API/Message";
|
||||
import MessagePlate from "@/components/MessagePlate.vue";
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: "request-refresh"): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
generalID: number;
|
||||
generalName: string;
|
||||
nationID: number;
|
||||
permissionLevel: number;
|
||||
}>();
|
||||
|
||||
const generalID = toRef(props, "generalID");
|
||||
const generalName = toRef(props, "generalName");
|
||||
const nationID = toRef(props, "nationID");
|
||||
const permissionLevel = toRef(props, "permissionLevel");
|
||||
|
||||
onMounted(() => {
|
||||
console.log("MessagePanel mounted");
|
||||
console.log(props);
|
||||
});
|
||||
|
||||
const lastSequence = ref(-1);
|
||||
|
||||
const initRefreshLimit = 5;
|
||||
let refreshLimit = initRefreshLimit;
|
||||
const refreshP: Set<Promise<boolean>> = new Set();
|
||||
let lastRefreshDone = 0;
|
||||
let refreshTimer: null | number = null;
|
||||
|
||||
const messageStorage = new Map<number, MsgItem>();
|
||||
const messagePublic = ref<MsgItem[]>([]);
|
||||
const messageNational = ref<MsgItem[]>([]);
|
||||
const messagePrivate = ref<MsgItem[]>([]);
|
||||
const messageDiplomacy = ref<MsgItem[]>([]);
|
||||
|
||||
const messageIndexedList: Record<MsgType, Ref<MsgItem[]>> = {
|
||||
public: messagePublic,
|
||||
national: messageNational,
|
||||
private: messagePrivate,
|
||||
diplomacy: messageDiplomacy,
|
||||
};
|
||||
|
||||
function updateMsgResponse(response: MsgResponse) {
|
||||
if (!response.keepRecent) {
|
||||
messageStorage.clear();
|
||||
for (const msgList of Object.values(messageIndexedList)) {
|
||||
msgList.value.length = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if(response.generalName != generalName.value){
|
||||
emit("request-refresh");
|
||||
return;
|
||||
}
|
||||
if(response.nationID != nationID.value){
|
||||
emit("request-refresh");
|
||||
return;
|
||||
}
|
||||
|
||||
lastSequence.value = Math.max(lastSequence.value, response.sequence);
|
||||
|
||||
for (const msgType of Object.keys(messageIndexedList) as (keyof typeof messageIndexedList)[]) {
|
||||
const msgList = messageIndexedList[msgType];
|
||||
|
||||
|
||||
const newMsgList = response[msgType];
|
||||
console.log(msgType, msgList, newMsgList);
|
||||
if (newMsgList.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
//순서가 어떤 순서인지 모르니, 여기서 내림차순으로 맞춘다.
|
||||
newMsgList.sort((a, b) => b.id - a.id);
|
||||
|
||||
if (msgList.value.length == 0) {
|
||||
msgList.value = newMsgList;
|
||||
continue;
|
||||
}
|
||||
|
||||
//head test
|
||||
const filteredMsgList: MsgItem[] = [];
|
||||
|
||||
for (const msg of newMsgList) {
|
||||
const oldMsg = messageStorage.get(msg.id);
|
||||
if (oldMsg !== undefined) {
|
||||
continue;
|
||||
}
|
||||
messageStorage.set(msg.id, msg);
|
||||
filteredMsgList.push(msg);
|
||||
}
|
||||
|
||||
if (filteredMsgList.length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const msgHeadID = msgList.value[0].id;
|
||||
const newMsgTailID = filteredMsgList[filteredMsgList.length - 1].id;
|
||||
if (newMsgTailID > msgHeadID) {
|
||||
msgList.value = [...filteredMsgList, ...msgList.value];
|
||||
continue;
|
||||
}
|
||||
|
||||
const msgTailID = msgList.value[msgList.value.length - 1].id;
|
||||
const newMsgHeadID = filteredMsgList[0].id;
|
||||
|
||||
if (msgTailID > newMsgHeadID) {
|
||||
msgList.value.push(...filteredMsgList);
|
||||
continue;
|
||||
}
|
||||
|
||||
//중간에 삽입되는 경우는 에러이다.
|
||||
console.error("중간 삽입 있음", msgType, newMsgList);
|
||||
}
|
||||
}
|
||||
|
||||
function beginRefreshTimer() {
|
||||
if (refreshTimer) {
|
||||
clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
}
|
||||
refreshTimer = window.setInterval(function () {
|
||||
const now = Date.now();
|
||||
if (lastRefreshDone + 5000 < now) {
|
||||
//만약 서버 응답이 없다면?
|
||||
|
||||
if (refreshP.size > refreshLimit && refreshTimer) {
|
||||
clearInterval(refreshTimer);
|
||||
refreshTimer = null;
|
||||
alert("서버 응답이 없습니다. 새로고침을 해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
refreshP.add(tryRefresh());
|
||||
}
|
||||
}, 2500);
|
||||
}
|
||||
|
||||
async function tryRefresh() {
|
||||
if (refreshP.size > 0) {
|
||||
await Promise.race([...refreshP, delay(500)]);
|
||||
}
|
||||
|
||||
const waiterP = (async () => {
|
||||
let response: MsgResponse | undefined = undefined;
|
||||
try {
|
||||
response = await SammoAPI.Message.GetRecentMessage({
|
||||
sequence: lastSequence.value,
|
||||
});
|
||||
|
||||
const now = Date.now();
|
||||
if (lastRefreshDone < now) {
|
||||
lastRefreshDone = now;
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
if (isString(e)) {
|
||||
alert(e);
|
||||
}
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
if(!response){
|
||||
return false;
|
||||
}
|
||||
|
||||
updateMsgResponse(response);
|
||||
|
||||
return true;
|
||||
})();
|
||||
void waiterP.then((result) => {
|
||||
if (!result) {
|
||||
refreshLimit--;
|
||||
} else {
|
||||
refreshLimit = initRefreshLimit;
|
||||
}
|
||||
refreshP.delete(waiterP);
|
||||
});
|
||||
refreshP.add(waiterP);
|
||||
|
||||
return waiterP;
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
tryRefresh,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
beginRefreshTimer();
|
||||
await tryRefresh();
|
||||
});
|
||||
</script>
|
||||
@@ -143,7 +143,6 @@ watch(
|
||||
icon: defaultIcon,
|
||||
};
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const deletableTimer: Ref<number | undefined> = ref();
|
||||
|
||||
@@ -7,7 +7,6 @@ include "func.php";
|
||||
//로그인 검사
|
||||
|
||||
Session::requireGameLogin()->setReadOnly();
|
||||
|
||||
//턴 실행 대기 및 갱신 증가는 API에서
|
||||
|
||||
//TODO: 내 정보도 API에서, 사망 여부도
|
||||
|
||||
Reference in New Issue
Block a user