feat: UserAction 조회, 선택 기능
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Command;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\Enums\APIRecoveryType;
|
||||
use sammo\Json;
|
||||
use sammo\KVStorage;
|
||||
use sammo\TimeUtil;
|
||||
use sammo\DTO\UserAction;
|
||||
|
||||
use function sammo\cutTurn;
|
||||
|
||||
class GetReservedUserAction extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
|
||||
{
|
||||
$db = DB::db();
|
||||
$generalID = $session->generalID;
|
||||
|
||||
$userActionKey = 'user_action';
|
||||
|
||||
$rawUserActions = $db->queryFirstField('SELECT JSON_QUERY(`aux`, %s) FROM general WHERE no=%i', "$.{$userActionKey}", $generalID);
|
||||
if($rawUserActions === null){
|
||||
$rawUserActions = '{}';
|
||||
}
|
||||
|
||||
$userActions = UserAction::fromArray(Json::decode($rawUserActions));
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
'userActions' => $userActions->toArray(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\API\Command;
|
||||
|
||||
use sammo\Session;
|
||||
use DateTimeInterface;
|
||||
use sammo\DB;
|
||||
use sammo\DTO\UserAction;
|
||||
use sammo\DTO\UserActionItem;
|
||||
use sammo\Enums\APIRecoveryType;
|
||||
use sammo\GameConst;
|
||||
use sammo\General;
|
||||
use sammo\Json;
|
||||
use sammo\KVStorage;
|
||||
use sammo\Util;
|
||||
use sammo\Validator;
|
||||
|
||||
use function sammo\buildUserActionCommandClass;
|
||||
use function sammo\setGeneralCommand;
|
||||
|
||||
class ReserveUserAction extends \sammo\BaseAPI
|
||||
{
|
||||
public function validateArgs(): ?string
|
||||
{
|
||||
$v = new Validator($this->args);
|
||||
$v->rule('required', [
|
||||
'turnIdx',
|
||||
'action'
|
||||
])
|
||||
->rule('int', 'turnIdx')
|
||||
->rule('max', 'turnIdx', GameConst::$maxTurn)
|
||||
->rule('min', 'turnIdx', 0)
|
||||
->rule('lengthMin', 'action', 1);
|
||||
if (!$v->validate()) {
|
||||
return $v->errorStr();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getRequiredSessionMode(): int
|
||||
{
|
||||
return static::REQ_GAME_LOGIN | static::REQ_READ_ONLY;
|
||||
}
|
||||
|
||||
public function launch(Session $session, ?DateTimeInterface $modifiedSince, ?string $reqEtag): null | string | array | APIRecoveryType
|
||||
{
|
||||
$turnIdx = $this->args['turnIdx'];
|
||||
$action = $this->args['action'];
|
||||
|
||||
$userActionKey = 'user_action';
|
||||
|
||||
if($turnIdx < 0 || $turnIdx >= GameConst::$maxTurn){
|
||||
return '올바르지 않은 턴입니다.';
|
||||
}
|
||||
|
||||
if(!in_array($action, Util::array_flatten(GameConst::$availableUserActionCommand))){
|
||||
return '사용할 수 없는 커맨드입니다.';
|
||||
}
|
||||
|
||||
$db = DB::db();
|
||||
$generalID = $session->generalID;
|
||||
$rawUserActions = $db->queryFirstField('SELECT JSON_QUERY(`aux`, %s) FROM general WHERE no=%i', "$.{$userActionKey}", $generalID);
|
||||
if(!$rawUserActions){
|
||||
$rawUserActions = '{}';
|
||||
}
|
||||
|
||||
$tmp = Json::decode($rawUserActions);
|
||||
$userActions = UserAction::fromArray($tmp);
|
||||
|
||||
|
||||
$gameStor = KVStorage::getStorage($db, 'game_env');
|
||||
$gameStor->cacheAll();
|
||||
$general = General::createObjFromDB($generalID);
|
||||
|
||||
$item = buildUserActionCommandClass($action, $general, $gameStor->getAll());
|
||||
$userActions->reserved[$turnIdx] = new UserActionItem(
|
||||
$item->getRawClassName(),
|
||||
$item->getCommandDetailTitle(),
|
||||
null,
|
||||
);
|
||||
|
||||
$db->update('general', [
|
||||
'aux' => $db->sqleval('JSON_SET(`aux`, %s, JSON_EXTRACT(%s, "$"))', "$.{$userActionKey}", Json::encode($userActions->toArray())),
|
||||
], 'no=%i', $generalID);
|
||||
|
||||
return [
|
||||
'result' => true,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
use LDTO\Attr\Convert;
|
||||
use LDTO\Converter\ArrayConverter;
|
||||
use LDTO\Converter\MapConverter;
|
||||
use LDTO\DTO;
|
||||
|
||||
|
||||
class UserAction extends \LDTO\DTO
|
||||
{
|
||||
/**
|
||||
* @param UserActionItem[] $active
|
||||
* @param Array<int,UserActionItem> $reserved
|
||||
* */
|
||||
public function __construct(
|
||||
|
||||
#[Convert(MapConverter::class, [UserActionItem::class])]
|
||||
public ?array $reserved,
|
||||
#[Convert(ArrayConverter::class, [UserActionItem::class])]
|
||||
public ?array $active,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<?php
|
||||
|
||||
namespace sammo\DTO;
|
||||
|
||||
class UserActionItem extends \LDTO\DTO {
|
||||
public function __construct(
|
||||
public string $command,
|
||||
public string $brief,
|
||||
#[\LDTO\Attr\NullIsUndefined]
|
||||
public ?int $untilYearMonth,
|
||||
) {
|
||||
}
|
||||
}
|
||||
@@ -42,10 +42,22 @@ async function refresh() {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "@scss/common/break_500px.scss";
|
||||
|
||||
//grid
|
||||
#pages {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
@include media-1000px {
|
||||
#pages {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
@include media-500px {
|
||||
#pages {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-gap: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,15 +17,15 @@
|
||||
<div :class="{
|
||||
commandTable: true,
|
||||
}">
|
||||
<DragSelect :style="rowGridStyle" :disabled="true" attribute="turnIdx">
|
||||
<div :style="rowGridStyle">
|
||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx" :turnIdx="turnIdx"
|
||||
class="idx_pad center d-grid">
|
||||
<div class="plain-center">
|
||||
{{ turnIdx + 1 }}
|
||||
</div>
|
||||
</div>
|
||||
</DragSelect>
|
||||
<DragSelect :style="rowGridStyle" attribute="turnIdx" :disabled="true">
|
||||
</div>
|
||||
<div :style="rowGridStyle">
|
||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx" height="24"
|
||||
class="month_pad center" :turnIdx="turnIdx" :style="{
|
||||
'white-space': 'nowrap',
|
||||
@@ -35,7 +35,7 @@
|
||||
{{ turnObj.year ? `${turnObj.year}年` : "" }}
|
||||
{{ turnObj.month ? `${turnObj.month}月` : "" }}
|
||||
</div>
|
||||
</DragSelect>
|
||||
</div>
|
||||
<div :style="rowGridStyle">
|
||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx"
|
||||
class="time_pad center" :style="{
|
||||
@@ -56,7 +56,19 @@
|
||||
</div>
|
||||
</div>
|
||||
<div :style="rowGridStyle">
|
||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx"
|
||||
<div v-for="(turnObj, turnIdx) in reservedUserActionList.slice(0, viewMaxTurn)" :key="turnIdx"
|
||||
class="turn_pad center">
|
||||
<span v-if="turnObj" class="turn_text">
|
||||
<!-- eslint-disable-next-line vue/no-v-html -->
|
||||
<span v-html="turnObj.brief" />
|
||||
</span>
|
||||
<span v-else class="turn_text">
|
||||
<span>휴식</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div :style="rowGridStyle">
|
||||
<div v-for="(turnObj, turnIdx) in reservedUserActionList.slice(0, viewMaxTurn)" :key="turnIdx"
|
||||
class="action_pad d-grid">
|
||||
<BButton :variant="turnIdx % 2 == 0 ? 'secondary' : 'dark'" size="sm" class="simple_action_btn bi bi-pencil"
|
||||
:disabled="!commandList.length" @click="toggleQuickReserveForm(turnIdx)" />
|
||||
@@ -100,7 +112,6 @@ import { joinYearMonth } from "@util/joinYearMonth";
|
||||
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, CommandTableResponse } from "@/defs";
|
||||
import CommandSelectForm from "@/components/CommandSelectForm.vue";
|
||||
@@ -110,7 +121,7 @@ import type { TurnObj } from "@/defs";
|
||||
import type { Args } from "./processing/args";
|
||||
import { getEmptyTurn } from "./util/QueryActionHelper";
|
||||
import SimpleClock from "./components/SimpleClock.vue";
|
||||
import type { ReservedCommandResponse } from "./defs/API/Command";
|
||||
import type { ReservedCommandResponse, ReservedUserActionResponse, UserActionItem } from "./defs/API/Command";
|
||||
import { unwrap } from "./util/unwrap";
|
||||
|
||||
defineExpose({
|
||||
@@ -135,7 +146,6 @@ const nullCommand: CommandItem = {
|
||||
}
|
||||
|
||||
const selectedCommand = ref<CommandItem>(nullCommand);
|
||||
const commandSelectForm = ref<InstanceType<typeof CommandSelectForm> | null>(null);
|
||||
const invCommandMap = new Map<string, CommandItem>();
|
||||
|
||||
async function updateCommandTable() {
|
||||
@@ -183,7 +193,7 @@ onMounted(() => {
|
||||
});
|
||||
|
||||
const reservedCommandList = ref(getEmptyTurn(maxTurn));
|
||||
|
||||
const reservedUserActionList = ref<(UserActionItem | undefined)[]>([]);
|
||||
const flippedMaxTurn = 14;
|
||||
|
||||
const basicModeRowHeight = 34.4;
|
||||
@@ -209,8 +219,13 @@ const serverNow = ref(new Date());
|
||||
|
||||
async function reloadCommandList() {
|
||||
let result: ReservedCommandResponse;
|
||||
let userActions: ReservedUserActionResponse;
|
||||
try {
|
||||
result = await SammoAPI.Command.GetReservedCommand();
|
||||
[result, userActions] = await Promise.all([
|
||||
SammoAPI.Command.GetReservedCommand(),
|
||||
SammoAPI.Command.GetReservedUserAction(),
|
||||
]);
|
||||
|
||||
} catch (e) {
|
||||
if (isString(e)) {
|
||||
toasts.danger({
|
||||
@@ -265,13 +280,20 @@ async function reloadCommandList() {
|
||||
}
|
||||
|
||||
serverNow.value = parseTime(result.date);
|
||||
|
||||
const newReservedUserActionList: (UserActionItem | undefined)[] = [];
|
||||
newReservedUserActionList.length = maxTurn;
|
||||
for (const [idx, item] of Object.entries(userActions.userActions.reserved ?? {})) {
|
||||
newReservedUserActionList[parseInt(idx)] = item;
|
||||
}
|
||||
reservedUserActionList.value = newReservedUserActionList;
|
||||
}
|
||||
|
||||
async function reserveCommand() {
|
||||
const turnIdx = currentQuickReserveTarget.value;
|
||||
|
||||
const commandName = selectedCommand.value.value;
|
||||
if(turnIdx < 0) {
|
||||
if (turnIdx < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -333,14 +355,7 @@ onMounted(() => {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns:
|
||||
minmax(20px, 0.8fr) minmax(75px, 2.4fr) minmax(40px, 0.9fr) 4.8fr minmax(28px, 0.8fr);
|
||||
//30, 70, 37.65, 160
|
||||
}
|
||||
|
||||
.commandTable.isEditMode {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(30px, 1fr) minmax(75px, 2.5fr) minmax(40px, 1fr) 5fr;
|
||||
minmax(20px, 0.6fr) minmax(75px, 2.0fr) minmax(40px, 0.9fr) 4.0fr 3.2fr minmax(28px, 0.8fr);
|
||||
//30, 70, 37.65, 160
|
||||
}
|
||||
|
||||
@@ -388,11 +403,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
|
||||
.isEditMode .month_pad:hover {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plain-center {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
+2
-1
@@ -15,7 +15,7 @@ import {
|
||||
export type { ValidResponse, InvalidResponse };
|
||||
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
|
||||
import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Betting";
|
||||
import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse } from "./defs/API/Command";
|
||||
import type { ReserveBulkCommandResponse, ReserveCommandResponse, ReservedCommandResponse, ReservedUserActionResponse } from "./defs/API/Command";
|
||||
import type { ChiefResponse } from "./defs/API/NationCommand";
|
||||
import type { inheritBuffType, InheritLogResponse } from "./defs/API/InheritAction";
|
||||
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse, NationInfoResponse } from "./defs/API/Nation";
|
||||
@@ -109,6 +109,7 @@ const apiRealPath = {
|
||||
},
|
||||
ReserveCommandResponse
|
||||
>,
|
||||
GetReservedUserAction: GET as APICallT<undefined, ReservedUserActionResponse>,
|
||||
ReserveBulkCommand: PUT as APICallT<
|
||||
{
|
||||
turnList: number[];
|
||||
|
||||
@@ -20,3 +20,19 @@ export type ReserveBulkCommandResponse = {
|
||||
result: true,
|
||||
briefList: string[],
|
||||
}
|
||||
|
||||
export type UserActionItem = {
|
||||
command: string,
|
||||
brief: string,
|
||||
untilYearMonth?: number,
|
||||
};
|
||||
|
||||
type UserAction = {
|
||||
reserved?: Record<number, UserActionItem>,
|
||||
active?: UserActionItem[],
|
||||
}
|
||||
|
||||
export type ReservedUserActionResponse = {
|
||||
result: true,
|
||||
userActions: UserAction,
|
||||
}
|
||||
+11
-2
@@ -39,9 +39,18 @@ if ($limitState >= 2) {
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=500" />
|
||||
<title><?= UniqueConst::$serverName ?>: 개인 전략</title>
|
||||
<?= WebUtil::printStaticValues(['staticValues' => [
|
||||
<?= WebUtil::printStaticValues([
|
||||
'staticValues' => [
|
||||
'serverName' => UniqueConst::$serverName,
|
||||
'serverNick' => DB::prefix(),
|
||||
'serverID' => UniqueConst::$serverID,
|
||||
'mapName' => GameConst::$mapName,
|
||||
'unitSet' => GameConst::$unitSet,
|
||||
|
||||
]]) ?>
|
||||
'maxTurn' => GameConst::$maxTurn,
|
||||
'serverNow' => TimeUtil::now(false),
|
||||
]
|
||||
], false) ?>
|
||||
<?= WebUtil::printJS('../d_shared/common_path.js', true) ?>
|
||||
<?= WebUtil::printDist('vue', ['v_userAction'], true) ?>
|
||||
</head>
|
||||
|
||||
Reference in New Issue
Block a user