feat(WIP): 턴 선택기 고급 확장
- 최근 예약 턴 저장
This commit is contained in:
@@ -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'
|
||||
];
|
||||
|
||||
@@ -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> 잘라내기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-files"></i> 복사하기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-clipboard-fill"></i> 붙여넣기
|
||||
</BDropdownItem>
|
||||
<BDropdownDivider />
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-bookmark-plus-fill"></i> 보관하기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-arrow-repeat"></i> 반복하기
|
||||
</BDropdownItem>
|
||||
<BDropdownDivider />
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-eraser"></i> 비우기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-arrow-bar-up"></i> 지우고 당기기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-arrow-bar-down"></i> 뒤로 밀기
|
||||
</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
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user