diff --git a/hwe/t_diplomacy.php b/hwe/t_diplomacy.php
index ce69ab3d..8f4fcf93 100644
--- a/hwe/t_diplomacy.php
+++ b/hwe/t_diplomacy.php
@@ -49,10 +49,7 @@ var permissionLevel = =$permission?>; //
=WebUtil::printJS('../d_shared/common_path.js')?>
=WebUtil::printJS('dist_js/vendors.js')?>
-=WebUtil::printJS('dist_js/common.js')?>
-=WebUtil::printJS('../e_lib/select2/select2.full.min.js')?>
-
-=WebUtil::printJS('js/diplomacy.js')?>
+=WebUtil::printJS('dist_js/diplomacy.js')?>
diff --git a/hwe/ts/defs.ts b/hwe/ts/defs.ts
index 83ee513a..5f3310f3 100644
--- a/hwe/ts/defs.ts
+++ b/hwe/ts/defs.ts
@@ -20,7 +20,7 @@ export type GeneralListResponse = {
result: true,
list: [
number,
- string, 0|1,
+ string, 0 | 1,
number, number, string,
string, string, string,
string, string | null,
@@ -31,4 +31,39 @@ export type GeneralListResponse = {
number, number
][],
token: Record,
+}
+
+export type NationLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
+export const NationLevelText: Record = {
+ 0: '방랑군',
+ 1: '호족',
+ 2: '군벌',
+ 3: '주자사',
+ 4: '주목',
+ 5: '공',
+ 6: '왕',
+ 7: '황제',
+}
+
+export type CityLevel = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
+export const CityLevelText: Record = {
+ 1: '수',
+ 2: '진',
+ 3: '관',
+ 4: '이',
+ 5: '소',
+ 6: '중',
+ 7: '대',
+ 8: '특'
+}
+
+export type NationStaticItem = {
+ nation: number,
+ name: string,
+ color: string,
+ type: string,
+ level: NationLevel,
+ capital: number,
+ gennum: number,
+ power: number,
}
\ No newline at end of file
diff --git a/hwe/ts/diplomacy.ts b/hwe/ts/diplomacy.ts
new file mode 100644
index 00000000..546aff5d
--- /dev/null
+++ b/hwe/ts/diplomacy.ts
@@ -0,0 +1,496 @@
+import $ from 'jquery';
+import { unwrap_any } from './util/unwrap_any';
+import axios from 'axios';
+import { isBrightColor } from './common_legacy';
+import { setAxiosXMLHttpRequest } from './util/setAxiosXMLHttpRequest';
+import { isString } from 'lodash';
+import { convertFormData } from './util/convertFormData';
+import { InvalidResponse, NationStaticItem } from './defs';
+import { escapeHtml } from './legacy/escapeHtml';
+import { nl2br } from './util/nl2br';
+import { unwrap } from './util/unwrap';
+import { exportWindow } from './util/exportWindow';
+import Popper from 'popper.js';
+exportWindow(Popper, 'Popper');
+import 'bootstrap';
+import 'select2/dist/js/select2.full.js'
+import { LoadingData } from 'select2';
+
+declare const permissionLevel: number;
+let myNationID: number | undefined;
+
+type LetterNationTarget = {
+ nationID: number,
+ nationName: string,
+ nationColor: string,
+}
+type LetterFullTarget = LetterNationTarget & {
+ nationID: number,
+ nationName: string,
+ nationColor: string,
+ generalName: string,
+ generalIcon: string,
+}
+
+type LetterState = 'proposed' | 'activated' | 'cancelled' | 'replaced';
+
+type LetterItem = {
+ no: number,
+
+ src: LetterFullTarget,
+ dest: LetterNationTarget | LetterFullTarget,
+ prev_no: number | null,
+ state: LetterState,
+ state_opt: 'try_destroy_src' | 'try_destroy_dest' | null,
+ brief: string,
+ detail: string,
+ date: string,
+}
+
+
+
+type LetterResponse = {
+ result: true,
+ nations: Record,
+ letters: Record,
+ myNationID: number,
+}
+
+const stateText: Record = {
+ 'proposed': '제안됨',
+ 'activated': '승인됨',
+ 'cancelled': '거부됨',
+ 'replaced': '대체됨',
+};
+
+const stateOptionText: Record, string> = {
+ 'try_destroy_src': '송신측의 파기 요청',
+ 'try_destroy_dest': '수신측의 파기 요청',
+}
+
+async function submitLetter(e: JQuery.Event): Promise {
+ e.preventDefault();
+
+ const $brief = $('#inputBrief');
+ const $detail = $('#inputDetail');
+ const $prevNo = $('#inputPrevNo');
+ const $destNation = $('#inputDestNation');
+ const brief = $.trim(unwrap_any($brief.val()));
+ const detail = $.trim(unwrap_any($detail.val()));
+ let prevNo = $prevNo.val() as number | undefined;
+ const destNation = unwrap_any($destNation.val());
+
+ if (prevNo !== undefined && prevNo < 1) {
+ prevNo = undefined;
+ }
+
+ console.log(brief);
+ if (!brief) {
+ return;
+ }
+
+ $brief.val('');
+ $detail.val('');
+
+ let result: InvalidResponse;
+
+ try {
+ const response = await axios({
+ url: 'j_diplomacy_send_letter.php',
+ responseType: 'json',
+ method: 'post',
+ data: convertFormData({
+ brief: brief,
+ detail: detail,
+ destNation: destNation,
+ prevNo: prevNo ?? null,
+ })
+ });
+ result = response.data;
+ if (!result.result) {
+ throw result.reason;
+ }
+ }
+ catch (e) {
+ console.error(e);
+ alert(`외교 서신을 보내는데 실패했습니다: ${e}`);
+ return;
+ }
+
+ alert('전송했습니다.');
+ location.reload();
+}
+
+async function repondLetter(letterNo: number, isAgree: boolean, reason: string | null): Promise {
+
+ let result: InvalidResponse;
+ try {
+ const response = await axios({
+ url: 'j_diplomacy_respond_letter.php',
+ responseType: 'json',
+ method: 'post',
+ data: convertFormData({
+ letterNo: letterNo,
+ isAgree: isAgree,
+ reason: reason,
+ })
+ });
+ result = response.data;
+ if (!result.result) {
+ throw result.reason;
+ }
+ }
+ catch (e) {
+ console.error(e);
+ alert(`응답을 실패했습니다: ${e}`);
+ return;
+ }
+
+ if (isAgree) {
+ alert('승인했습니다.');
+ }
+ else {
+ alert('거부했습니다.');
+ }
+ location.reload();
+}
+
+async function rollbackLetter(letterNo: number) {
+ let result: InvalidResponse;
+ try {
+ const response = await axios({
+ url: 'j_diplomacy_rollback_letter.php',
+ responseType: 'json',
+ method: 'post',
+ data: convertFormData({
+ letterNo: letterNo,
+ })
+ });
+ result = response.data;
+ if (!result.result) {
+ throw result.reason;
+ }
+ }
+ catch (e) {
+ console.error(e);
+ alert(`회수를 실패했습니다: ${e}`);
+ return;
+ }
+
+ alert('회수 했습니다.');
+ location.reload();
+}
+
+async function destroyLetter(letterNo: number) {
+ let result: InvalidResponse | {
+ result: true,
+ state: LetterState
+ };
+ try {
+ const response = await axios({
+ url: 'j_diplomacy_destroy_letter.php',
+ responseType: 'json',
+ method: 'post',
+ data: convertFormData({
+ letterNo: letterNo,
+ })
+ });
+ result = response.data;
+ if (!result.result) {
+ throw result.reason;
+ }
+ }
+ catch (e) {
+ console.error(e);
+ alert(`회수를 실패했습니다: ${e}`);
+ return;
+ }
+
+ if (result.state == 'activated') {
+ alert('파기를 요청 했습니다.');
+ }
+ else {
+ alert('파기 되었습니다.');
+ }
+
+ location.reload();
+}
+
+function drawLetter(letterObj: LetterItem) {
+
+ if (letterObj.state == 'cancelled') {
+ //TODO: 취소되거나, 대체된 문서도 보여줄 방법을 찾아볼 것
+ return;
+ }
+
+ console.log(letterObj);
+
+ const $letterFrame = $('#letterTemplate > .letterFrame');
+
+ const srcColorFormat = {
+ 'background-color': letterObj.src.nationColor,
+ 'color': isBrightColor(letterObj.src.nationColor) ? '#000000' : '#ffffff'
+ };
+
+ const destColorFormat = {
+ 'background-color': letterObj.dest.nationColor,
+ 'color': isBrightColor(letterObj.dest.nationColor) ? '#000000' : '#ffffff'
+ };
+
+ const targetNation = letterObj.src.nationID == myNationID ? letterObj.dest : letterObj.src;
+ const targetColor = letterObj.src.nationID == myNationID ? destColorFormat : srcColorFormat;
+
+ const $letter = $letterFrame.clone();
+
+ if (letterObj.state == 'replaced') {
+ $letter.hide();
+ }
+
+ $letter.addClass('letterObj')
+ .data('no', letterObj.no)
+ .data('brief', letterObj.brief)
+ .data('detail', letterObj.detail)
+ .attr('id', 'letter_' + letterObj.no);
+
+ $letter.find('.letterHeader').css(targetColor);
+ $letter.find('.letterNationName').text(targetNation.nationName);
+ $letter.find('.letterDate').text(letterObj.date);
+ $letter.find('.letterNo').text('#' + letterObj.no);
+
+ $letter.find('.letterStatus').text(stateText[letterObj.state]);
+
+ if (letterObj.state_opt !== null) {
+ $letter.find('.letterStatusOpt').text(`(${unwrap(stateOptionText[letterObj.state_opt])})`);
+ }
+ if (letterObj.prev_no !== null) {
+ const $showPrev = $(`#${letterObj.prev_no}`);
+ $showPrev.click(function () {
+ $('#letter_' + letterObj.prev_no).toggle();
+ })
+ $letter.find('.letterPrevNo').empty().append($showPrev);
+ }
+ else {
+ $letter.find('.letterPrevNo').text('신규');
+ }
+ $letter.find('.letterBrief').html(nl2br(escapeHtml(letterObj.brief)));
+ $letter.find('.letterDetail').html(nl2br(escapeHtml(letterObj.detail)));
+
+ $letter.find('.letterSrc .signerImg img.generalIcon').attr('src', letterObj.src.generalIcon);
+ $letter.find('.letterSrc .signerNation').text(letterObj.src.nationName).css(srcColorFormat);
+ $letter.find('.letterSrc .signerName').text(letterObj.src.generalName).css(srcColorFormat);
+
+ if ('generalName' in letterObj.dest) {
+ $letter.find('.letterDest .signerImg img.generalIcon').attr('src', letterObj.dest.generalIcon);
+ $letter.find('.letterDest .signerNation').text(letterObj.dest.nationName).css(destColorFormat);
+ $letter.find('.letterDest .signerName').text(letterObj.dest.generalName).css(destColorFormat);
+ }
+
+ if (letterObj.state == 'proposed' && letterObj.src.nationID != myNationID) {
+ $letter.find('.btnAgree').show().click(async function (e) {
+ e.preventDefault();
+ if (!confirm('승인하시겠습니까?')) {
+ return;
+ }
+ await repondLetter(letterObj.no, true, null);
+ });
+ $letter.find('.btnDisagree').show().click(async function (e) {
+ e.preventDefault();
+ let reason = prompt('거부하시겠습니까? (이유 [최대 50자])');
+ if (reason === null) {
+ return;
+ }
+ reason = reason.substr(0, 50);
+ await repondLetter(letterObj.no, false, reason);
+ });
+ }
+
+ if (letterObj.state == 'proposed' && letterObj.src.nationID == myNationID) {
+ $letter.find('.btnRollback').show().click(async function (e) {
+ e.preventDefault();
+ if (!confirm('회수하시겠습니까?')) {
+ return false;
+ }
+ await rollbackLetter(letterObj.no);
+ });
+ }
+
+ if (letterObj.state == 'activated') {
+ const $btnDestroy = $letter.find('.btnDestroy');
+ if ((letterObj.src.nationID == myNationID && letterObj.state_opt == 'try_destroy_src') ||
+ (letterObj.dest.nationID == myNationID && letterObj.state_opt == 'try_destroy_dest')) {
+ $btnDestroy.show().prop('disabled', true);
+ }
+ else {
+ $btnDestroy.show().click(async function (e) {
+ e.preventDefault();
+ if (!confirm('본 문서를 파기하겠습니까? (상호 동의 필요)')) {
+ return false;
+ }
+ await destroyLetter(letterObj.no);
+ })
+
+ }
+ }
+
+
+ $letter.find('.btnRenew').click(function () {
+ const $inputPrevNo = $('#inputPrevNo');
+ $inputPrevNo.val(letterObj.no);
+ $inputPrevNo.trigger('change');
+ })
+
+
+ $('#letters').prepend($letter);
+}
+
+function initNewLetterForm(lettersObj: LetterResponse) {
+ console.log(lettersObj);
+ const nationList: (NationStaticItem & { id: number, text: string })[] = [];
+ for (const nation of Object.values(lettersObj.nations)) {
+ nationList.push({
+ ...nation,
+ id: nation.nation,
+ text: nation.name,
+ });
+ }
+
+ const $destNation = $('#inputDestNation').select2({
+ theme: 'bootstrap4',
+ placeholder: "",
+ language: "ko",
+ width: '300px',
+ containerCss: {
+ display: "inline-block !important;",
+ color: 'white !important'
+ },
+ data: nationList,
+ templateResult: colorNation,
+ containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
+ dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
+ });
+
+
+ const prevNoList: {
+ id: number,
+ text: string,
+ nation: null | number,
+ }[] = [{
+ id: 0,
+ text: '-새 문서-',
+ nation: null,
+ }];
+
+ for(const letterObj of Object.values(lettersObj.letters)){
+ if (letterObj.state == 'replaced' || letterObj.state == 'cancelled') {
+ continue;
+ }
+ const targetNation = letterObj.src.nationID == myNationID ? letterObj.dest : letterObj.src;
+ prevNoList.push({
+ id: letterObj.no,
+ text: `#${letterObj.no} <${targetNation.nationName}>`,
+ nation: targetNation.nationID
+ });
+ }
+
+ const $inputPrevNo = $('#inputPrevNo').select2({
+ theme: 'bootstrap4',
+ placeholder: "",
+ language: "ko",
+ width: '300px',
+ containerCss: {
+ display: "inline-block !important;",
+ color: 'white !important'
+ },
+ data: prevNoList,
+ containerCssClass: 'simple-select2-align-center bg-secondary text-secondary',
+ dropdownCssClass: 'simple-select2-align-center bg-secondary text-secondary',
+ });
+ $inputPrevNo.on('change', function () {
+ const data = $inputPrevNo.select2('data')[0] as unknown as typeof prevNoList[0];
+ console.log(data);
+ if (data.nation == null) {
+ $destNation.prop("disabled", false);
+ }
+ else {
+ $destNation.val(data.nation).prop("disabled", true);
+ const $targetLetter = $('#letter_' + data.id);
+ resizeTextarea($('#inputBrief').val($targetLetter.data('brief')));
+ resizeTextarea($('#inputDetail').val($targetLetter.data('detail')));
+ }
+ });
+
+ $('#btnSend').click(submitLetter);
+
+ $('#newLetter').show();
+}
+
+function drawLetters(lettersObj: LetterResponse) {
+ myNationID = lettersObj.myNationID;
+
+ if (permissionLevel == 4) {
+ initNewLetterForm(lettersObj);
+ $('.letterActionPlate').show();
+ }
+
+ $('.letterObj').detach();//첫 버전이니까 일괄 삭제 일괄 로드
+ for (const letter of Object.values(lettersObj.letters)) {
+ drawLetter(letter);
+ }
+}
+
+async function loadLetters(): Promise {
+ const response = await axios({
+ url: 'j_diplomacy_get_letter.php',
+ responseType: 'json',
+ method: 'post',
+ data: convertFormData({
+ })
+ });
+ const result: LetterResponse|InvalidResponse = response.data;
+ if(!result.result){
+ throw result.reason;
+ }
+ return result;
+}
+
+function colorNation(nationInfo: { id: number, text: string, color?: string } | LoadingData) {
+ if (!('color' in nationInfo)) {
+ return nationInfo.text;
+ }
+ if (!nationInfo.color) {
+ return nationInfo.text;
+ }
+
+ const fgColor = isBrightColor(nationInfo.color) ? '#000000' : '#ffffff';
+ const $nationForm = $('' + nationInfo.text + '
').css({
+ 'color': fgColor,
+ 'background-color': nationInfo.color
+ });
+ return $nationForm;
+}
+
+function resizeTextarea($obj: JQuery) {
+ $obj.height(1).height($obj.prop('scrollHeight') + 12);
+}
+
+$(async function () {
+ setAxiosXMLHttpRequest();
+ $('textarea.autosize').on('keydown keyup', function () {
+ resizeTextarea($(this));
+ })
+
+ try {
+ const letters = await loadLetters();
+ drawLetters(letters);
+ }
+ catch (e) {
+ console.error(e);
+ if (isString(e)) {
+ alert(e);
+ }
+ else {
+ alert(`실패했습니다.`);
+ }
+ return;
+ }
+});
\ No newline at end of file
diff --git a/webpack.config.cjs b/webpack.config.cjs
index 83bbb6bf..51436b31 100644
--- a/webpack.config.cjs
+++ b/webpack.config.cjs
@@ -35,6 +35,7 @@ module.exports = (env, argv) => {
extExpandCity: '@/extExpandCity.ts',
main: '@/main.ts',
dipcenter: '@/dipcenter.ts',
+ diplomacy: '@/diplomacy.ts',
//FORM 입력용, frontend 변경후 제거