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);
return [
'result'=>true,
'brief'=>$commandObj->getBrief(),
'reason'=>'success'
];
}
@@ -474,6 +475,7 @@ function setNationCommand(int $generalID, array $turnList, string $command, ?arr
_setNationCommand($commandObj, $turnList);
return [
'result'=>true,
'brief'=>$commandObj->getBrief(),
'arg_test'=>true,
'reason'=>'success'
];
+158 -64
View File
@@ -3,47 +3,12 @@
<div class="col alert alert-dark m-0 p-1 center">
<h4 class="m-0">명령 목록</h4>
</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="col d-grid">
<BButton v-if="!isEditMode" variant="secondary" @click="isEditMode = !isEditMode">고급 모드</BButton>
<BDropdown v-else 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>
<BButton
variant="secondary"
@click="isEditMode = !isEditMode"
>{{ isEditMode ? '일반 모드로' : '고급 모드로' }}</BButton>
</div>
<div
class="col alert alert-primary m-0 p-0"
@@ -65,12 +30,88 @@
</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
class="commandQuickReserveFormAnchor bg-dark"
:style="{
position: 'absolute',
top: `${29.4 * currentQuickReserveTarget + 30}px`,
top: `${basicModeRowHeight * currentQuickReserveTarget + 30}px`,
width: '100%',
zIndex: 9,
}"
@@ -204,7 +245,7 @@ selectTurn(...$event);
</div>
<div class="row gx-1">
<div class="col d-grid">
<BDropdown text="당기기">
<BDropdown :split="isEditMode" text="당기기" @click="pullGeneralCommandSingle">
<BDropdownItem
v-for="turnIdx in maxPushTurn"
:key="turnIdx"
@@ -213,7 +254,7 @@ selectTurn(...$event);
</BDropdown>
</div>
<div class="col d-grid">
<BDropdown text="미루기">
<BDropdown :split="isEditMode" text="미루기" @click="pushGeneralCommandSingle">
<BDropdownItem
v-for="turnIdx in maxPushTurn"
:key="turnIdx"
@@ -254,6 +295,7 @@ declare const staticValues: {
values: CommandItem[];
}[],
serverNow: string,
serverNick: string,
}
</script>
@@ -269,16 +311,12 @@ import { mb_strwidth } from "@util/mb_strwidth";
import { parseTime } from "@util/parseTime";
import { parseYearMonth } from "@util/parseYearMonth";
import DragSelect from "@/components/DragSelect.vue";
import { SammoAPI } from "./SammoAPI";
import type { CommandItem } from "@/defs";
import { SammoAPI, type InvalidResponse } from "./SammoAPI";
import type { CommandItem, ReserveCommandResponse } from "@/defs";
import CommandSelectForm from "@/components/CommandSelectForm.vue";
import { BButton, BButtonGroup, BDropdownItem, BDropdown, BDropdownText, BDropdownDivider } from "bootstrap-vue-3";
type TurnObj = {
action: string;
brief: string;
arg: null | [] | Record<string, number | string | number[] | string[]>;
};
import { StoredActionsHelper } from "./util/StoredActionsHelper";
import type { TurnObj } from '@/defs';
type TurnObjWithTime = TurnObj & {
time: string;
@@ -362,7 +400,7 @@ setTimeout(() => {
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
length: staticValues.maxTurn,
}).fill({
arg: null,
arg: {},
brief: "",
action: "",
year: undefined,
@@ -375,17 +413,20 @@ const editModeKey = `sammo_edit_mode_key`;
const prevTurnList = ref(new Set([0]));
const turnList = ref(new Set<number>());
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 rowGridStyle = ref({
display: "grid",
gridTemplateRows: `repeat(${viewMaxTurn.value}, 29.4px)`,
gridTemplateRows: `repeat(${viewMaxTurn.value}, ${isEditMode.value ? editModeRowHeight : basicModeRowHeight}px)`,
});
watch(viewMaxTurn, (val) => {
rowGridStyle.value.gridTemplateRows = `repeat(${val}, 29.4px)`;
watch([isEditMode, viewMaxTurn], ([isEditMode, maxTurn]) => {
rowGridStyle.value.gridTemplateRows = `repeat(${maxTurn}, ${isEditMode ? editModeRowHeight : basicModeRowHeight}px)`;
});
const isDragSingle = ref(false);
@@ -484,12 +525,15 @@ async function pushGeneralCommand(amount: number) {
await reloadCommandList();
}
/*
function pushGeneralCommandSingle(e: Event) {
//NOTE: split 구현에 버그가 있어서, 수동으로 구분해야함
if (isDropdownChildren(e)) {
return;
}
if (!isEditMode.value) {
return;
}
void pushGeneralCommand(1);
}
@@ -498,9 +542,12 @@ function pullGeneralCommandSingle(e: Event) {
if (isDropdownChildren(e)) {
return;
}
if (!isEditMode.value) {
return;
}
void pushGeneralCommand(-1);
}
*/
async function reloadCommandList() {
let result: ReservedCommandResponse;
@@ -563,6 +610,41 @@ async function reloadCommandList() {
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() {
let reqTurnList: number[];
if (turnList.value.size == 0) {
@@ -589,11 +671,17 @@ async function reserveCommand() {
}
try {
await SammoAPI.Command.ReserveCommand({
const result = await SammoAPI.Command.ReserveCommand<ReserveCommandResponse>({
turnList: reqTurnList,
action: commandName,
});
storedActionsHelper.pushRecentActions({
action: commandName,
brief: result.brief,
arg: {}
});
if (turnList.value.size > 0) {
prevTurnList.value.clear();
for (const v of turnList.value) {
@@ -614,6 +702,7 @@ function chooseCommand(val?: string) {
return;
}
selectedCommand.value = invCommandMap[val];
void reserveCommand();
}
const commandQuickReserveForm = ref<InstanceType<typeof CommandSelectForm> | null>(null);
@@ -637,13 +726,9 @@ function toggleQuickReserveForm(turnIdx: number) {
commandQuickReserveForm.value?.show();
}
onMounted(() => {
void reloadCommandList();
});
watch(isEditMode, newEditMode => {
localStorage.setItem(editModeKey, newEditMode?'1':'0');
localStorage.setItem(editModeKey, newEditMode ? '1' : '0');
if (newEditMode) {
commandQuickReserveForm.value?.close();
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>
<style lang="scss">
@use "sass:color";
+8 -1
View File
@@ -1,3 +1,5 @@
import type { Args } from "./processing/args";
export type InvalidResponse = {
result: false;
reason: string;
@@ -33,6 +35,11 @@ export type GeneralListResponse = {
token: Record<number, number>,
}
export type ReserveCommandResponse = {
result: true,
brief: string,
}
export type NationLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7;
export const NationLevelText: Record<NationLevel, string> = {
0: '방랑군',
@@ -176,7 +183,7 @@ export type OptionalFull<Type> = {
export type TurnObj = {
action: 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 { isString } from 'lodash';
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;
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]}`);
}
console.log('trySubmit', arg);
const response = await targetAPI({
const responseP = targetAPI<T>({
action,
turnList,
arg,
});
const storedActionsHelper = new StoredActionsHelper(staticValues.serverNick, isChiefTurn?'nation':'general');
const response = await responseP;
storedActionsHelper.pushRecentActions({
action,
brief: response.brief,
arg: (arg??{}),
})
if (!isChiefTurn) {
window.location.href = './';
} else {
window.location.href = 'v_chiefCenter.php';
}
return response as T;
return response;
}
catch (e) {
console.error(e);