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>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import "@scss/common/break_500px.scss";
|
||||||
|
|
||||||
//grid
|
//grid
|
||||||
#pages {
|
@include media-1000px {
|
||||||
display: grid;
|
#pages {
|
||||||
grid-template-columns: 1fr 1fr;
|
display: grid;
|
||||||
grid-gap: 10px;
|
grid-template-columns: 1fr 1fr;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include media-500px {
|
||||||
|
#pages {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
grid-gap: 10px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -17,15 +17,15 @@
|
|||||||
<div :class="{
|
<div :class="{
|
||||||
commandTable: true,
|
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"
|
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx" :turnIdx="turnIdx"
|
||||||
class="idx_pad center d-grid">
|
class="idx_pad center d-grid">
|
||||||
<div class="plain-center">
|
<div class="plain-center">
|
||||||
{{ turnIdx + 1 }}
|
{{ turnIdx + 1 }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DragSelect>
|
</div>
|
||||||
<DragSelect :style="rowGridStyle" attribute="turnIdx" :disabled="true">
|
<div :style="rowGridStyle">
|
||||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx" height="24"
|
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx" height="24"
|
||||||
class="month_pad center" :turnIdx="turnIdx" :style="{
|
class="month_pad center" :turnIdx="turnIdx" :style="{
|
||||||
'white-space': 'nowrap',
|
'white-space': 'nowrap',
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
{{ turnObj.year ? `${turnObj.year}年` : "" }}
|
{{ turnObj.year ? `${turnObj.year}年` : "" }}
|
||||||
{{ turnObj.month ? `${turnObj.month}月` : "" }}
|
{{ turnObj.month ? `${turnObj.month}月` : "" }}
|
||||||
</div>
|
</div>
|
||||||
</DragSelect>
|
</div>
|
||||||
<div :style="rowGridStyle">
|
<div :style="rowGridStyle">
|
||||||
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx"
|
<div v-for="(turnObj, turnIdx) in reservedCommandList.slice(0, viewMaxTurn)" :key="turnIdx"
|
||||||
class="time_pad center" :style="{
|
class="time_pad center" :style="{
|
||||||
@@ -56,7 +56,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div :style="rowGridStyle">
|
<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">
|
class="action_pad d-grid">
|
||||||
<BButton :variant="turnIdx % 2 == 0 ? 'secondary' : 'dark'" size="sm" class="simple_action_btn bi bi-pencil"
|
<BButton :variant="turnIdx % 2 == 0 ? 'secondary' : 'dark'" size="sm" class="simple_action_btn bi bi-pencil"
|
||||||
:disabled="!commandList.length" @click="toggleQuickReserveForm(turnIdx)" />
|
:disabled="!commandList.length" @click="toggleQuickReserveForm(turnIdx)" />
|
||||||
@@ -100,7 +112,6 @@ import { joinYearMonth } from "@util/joinYearMonth";
|
|||||||
import { mb_strwidth } from "@util/mb_strwidth";
|
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 { SammoAPI } from "./SammoAPI";
|
import { SammoAPI } from "./SammoAPI";
|
||||||
import type { CommandItem, CommandTableResponse } from "@/defs";
|
import type { CommandItem, CommandTableResponse } from "@/defs";
|
||||||
import CommandSelectForm from "@/components/CommandSelectForm.vue";
|
import CommandSelectForm from "@/components/CommandSelectForm.vue";
|
||||||
@@ -110,7 +121,7 @@ import type { TurnObj } from "@/defs";
|
|||||||
import type { Args } from "./processing/args";
|
import type { Args } from "./processing/args";
|
||||||
import { getEmptyTurn } from "./util/QueryActionHelper";
|
import { getEmptyTurn } from "./util/QueryActionHelper";
|
||||||
import SimpleClock from "./components/SimpleClock.vue";
|
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";
|
import { unwrap } from "./util/unwrap";
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
@@ -135,7 +146,6 @@ const nullCommand: CommandItem = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectedCommand = ref<CommandItem>(nullCommand);
|
const selectedCommand = ref<CommandItem>(nullCommand);
|
||||||
const commandSelectForm = ref<InstanceType<typeof CommandSelectForm> | null>(null);
|
|
||||||
const invCommandMap = new Map<string, CommandItem>();
|
const invCommandMap = new Map<string, CommandItem>();
|
||||||
|
|
||||||
async function updateCommandTable() {
|
async function updateCommandTable() {
|
||||||
@@ -183,7 +193,7 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const reservedCommandList = ref(getEmptyTurn(maxTurn));
|
const reservedCommandList = ref(getEmptyTurn(maxTurn));
|
||||||
|
const reservedUserActionList = ref<(UserActionItem | undefined)[]>([]);
|
||||||
const flippedMaxTurn = 14;
|
const flippedMaxTurn = 14;
|
||||||
|
|
||||||
const basicModeRowHeight = 34.4;
|
const basicModeRowHeight = 34.4;
|
||||||
@@ -209,8 +219,13 @@ const serverNow = ref(new Date());
|
|||||||
|
|
||||||
async function reloadCommandList() {
|
async function reloadCommandList() {
|
||||||
let result: ReservedCommandResponse;
|
let result: ReservedCommandResponse;
|
||||||
|
let userActions: ReservedUserActionResponse;
|
||||||
try {
|
try {
|
||||||
result = await SammoAPI.Command.GetReservedCommand();
|
[result, userActions] = await Promise.all([
|
||||||
|
SammoAPI.Command.GetReservedCommand(),
|
||||||
|
SammoAPI.Command.GetReservedUserAction(),
|
||||||
|
]);
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (isString(e)) {
|
if (isString(e)) {
|
||||||
toasts.danger({
|
toasts.danger({
|
||||||
@@ -265,13 +280,20 @@ async function reloadCommandList() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverNow.value = parseTime(result.date);
|
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() {
|
async function reserveCommand() {
|
||||||
const turnIdx = currentQuickReserveTarget.value;
|
const turnIdx = currentQuickReserveTarget.value;
|
||||||
|
|
||||||
const commandName = selectedCommand.value.value;
|
const commandName = selectedCommand.value.value;
|
||||||
if(turnIdx < 0) {
|
if (turnIdx < 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,14 +355,7 @@ onMounted(() => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
minmax(20px, 0.8fr) minmax(75px, 2.4fr) minmax(40px, 0.9fr) 4.8fr minmax(28px, 0.8fr);
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
.commandTable.isEditMode {
|
|
||||||
width: 100%;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(30px, 1fr) minmax(75px, 2.5fr) minmax(40px, 1fr) 5fr;
|
|
||||||
//30, 70, 37.65, 160
|
//30, 70, 37.65, 160
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,11 +403,6 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.isEditMode .month_pad:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.plain-center {
|
.plain-center {
|
||||||
background-color: black;
|
background-color: black;
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -15,7 +15,7 @@ import {
|
|||||||
export type { ValidResponse, InvalidResponse };
|
export type { ValidResponse, InvalidResponse };
|
||||||
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
|
import { APIPathGen, NumVar, StrVar } from "./util/APIPathGen.js";
|
||||||
import type { BettingDetailResponse, BettingListResponse } from "./defs/API/Betting";
|
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 { ChiefResponse } from "./defs/API/NationCommand";
|
||||||
import type { inheritBuffType, InheritLogResponse } from "./defs/API/InheritAction";
|
import type { inheritBuffType, InheritLogResponse } from "./defs/API/InheritAction";
|
||||||
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse, NationInfoResponse } from "./defs/API/Nation";
|
import type { SetBlockWarResponse, GeneralListResponse as NationGeneralListResponse, NationInfoResponse } from "./defs/API/Nation";
|
||||||
@@ -109,6 +109,7 @@ const apiRealPath = {
|
|||||||
},
|
},
|
||||||
ReserveCommandResponse
|
ReserveCommandResponse
|
||||||
>,
|
>,
|
||||||
|
GetReservedUserAction: GET as APICallT<undefined, ReservedUserActionResponse>,
|
||||||
ReserveBulkCommand: PUT as APICallT<
|
ReserveBulkCommand: PUT as APICallT<
|
||||||
{
|
{
|
||||||
turnList: number[];
|
turnList: number[];
|
||||||
|
|||||||
@@ -20,3 +20,19 @@ export type ReserveBulkCommandResponse = {
|
|||||||
result: true,
|
result: true,
|
||||||
briefList: string[],
|
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 http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<meta name="viewport" content="width=500" />
|
<meta name="viewport" content="width=500" />
|
||||||
<title><?= UniqueConst::$serverName ?>: 개인 전략</title>
|
<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::printJS('../d_shared/common_path.js', true) ?>
|
||||||
<?= WebUtil::printDist('vue', ['v_userAction'], true) ?>
|
<?= WebUtil::printDist('vue', ['v_userAction'], true) ?>
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Reference in New Issue
Block a user