feat: UserAction 조회, 선택 기능

This commit is contained in:
2023-12-25 12:20:01 +00:00
parent eb36e7ccf4
commit 0f621b22a5
9 changed files with 254 additions and 31 deletions
@@ -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,
];
}
}
+25
View File
@@ -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,
) {
}
}
+13
View File
@@ -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,
) {
}
}
+16 -4
View File
@@ -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>
+34 -24
View File
@@ -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
View File
@@ -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[];
+16
View File
@@ -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
View File
@@ -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>