feat: 턴 선택기에 복합 커맨드 선택 적용 (#199)

- vue-multiselect 기반
- 초성 검색
- 이득, 불리 표기
- 불가 표기
- 선택후 단축 표기

Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/199
Co-authored-by: hide_d <hided62@gmail.com>
Co-committed-by: hide_d <hided62@gmail.com>
This commit is contained in:
2021-12-05 23:02:27 +09:00
parent 3972cf0194
commit d317a84b09
14 changed files with 719 additions and 67 deletions
+14
View File
@@ -0,0 +1,14 @@
declare module 'vue-multiselect' {
// Type definitions for Vue-Multislect 2.1.0
// Definitions by: Akshay Jat https://github.com/akki-jat
import { VueConstructor } from 'vue';
declare class Multiselect extends VueConstructor { }
declare class multiselectMixin extends VueConstructor { }
declare class pointerMixin extends VueConstructor { }
export default Multiselect;
export { Multiselect, multiselectMixin, pointerMixin };
}
+108 -32
View File
@@ -75,7 +75,7 @@
'white-space': 'nowrap',
'font-size': `${Math.min(
14,
(70 / (`${turnObj.year ?? 1}`.length + 8)) * 1.8
(75 / (`${turnObj.year ?? 1}`.length + 8)) * 1.8
)}px`,
overflow: 'hidden',
}"
@@ -128,21 +128,56 @@
</div>
</div>
<div class="row gx-0">
<div class="col-9 d-grid">
<b-form-select v-model="selectedCommand"
><b-form-select-option-group
v-for="cgroup in commandList"
:key="cgroup['category']"
:label="cgroup['category']"
><b-form-select-option
v-for="(citem, ckey) in cgroup['values']"
:value="ckey"
:key="ckey"
>{{ citem.title
}}{{ citem.possible ? "" : "(불가)" }}</b-form-select-option
>
</b-form-select-option-group></b-form-select
<div class="col-9">
<v-multiselect
v-model="selectedCommand"
:allow-empty="false"
:options="commandList"
:group-select="false"
group-values="values"
group-label="category"
label="searchText"
track-by="value"
open-direction="top"
:show-labels="false"
selectLabel="선택(엔터)"
selectGroupLabel=""
selectedLabel="선택됨"
deselectLabel="해제(엔터)"
deselectGroupLabel=""
placeholder="턴 선택"
:maxHeight="400"
>
<template v-slot:noResult>검색 결과가 없습니다.</template>
<template v-slot:option="props"
><!--FIXME: 카테고리-->
<template v-if="props.option.title">
<span
class="compensatePositive"
v-if="props.option.compensation > 0"
>▲</span
>
<span
class="compensateNegative"
v-else-if="props.option.compensation < 0"
>▼</span
>
<span class="compensateNeutral" v-else></span>
<span :class="[
props.option.possible?'':'commandImpossible',
]">
{{ props.option.title }}
</span>
</template>
<template v-else-if="props.option.category">
{{ props.option.category }}
</template>
</template>
<template v-slot:singleLabel="props">
{{ props.option.simpleName }}
</template>
</v-multiselect>
</div>
<div class="col-3 d-grid">
<b-button @click="reserveCommand()" variant="primary">실행</b-button>
@@ -159,22 +194,27 @@ import { stringifyUrl } from "query-string";
import { defineComponent } from "vue";
import { formatTime } from "./util/formatTime";
import { joinYearMonth } from "./util/joinYearMonth";
import { mb_strwidth } from "./util/mb_strwidth";
import { parseTime } from "./util/parseTime";
import { parseYearMonth } from "./util/parseYearMonth";
import { sammoAPI } from "./util/sammoAPI";
import { unwrap_any } from "./util/unwrap_any";
import { filter초성 } from "./util/filter초성";
type commandItem = {
value: string;
title: string;
compensation: number;
simpleName: string;
possible: boolean;
reqArg: boolean;
searchText?: string;
};
declare const maxTurn: number;
declare const maxPushTurn: number;
declare const commandList: {
category: string;
values: Record<string, commandItem>;
values: commandItem[];
}[];
declare const serverNow: string;
@@ -208,13 +248,11 @@ for (const commandCategories of commandList) {
if (!commandCategories.values) {
continue;
}
for (const [commandName, commandObj] of Object.entries(
commandCategories.values
)) {
for (const commandObj of commandCategories.values) {
if (!commandObj.reqArg) {
continue;
}
listReqArgCommand.add(commandName);
listReqArgCommand.add(commandObj.value);
}
}
@@ -244,7 +282,7 @@ export default defineComponent({
this.serverNow = formatTime(serverNow, "HH:mm:ss");
setTimeout(() => {
this.updateNow();
}, 250);
}, 1000 - serverNow.getMilliseconds());
},
toggleTurn(turnIdx: number) {
this.pressed[turnIdx] = !this.pressed[turnIdx];
@@ -345,14 +383,22 @@ export default defineComponent({
for (const obj of result.turn) {
const [year, month] = parseYearMonth(yearMonth);
let tooltip: string | undefined = undefined;
let tooltip: string[] = [];
let style: Record<string, unknown> = {};
const brief = obj.brief;
if (yearMonth <= autorunLimitYearMonth) {
if (obj.brief == "휴식") {
obj.brief = "휴식<small>(자율 행동)</small>";
}
style.color = "#aaffff";
tooltip = `자율 행동 기간: ${autorunLimitYear}년 ${autorunLimitMonth}월까지`;
tooltip.push(`자율 행동 기간: ${autorunLimitYear}년 ${autorunLimitMonth}월까지`);
}
if(mb_strwidth(brief) > 22){
tooltip.push(brief);
}
reservedCommandList.push({
@@ -360,7 +406,7 @@ export default defineComponent({
year,
month,
time: formatTime(nextTurnTime, "HH:mm"),
tooltip,
tooltip: tooltip.length==0?undefined:tooltip.join("\n"),
style,
});
@@ -383,11 +429,13 @@ export default defineComponent({
turnList.push(turnIdx);
}
if (listReqArgCommand.has(this.selectedCommand)) {
const commandName = this.selectedCommand.value;
if (listReqArgCommand.has(commandName)) {
document.location.href = stringifyUrl({
url: "b_processing.php",
query: {
command: unwrap_any<string>(this.selectedCommand),
command: commandName,
turnList: turnList.join("_"),
},
});
@@ -397,7 +445,7 @@ export default defineComponent({
try {
await sammoAPI("Command/ReserveCommand", {
turnList,
action: this.selectedCommand,
action: commandName,
});
} catch (e) {
console.error(e);
@@ -414,12 +462,21 @@ export default defineComponent({
setTimeout(() => {
this.updateNow();
}, 250);
}, 1000 - serverNowObj.getMilliseconds());
const pressed = Array.from<boolean>({ length: maxTurn }).fill(false);
pressed[0] = true;
const selectedCommand = "휴식";
const selectedCommand = commandList[0].values[0];
for(const subCategory of commandList){
for(const command of subCategory.values){
if(command.searchText){
continue;
}
const filteredText = filter초성(command.simpleName).replace(/\s+/g, '');
command.searchText = `${command.simpleName} ${filteredText}`
}
}
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
length: maxTurn,
@@ -464,13 +521,29 @@ export default defineComponent({
.commandTable {
width: 100%;
display: grid;
grid-template-columns: minmax(30px, 1fr) minmax(70px, 2.5fr) minmax(40px, 1fr) 5fr;
grid-template-columns: minmax(30px, 1fr) minmax(75px, 2.5fr) minmax(40px, 1fr) 5fr;
//30, 70, 37.65, 160
}
@include media-breakpoint-up(md) {
.commandPad {
margin-left: 10px;
.turn_pad {
overflow: hidden;
text-overflow: ellipsis;
}
.multiselect__content-wrapper {
width: 133.3%;
}
.multiselect__single {
display: inline-block;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
}
}
}
@@ -491,7 +564,7 @@ export default defineComponent({
}
}
.month_pad:hover{
.month_pad:hover {
text-decoration: underline;
cursor: pointer;
}
@@ -512,5 +585,8 @@ export default defineComponent({
.turn_pad .turn_text {
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
</style>
+17
View File
@@ -0,0 +1,17 @@
export function filter초성(text: string): string {
const = [
"ㄱ", "ㄲ", "ㄴ", "ㄷ", "ㄸ", "ㄹ", "ㅁ", "ㅂ", "ㅃ",
"ㅅ", "ㅆ", "ㅇ", "ㅈ", "ㅉ", "ㅊ", "ㅋ", "ㅌ", "ㅍ", "ㅎ"
];
const result: string[] = [];
for (const char of text) {
const code = (char.codePointAt(0) ?? 0) - 44032;
if (0 <= code && code < 11172) {
result.push([~~(code / 588)]);
}
else {
result.push(char);
}
}
return result.join('');
}
+2 -2
View File
@@ -2,8 +2,8 @@ import { createApp } from 'vue'
import ReservedCommand from './ReservedCommand.vue';
import BootstrapVue3 from 'bootstrap-vue-3'
import { setAxiosXMLHttpRequest } from './util/setAxiosXMLHttpRequest';
import Multiselect from 'vue-multiselect';
setAxiosXMLHttpRequest();
createApp(ReservedCommand).use(BootstrapVue3).mount('#reservedCommandList')
createApp(ReservedCommand).use(BootstrapVue3).component('v-multiselect', Multiselect).mount('#reservedCommandList')