356 lines
10 KiB
Vue
356 lines
10 KiB
Vue
<template>
|
|
<div id="outBlock">
|
|
<div id="outBlock2">
|
|
<nav class="navbar navbar-expand navbar-dark bg-dark d-sm-block p-0">
|
|
<div class="container-fluid px-0">
|
|
<div class="collapse navbar-collapse">
|
|
<ul class="navbar-nav me-auto mx-auto">
|
|
<li class="nav-item">테스트</li>
|
|
<li class="nav-item">테스트</li>
|
|
<li class="nav-item">테스트</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
<!-- eslint-disable-next-line vue/max-attributes-per-line -->
|
|
<BContainer v-if="asyncReady" id="container" :position="'position-relative'" :toast="{ root: true }" class="bg0">
|
|
<main>
|
|
<div class="commonToolbar">툴바?</div>
|
|
<div class="gameInfo">시나리오</div>
|
|
<div class="gameInfo">토너먼트/설문</div>
|
|
<div class="gameInfo2">접속중인</div>
|
|
<div class="nationNotice">국방</div>
|
|
<div>접속자</div>
|
|
<div class="d-grid"><BButton @click="tryRefresh">갱신</BButton></div>
|
|
<!-- TODO: 운영자 툴바는 어디에?-->
|
|
<div class="mapView">지도</div>
|
|
<div class="reservedCommandZone">예턴</div>
|
|
<div class="cityInfo">도시 정보</div>
|
|
<div class="nationInfo">국가 정보</div>
|
|
<div v-if="frontInfo && generalInfo && nationStaticInfo" class="generalInfo">
|
|
<GeneralBasicCard
|
|
:general="generalInfo"
|
|
:nation="nationStaticInfo"
|
|
:troopInfo="frontInfo.general.troopInfo"
|
|
:turnTerm="frontInfo.global.turnterm"
|
|
:lastExecuted="lastExecuted"
|
|
/>
|
|
</div>
|
|
<div class="generalCommandToolbar">국가 툴바</div>
|
|
<div class="actionMiniPlate">갱신/로비 버튼</div>
|
|
<div class="PublicRecord">
|
|
<div class="title">장수 동향</div>
|
|
<template v-for="[idx, rawText] of globalRecords.toArray()" :key="idx">
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
|
|
</template>
|
|
</div>
|
|
<div class="GeneralLog">
|
|
<div class="title">개인 기록</div>
|
|
<template v-for="[idx, rawText] of generalRecords.toArray()" :key="idx">
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
|
|
</template>
|
|
</div>
|
|
<div class="WorldHistory">
|
|
<div class="title">중원 정세</div>
|
|
<template v-for="[idx, rawText] of worldHistory.toArray()" :key="idx">
|
|
<!-- eslint-disable-next-line vue/no-v-html -->
|
|
<div :v-data-idx="idx" v-html="formatLog(rawText)" />
|
|
</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>
|
|
<div class="commonToolbar">툴바?</div>
|
|
</main>
|
|
</BContainer>
|
|
<div v-else>서버 갱신 중입니다.</div>
|
|
</div>
|
|
<nav id="mobileBottomBar">
|
|
<BButton>하단 바</BButton>
|
|
<BButton>하단 바2</BButton>
|
|
</nav>
|
|
</div>
|
|
</template>
|
|
<script lang="ts">
|
|
declare const staticValues: {
|
|
serverName: string;
|
|
serverNick: string;
|
|
serverID: string;
|
|
mapName: string;
|
|
unitSet: string;
|
|
};
|
|
</script>
|
|
<script lang="ts" setup>
|
|
import { BContainer, BButton, useToast } from "bootstrap-vue-3";
|
|
import { isString } from "lodash";
|
|
import { provide, ref, watch } from "vue";
|
|
import { GameConstStore, getGameConstStore } from "./GameConstStore";
|
|
import { SammoAPI, type InvalidResponse } from "./SammoAPI";
|
|
import { parseTime } from "./util/parseTime";
|
|
import { unwrap } from "./util/unwrap";
|
|
import Denque from "denque";
|
|
import { formatLog } from "@/utilGame/formatLog";
|
|
import type { ExecuteResponse, GetFrontInfoResponse } from "./defs/API/Global";
|
|
import { delay } from "./util/delay";
|
|
import GeneralBasicCard from "./components/GeneralBasicCard.vue";
|
|
import type { GeneralListItemP1 } from "./defs/API/Nation";
|
|
import type { NationStaticItem } from "./defs";
|
|
|
|
const { serverName, serverNick, serverID } = staticValues;
|
|
|
|
const asyncReady = ref(false);
|
|
const gameConstStore = ref<GameConstStore>();
|
|
|
|
const toasts = unwrap(useToast());
|
|
|
|
provide("gameConstStore", gameConstStore);
|
|
|
|
const lastExecuted = ref<Date>(parseTime("2022-08-15 00:00:00"));
|
|
const serverLocked = ref(true);
|
|
const refreshCounter = ref(0);
|
|
|
|
const storeP = getGameConstStore().then((store) => {
|
|
gameConstStore.value = store;
|
|
});
|
|
|
|
let responseLock = false;
|
|
|
|
async function tryRefresh() {
|
|
if (responseLock) {
|
|
return;
|
|
}
|
|
try {
|
|
responseLock = true;
|
|
const responseP = SammoAPI.Global.ExecuteEngine({ serverID }, true).then((response) => {
|
|
if (response.result) {
|
|
lastExecuted.value = parseTime(response.lastExecuted);
|
|
serverLocked.value = response.locked;
|
|
}
|
|
return response;
|
|
});
|
|
|
|
//TODO: 갱신 알림 띄우기
|
|
const response = await Promise.race([delay(3000), responseP]);
|
|
responseLock = false;
|
|
|
|
if (response === undefined) {
|
|
//timeout이지만 일단 갱신한다.
|
|
refreshCounter.value += 1;
|
|
return;
|
|
}
|
|
|
|
if (!response.result) {
|
|
if (response.reqRefresh) {
|
|
alert(`갱신이 필요합니다: ${response.reason}`);
|
|
window.location.reload();
|
|
return;
|
|
}
|
|
|
|
console.error(response.reason);
|
|
if (!asyncReady.value) {
|
|
throw response.reason;
|
|
}
|
|
toasts.danger({
|
|
title: "갱신 실패",
|
|
body: response.reason,
|
|
});
|
|
return;
|
|
}
|
|
|
|
refreshCounter.value += 1;
|
|
|
|
//TODO: 서버와 클라이언트 버전이 다르다면 갱신 필요
|
|
} catch (e) {
|
|
responseLock = false;
|
|
//매우 심각한 버그
|
|
console.error(e);
|
|
alert(`서버 갱신 실패: ${e}`);
|
|
throw e;
|
|
}
|
|
}
|
|
|
|
void Promise.all([storeP, tryRefresh()]).then(() => {
|
|
asyncReady.value = true;
|
|
});
|
|
|
|
const lastGeneralRecordID = ref(0);
|
|
const lastWorldHistoryID = ref(0);
|
|
|
|
const generalRecords = ref(new Denque<[number, string]>());
|
|
const globalRecords = ref(new Denque<[number, string]>());
|
|
const worldHistory = ref(new Denque<[number, string]>());
|
|
|
|
let recordLock = false;
|
|
|
|
watch(refreshCounter, async () => {
|
|
if (recordLock) {
|
|
return;
|
|
}
|
|
try {
|
|
recordLock = true;
|
|
const response = await SammoAPI.Global.GetRecentRecord({
|
|
lastGeneralRecordID: lastGeneralRecordID.value,
|
|
lastWorldHistoryID: lastWorldHistoryID.value,
|
|
});
|
|
recordLock = false;
|
|
|
|
if (response.flushGeneral) {
|
|
generalRecords.value = new Denque<[number, string]>();
|
|
}
|
|
if (response.flushGlobal) {
|
|
globalRecords.value = new Denque<[number, string]>();
|
|
}
|
|
if (response.flushHistory) {
|
|
worldHistory.value = new Denque<[number, string]>();
|
|
}
|
|
|
|
if (response.general.length) {
|
|
lastGeneralRecordID.value = Math.max(lastGeneralRecordID.value, response.general[0][0]);
|
|
}
|
|
if (response.global.length) {
|
|
lastGeneralRecordID.value = Math.max(lastGeneralRecordID.value, response.global[0][0]);
|
|
}
|
|
if (response.history.length) {
|
|
lastWorldHistoryID.value = Math.max(lastWorldHistoryID.value, response.history[0][0]);
|
|
}
|
|
|
|
while (response.general.length) {
|
|
const [id, record] = unwrap(response.general.pop());
|
|
if (!generalRecords.value.isEmpty() && id <= unwrap(generalRecords.value.get(0))[0]) {
|
|
continue;
|
|
}
|
|
if (generalRecords.value.length >= 15) {
|
|
generalRecords.value.pop();
|
|
}
|
|
generalRecords.value.unshift([id, record]);
|
|
}
|
|
|
|
while (response.global.length) {
|
|
const [id, record] = unwrap(response.global.pop());
|
|
if (!globalRecords.value.isEmpty() && id <= unwrap(globalRecords.value.get(0))[0]) {
|
|
continue;
|
|
}
|
|
if (globalRecords.value.length >= 15) {
|
|
globalRecords.value.pop();
|
|
}
|
|
globalRecords.value.unshift([id, record]);
|
|
}
|
|
|
|
while (response.history.length) {
|
|
const [id, record] = unwrap(response.history.pop());
|
|
if (!worldHistory.value.isEmpty() && id <= unwrap(worldHistory.value.get(0))[0]) {
|
|
continue;
|
|
}
|
|
if (worldHistory.value.length >= 15) {
|
|
worldHistory.value.pop();
|
|
}
|
|
worldHistory.value.unshift([id, record]);
|
|
}
|
|
} catch (e) {
|
|
recordLock = false;
|
|
console.error(e);
|
|
toasts.danger({
|
|
title: "최근 기록 갱신 실패",
|
|
body: `${e}`,
|
|
});
|
|
}
|
|
});
|
|
|
|
const frontInfo = ref<GetFrontInfoResponse>();
|
|
|
|
const generalInfo = ref<GeneralListItemP1>();
|
|
const nationStaticInfo = ref<NationStaticItem>();
|
|
|
|
let generalInfoLock = false;
|
|
|
|
watch(refreshCounter, async () => {
|
|
if (generalInfoLock) {
|
|
return;
|
|
}
|
|
try {
|
|
generalInfoLock = true;
|
|
const response = await SammoAPI.General.GetFrontInfo();
|
|
generalInfoLock = false;
|
|
|
|
frontInfo.value = response;
|
|
generalInfo.value = response.general;
|
|
|
|
const newLastExecuted = parseTime(response.global.lastExecuted);
|
|
if(newLastExecuted.getTime() > lastExecuted.value.getTime()){
|
|
lastExecuted.value = newLastExecuted;
|
|
}
|
|
|
|
const rawNation = response.nation;
|
|
nationStaticInfo.value = {
|
|
nation: rawNation.id,
|
|
name: rawNation.name,
|
|
color: rawNation.color,
|
|
type: rawNation.type.raw,
|
|
level: rawNation.level,
|
|
capital: rawNation.capital,
|
|
gennum: rawNation.gennum,
|
|
power: rawNation.power,
|
|
};
|
|
} catch (e) {
|
|
recordLock = false;
|
|
console.error(e);
|
|
toasts.danger({
|
|
title: "최근 정보 갱신 실패",
|
|
body: `${e}`,
|
|
});
|
|
}
|
|
});
|
|
</script>
|
|
<style lang="scss" scoped>
|
|
@import "@scss/common/break_500px.scss";
|
|
|
|
#mobileBottomBar {
|
|
display: none;
|
|
}
|
|
|
|
#outBlock {
|
|
display: flex;
|
|
flex-flow: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
@include media-500px {
|
|
#outBlock {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100%;
|
|
height: 100%;
|
|
|
|
#mobileBottomBar {
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
#outBlock2 {
|
|
flex-grow: 1;
|
|
overflow-y: scroll;
|
|
}
|
|
|
|
#container {
|
|
width: 500px;
|
|
|
|
> main > div {
|
|
min-height: 100px;
|
|
/* 테스트 */
|
|
}
|
|
}
|
|
}
|
|
|
|
@include media-1000px {
|
|
#container {
|
|
width: 1000px;
|
|
}
|
|
}
|
|
</style>
|