feat(WIP): 턴 선택기 고급 확장

- 최근 예약 턴 저장
This commit is contained in:
2022-03-23 20:12:59 +09:00
parent 30b78a1938
commit bcf52e0460
5 changed files with 243 additions and 70 deletions
+2
View File
@@ -393,6 +393,7 @@ function setGeneralCommand(int $generalID, array $rawTurnList, string $command,
_setGeneralCommand($commandObj, $turnList); _setGeneralCommand($commandObj, $turnList);
return [ return [
'result'=>true, 'result'=>true,
'brief'=>$commandObj->getBrief(),
'reason'=>'success' 'reason'=>'success'
]; ];
} }
@@ -474,6 +475,7 @@ function setNationCommand(int $generalID, array $turnList, string $command, ?arr
_setNationCommand($commandObj, $turnList); _setNationCommand($commandObj, $turnList);
return [ return [
'result'=>true, 'result'=>true,
'brief'=>$commandObj->getBrief(),
'arg_test'=>true, 'arg_test'=>true,
'reason'=>'success' 'reason'=>'success'
]; ];
+158 -64
View File
@@ -3,47 +3,12 @@
<div class="col alert alert-dark m-0 p-1 center"> <div class="col alert alert-dark m-0 p-1 center">
<h4 class="m-0">명령 목록</h4> <h4 class="m-0">명령 목록</h4>
</div> </div>
<div class="commandSelectFormAnchor">
<div v-if="isEditMode" class="row gx-0">
<div class="col-3 d-grid">
<BButton variant="warning" @click="isEditMode = !isEditMode">취소</BButton>
</div>
<div class="col-6 d-grid">
<BButton
variant="light"
@click="toggleForm($event)"
:style="{ color: 'black' }"
>{{ selectedCommand.simpleName }} </BButton>
</div>
<div class="col-3 d-grid">
<BButton @click="reserveCommand()" variant="primary">실행</BButton>
</div>
</div>
</div>
<div class="row gx-1"> <div class="row gx-1">
<div class="col d-grid"> <div class="col d-grid">
<BButton v-if="!isEditMode" variant="secondary" @click="isEditMode = !isEditMode">고급 모드</BButton> <BButton
<BDropdown v-else left text="턴 선택"> variant="secondary"
<BDropdownItem @click="selectTurn()">해제</BDropdownItem> @click="isEditMode = !isEditMode"
<BDropdownItem @click="selectAll(true)">모든턴</BDropdownItem> >{{ isEditMode ? '일반 모드로' : '고급 모드로' }}</BButton>
<BDropdownItem @click="selectStep(0, 2)">홀수턴</BDropdownItem>
<BDropdownItem @click="selectStep(1, 2)">짝수턴</BDropdownItem>
<BDropdownDivider></BDropdownDivider>
<BDropdownText v-for="spanIdx in [3, 4, 5, 6, 7]" :key="spanIdx">
{{ spanIdx }} 간격
<br />
<BButtonGroup>
<BButton
class="ignoreMe"
v-for="beginIdx in spanIdx"
:key="beginIdx"
@click="selectStep(beginIdx - 1, spanIdx)"
>{{ beginIdx }}</BButton>
</BButtonGroup>
</BDropdownText>
</BDropdown>
</div> </div>
<div <div
class="col alert alert-primary m-0 p-0" class="col alert alert-primary m-0 p-0"
@@ -65,12 +30,88 @@
</div> </div>
</div> </div>
<div class="commandSelectFormAnchor">
<div v-if="isEditMode" class="row gx-0">
<div class="col-3 d-grid">
<BDropdown left text="범위">
<BDropdownItem @click="selectTurn()">해제</BDropdownItem>
<BDropdownItem @click="selectAll(true)">모든턴</BDropdownItem>
<BDropdownItem @click="selectStep(0, 2)">홀수턴</BDropdownItem>
<BDropdownItem @click="selectStep(1, 2)">짝수턴</BDropdownItem>
<BDropdownDivider></BDropdownDivider>
<BDropdownText v-for="spanIdx in [3, 4, 5, 6, 7]" :key="spanIdx">
{{ spanIdx }} 간격
<br />
<BButtonGroup>
<BButton
class="ignoreMe"
v-for="beginIdx in spanIdx"
:key="beginIdx"
@click="selectStep(beginIdx - 1, spanIdx)"
>{{ beginIdx }}</BButton>
</BButtonGroup>
</BDropdownText>
</BDropdown>
</div>
<div class="col-4 d-grid">
<BDropdown left text="보관함">
<!-- 즐겨찾기 모드 -->
</BDropdown>
</div>
<div class="col-5 d-grid">
<BDropdown right variant="info" text="선택한 턴을">
<BDropdownItem>
<i class="bi bi-scissors"></i>&nbsp;잘라내기
</BDropdownItem>
<BDropdownItem>
<i class="bi bi-files"></i>&nbsp;복사하기
</BDropdownItem>
<BDropdownItem>
<i class="bi bi-clipboard-fill"></i>&nbsp;붙여넣기
</BDropdownItem>
<BDropdownDivider />
<BDropdownItem>
<i class="bi bi-bookmark-plus-fill"></i>&nbsp;보관하기
</BDropdownItem>
<BDropdownItem>
<i class="bi bi-arrow-repeat"></i>&nbsp;반복하기
</BDropdownItem>
<BDropdownDivider />
<BDropdownItem>
<i class="bi bi-eraser"></i>&nbsp;비우기
</BDropdownItem>
<BDropdownItem>
<i class="bi bi-arrow-bar-up"></i>&nbsp;지우고 당기기
</BDropdownItem>
<BDropdownItem>
<i class="bi bi-arrow-bar-down"></i>&nbsp;뒤로 밀기
</BDropdownItem>
<!-- 최근에 실행한 10 -->
</BDropdown>
</div>
<div class="col-4 d-grid">
<BDropdown left text="최근 실행">
<BDropdownItem
v-for="(action, idx) in recentActions"
:key="idx"
@click="void reserveCommandDirect([[Array.from(turnList.values()), action]])"
>{{ action.brief }}</BDropdownItem>
</BDropdown>
</div>
<div class="col-8 d-grid">
<BButton variant="light" @click="toggleForm($event)" :style="{ color: 'black' }">명령 선택 </BButton>
</div>
</div>
</div>
<div :style="{ position: 'relative' }"> <div :style="{ position: 'relative' }">
<div <div
class="commandQuickReserveFormAnchor bg-dark" class="commandQuickReserveFormAnchor bg-dark"
:style="{ :style="{
position: 'absolute', position: 'absolute',
top: `${29.4 * currentQuickReserveTarget + 30}px`, top: `${basicModeRowHeight * currentQuickReserveTarget + 30}px`,
width: '100%', width: '100%',
zIndex: 9, zIndex: 9,
}" }"
@@ -204,7 +245,7 @@ selectTurn(...$event);
</div> </div>
<div class="row gx-1"> <div class="row gx-1">
<div class="col d-grid"> <div class="col d-grid">
<BDropdown text="당기기"> <BDropdown :split="isEditMode" text="당기기" @click="pullGeneralCommandSingle">
<BDropdownItem <BDropdownItem
v-for="turnIdx in maxPushTurn" v-for="turnIdx in maxPushTurn"
:key="turnIdx" :key="turnIdx"
@@ -213,7 +254,7 @@ selectTurn(...$event);
</BDropdown> </BDropdown>
</div> </div>
<div class="col d-grid"> <div class="col d-grid">
<BDropdown text="미루기"> <BDropdown :split="isEditMode" text="미루기" @click="pushGeneralCommandSingle">
<BDropdownItem <BDropdownItem
v-for="turnIdx in maxPushTurn" v-for="turnIdx in maxPushTurn"
:key="turnIdx" :key="turnIdx"
@@ -254,6 +295,7 @@ declare const staticValues: {
values: CommandItem[]; values: CommandItem[];
}[], }[],
serverNow: string, serverNow: string,
serverNick: string,
} }
</script> </script>
@@ -269,16 +311,12 @@ import { mb_strwidth } from "@util/mb_strwidth";
import { parseTime } from "@util/parseTime"; import { parseTime } from "@util/parseTime";
import { parseYearMonth } from "@util/parseYearMonth"; import { parseYearMonth } from "@util/parseYearMonth";
import DragSelect from "@/components/DragSelect.vue"; import DragSelect from "@/components/DragSelect.vue";
import { SammoAPI } from "./SammoAPI"; import { SammoAPI, type InvalidResponse } from "./SammoAPI";
import type { CommandItem } from "@/defs"; import type { CommandItem, ReserveCommandResponse } from "@/defs";
import CommandSelectForm from "@/components/CommandSelectForm.vue"; import CommandSelectForm from "@/components/CommandSelectForm.vue";
import { BButton, BButtonGroup, BDropdownItem, BDropdown, BDropdownText, BDropdownDivider } from "bootstrap-vue-3"; import { BButton, BButtonGroup, BDropdownItem, BDropdown, BDropdownText, BDropdownDivider } from "bootstrap-vue-3";
import { StoredActionsHelper } from "./util/StoredActionsHelper";
type TurnObj = { import type { TurnObj } from '@/defs';
action: string;
brief: string;
arg: null | [] | Record<string, number | string | number[] | string[]>;
};
type TurnObjWithTime = TurnObj & { type TurnObjWithTime = TurnObj & {
time: string; time: string;
@@ -362,7 +400,7 @@ setTimeout(() => {
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({ const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
length: staticValues.maxTurn, length: staticValues.maxTurn,
}).fill({ }).fill({
arg: null, arg: {},
brief: "", brief: "",
action: "", action: "",
year: undefined, year: undefined,
@@ -375,17 +413,20 @@ const editModeKey = `sammo_edit_mode_key`;
const prevTurnList = ref(new Set([0])); const prevTurnList = ref(new Set([0]));
const turnList = ref(new Set<number>()); const turnList = ref(new Set<number>());
const reservedCommandList = ref(emptyTurn); const reservedCommandList = ref(emptyTurn);
const isEditMode = ref(localStorage.getItem(editModeKey)==='1'); const isEditMode = ref(localStorage.getItem(editModeKey) === '1');
const flippedMaxTurn = 15; const flippedMaxTurn = 14;
const editModeRowHeight = 29.35;
const basicModeRowHeight = 34.4;
const viewMaxTurn = ref(flippedMaxTurn); const viewMaxTurn = ref(flippedMaxTurn);
const rowGridStyle = ref({ const rowGridStyle = ref({
display: "grid", display: "grid",
gridTemplateRows: `repeat(${viewMaxTurn.value}, 29.4px)`, gridTemplateRows: `repeat(${viewMaxTurn.value}, ${isEditMode.value ? editModeRowHeight : basicModeRowHeight}px)`,
}); });
watch(viewMaxTurn, (val) => { watch([isEditMode, viewMaxTurn], ([isEditMode, maxTurn]) => {
rowGridStyle.value.gridTemplateRows = `repeat(${val}, 29.4px)`; rowGridStyle.value.gridTemplateRows = `repeat(${maxTurn}, ${isEditMode ? editModeRowHeight : basicModeRowHeight}px)`;
}); });
const isDragSingle = ref(false); const isDragSingle = ref(false);
@@ -484,12 +525,15 @@ async function pushGeneralCommand(amount: number) {
await reloadCommandList(); await reloadCommandList();
} }
/*
function pushGeneralCommandSingle(e: Event) { function pushGeneralCommandSingle(e: Event) {
//NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함 //NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함
if (isDropdownChildren(e)) { if (isDropdownChildren(e)) {
return; return;
} }
if (!isEditMode.value) {
return;
}
void pushGeneralCommand(1); void pushGeneralCommand(1);
} }
@@ -498,9 +542,12 @@ function pullGeneralCommandSingle(e: Event) {
if (isDropdownChildren(e)) { if (isDropdownChildren(e)) {
return; return;
} }
if (!isEditMode.value) {
return;
}
void pushGeneralCommand(-1); void pushGeneralCommand(-1);
} }
*/
async function reloadCommandList() { async function reloadCommandList() {
let result: ReservedCommandResponse; let result: ReservedCommandResponse;
@@ -563,6 +610,41 @@ async function reloadCommandList() {
timeDiff.value = serverNow.value.getTime() - clientNow.value.getTime(); timeDiff.value = serverNow.value.getTime() - clientNow.value.getTime();
} }
async function reserveCommandDirect(args: [number[], TurnObj][], reload = true): Promise<boolean> {
const waiterList: Promise<ReserveCommandResponse | InvalidResponse>[] = [];
for (const [turnList, { action, arg }] of args) {
waiterList.push(
SammoAPI.Command.ReserveCommand<ReserveCommandResponse, InvalidResponse>({
turnList,
action,
arg,
}, true)
);
}
let success = true;
for (const [idx, waiter] of waiterList.entries()) {
try {
const response = await waiter;
if (!response.result) {
const message = `${args[idx][0].join(',')}을 예약하지 못했습니다: ${response.reason}`
console.error(message, args[idx][1]);
alert(message);
success = false;
}
}
catch (e) {
const message = `${args[idx][0].join(',')}을 예약하지 못했습니다: ${e}`
console.error(message, args[idx][1]);
success = false;
}
}
if (reload) {
await reloadCommandList();
}
return success;
}
async function reserveCommand() { async function reserveCommand() {
let reqTurnList: number[]; let reqTurnList: number[];
if (turnList.value.size == 0) { if (turnList.value.size == 0) {
@@ -589,11 +671,17 @@ async function reserveCommand() {
} }
try { try {
await SammoAPI.Command.ReserveCommand({ const result = await SammoAPI.Command.ReserveCommand<ReserveCommandResponse>({
turnList: reqTurnList, turnList: reqTurnList,
action: commandName, action: commandName,
}); });
storedActionsHelper.pushRecentActions({
action: commandName,
brief: result.brief,
arg: {}
});
if (turnList.value.size > 0) { if (turnList.value.size > 0) {
prevTurnList.value.clear(); prevTurnList.value.clear();
for (const v of turnList.value) { for (const v of turnList.value) {
@@ -614,6 +702,7 @@ function chooseCommand(val?: string) {
return; return;
} }
selectedCommand.value = invCommandMap[val]; selectedCommand.value = invCommandMap[val];
void reserveCommand();
} }
const commandQuickReserveForm = ref<InstanceType<typeof CommandSelectForm> | null>(null); const commandQuickReserveForm = ref<InstanceType<typeof CommandSelectForm> | null>(null);
@@ -637,13 +726,9 @@ function toggleQuickReserveForm(turnIdx: number) {
commandQuickReserveForm.value?.show(); commandQuickReserveForm.value?.show();
} }
onMounted(() => {
void reloadCommandList();
});
watch(isEditMode, newEditMode => { watch(isEditMode, newEditMode => {
localStorage.setItem(editModeKey, newEditMode?'1':'0'); localStorage.setItem(editModeKey, newEditMode ? '1' : '0');
if (newEditMode) { if (newEditMode) {
commandQuickReserveForm.value?.close(); commandQuickReserveForm.value?.close();
currentQuickReserveTarget.value = -1; currentQuickReserveTarget.value = -1;
@@ -653,6 +738,15 @@ watch(isEditMode, newEditMode => {
} }
}); });
const storedActionsHelper = new StoredActionsHelper(staticValues.serverNick, 'general');
const recentActions = storedActionsHelper.recentActions;
const storedActions = storedActionsHelper.storedActions;
onMounted(() => {
void reloadCommandList();
});
</script> </script>
<style lang="scss"> <style lang="scss">
@use "sass:color"; @use "sass:color";
+8 -1
View File
@@ -1,3 +1,5 @@
import type { Args } from "./processing/args";
export type InvalidResponse = { export type InvalidResponse = {
result: false; result: false;
reason: string; reason: string;
@@ -33,6 +35,11 @@ export type GeneralListResponse = {
token: Record<number, number>, token: Record<number, number>,
} }
export type ReserveCommandResponse = {
result: true,
brief: string,
}
export type NationLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7; export type NationLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
export const NationLevelText: Record<NationLevel, string> = { export const NationLevelText: Record<NationLevel, string> = {
0: '방랑군', 0: '방랑군',
@@ -176,7 +183,7 @@ export type OptionalFull<Type> = {
export type TurnObj = { export type TurnObj = {
action: string; action: string;
brief: string; brief: string;
arg: null | [] | Record<string, number | string | number[] | string[]>; arg: Args;
}; };
+54
View File
@@ -0,0 +1,54 @@
import type { TurnObj } from '@/defs';
import { ref } from 'vue';
export class StoredActionsHelper {
public readonly recentActions = ref<TurnObj[]>([]);
public readonly storedActions = ref(new Map<string, Record<number, TurnObj>>());
public readonly recentActionsKey: string;
public readonly storedActionsKey: string;
constructor(protected serverNick: string, protected type: 'general'|'nation', protected maxRecent = 10) {
this.recentActionsKey = `${serverNick}_${type}RecentActions`;
this.storedActionsKey = `${serverNick}_${type}StoredActions`;
this.loadRecentActions();
this.loadStoredActions();
}
loadRecentActions() {
this.recentActions.value = JSON.parse(localStorage.getItem(this.recentActionsKey) ?? '[]');
}
pushRecentActions(action: TurnObj) {
this.recentActions.value.unshift(action);
if(this.recentActions.value.length > this.maxRecent){
this.recentActions.value.pop();
}
this.saveRecentActions();
}
saveRecentActions() {
localStorage.setItem(this.recentActionsKey, JSON.stringify(this.recentActions.value));
}
loadStoredActions() {
const rawValue: [string, Record<number, TurnObj>][] = JSON.parse(localStorage.getItem(this.storedActionsKey) ?? '[]');
this.storedActions.value = new Map(rawValue);
}
setStoredActions(actionKey: string, actions: Record<number, TurnObj>) {
this.storedActions.value.set(actionKey, actions);
this.saveStoredActions();
}
deleteStoredActions(actionKey: string) {
if (this.storedActions.value.delete(actionKey)) {
this.saveStoredActions();
}
}
saveStoredActions() {
localStorage.setItem(this.storedActionsKey, JSON.stringify(Object.entries(this.storedActions.value)));
}
}
+21 -5
View File
@@ -9,11 +9,18 @@ import { type App, createApp } from 'vue';
import { auto500px } from './util/auto500px'; import { auto500px } from './util/auto500px';
import { isString } from 'lodash'; import { isString } from 'lodash';
import { type Args, testSubmitArgs } from './processing/args'; import { type Args, testSubmitArgs } from './processing/args';
import { SammoAPI, type ValidResponse } from './SammoAPI'; import { SammoAPI } from './SammoAPI';
import { StoredActionsHelper } from './util/StoredActionsHelper';
import type { ReserveCommandResponse } from './defs';
declare const turnList: number[]; declare const staticValues: {
serverNick: string,
turnList: number[],
};
async function submitCommand<T extends ValidResponse>(isChiefTurn: boolean, turnList: number[], action: string, arg: Args): Promise<T> { const { turnList } = staticValues;
async function submitCommand<T extends ReserveCommandResponse>(isChiefTurn: boolean, turnList: number[], action: string, arg: Args): Promise<T> {
const targetAPI = isChiefTurn ? SammoAPI.NationCommand.ReserveCommand : SammoAPI.Command.ReserveCommand; const targetAPI = isChiefTurn ? SammoAPI.NationCommand.ReserveCommand : SammoAPI.Command.ReserveCommand;
try { try {
@@ -22,19 +29,28 @@ async function submitCommand<T extends ValidResponse>(isChiefTurn: boolean, turn
throw new TypeError(`Invalied Type ${testResult[0]}, ${testResult[2]} should be ${testResult[1]}`); throw new TypeError(`Invalied Type ${testResult[0]}, ${testResult[2]} should be ${testResult[1]}`);
} }
console.log('trySubmit', arg); console.log('trySubmit', arg);
const response = await targetAPI({ const responseP = targetAPI<T>({
action, action,
turnList, turnList,
arg, arg,
}); });
const storedActionsHelper = new StoredActionsHelper(staticValues.serverNick, isChiefTurn?'nation':'general');
const response = await responseP;
storedActionsHelper.pushRecentActions({
action,
brief: response.brief,
arg: (arg??{}),
})
if (!isChiefTurn) { if (!isChiefTurn) {
window.location.href = './'; window.location.href = './';
} else { } else {
window.location.href = 'v_chiefCenter.php'; window.location.href = 'v_chiefCenter.php';
} }
return response as T; return response;
} }
catch (e) { catch (e) {
console.error(e); console.error(e);