feat(WIP): 턴 조작기 고급 기능

- 잘라내기, 복사하기, 붙여넣기
- 반복하기
- 비우기
- 지우고 당기기, 뒤로 밀기
This commit is contained in:
2022-03-23 20:12:59 +09:00
parent bcf52e0460
commit 39ac64b837
3 changed files with 252 additions and 37 deletions
+248 -33
View File
@@ -32,6 +32,11 @@
<div class="commandSelectFormAnchor">
<div v-if="isEditMode" class="row gx-0">
<div class="col-4 d-grid">
<BDropdown left text="보관함">
<!-- 즐겨찾기 모드 -->
</BDropdown>
</div>
<div class="col-3 d-grid">
<BDropdown left text="범위">
<BDropdownItem @click="selectTurn()">해제</BDropdownItem>
@@ -54,37 +59,33 @@
</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>
<BDropdownItem @click="clipboardCut">
<i class="bi bi-scissors"></i>&nbsp;잘라내기
</BDropdownItem>
<BDropdownItem>
<BDropdownItem @click="clipboardCopy">
<i class="bi bi-files"></i>&nbsp;복사하기
</BDropdownItem>
<BDropdownItem>
<BDropdownItem @click="clipboardPaste">
<i class="bi bi-clipboard-fill"></i>&nbsp;붙여넣기
</BDropdownItem>
<BDropdownDivider />
<BDropdownItem>
<i class="bi bi-bookmark-plus-fill"></i>&nbsp;보관하기
</BDropdownItem>
<BDropdownItem>
<BDropdownItem @click="subRepeatCommand">
<i class="bi bi-arrow-repeat"></i>&nbsp;반복하기
</BDropdownItem>
<BDropdownDivider />
<BDropdownItem>
<BDropdownItem @click="eraseSelectedTurnList">
<i class="bi bi-eraser"></i>&nbsp;비우기
</BDropdownItem>
<BDropdownItem>
<BDropdownItem @click="eraseAndPullCommand">
<i class="bi bi-arrow-bar-up"></i>&nbsp;지우고 당기기
</BDropdownItem>
<BDropdownItem>
<BDropdownItem @click="pushEmptyCommand">
<i class="bi bi-arrow-bar-down"></i>&nbsp;뒤로 밀기
</BDropdownItem>
<!-- 최근에 실행한 10 -->
@@ -263,11 +264,7 @@ selectTurn(...$event);
</BDropdown>
</div>
<div class="col d-grid">
<BButton @click="toggleViewMaxTurn">
{{
flippedMaxTurn == viewMaxTurn ? "펼치기" : "접기"
}}
</BButton>
<BButton @click="toggleViewMaxTurn">{{ flippedMaxTurn == viewMaxTurn ? "펼치기" : "접기" }}</BButton>
</div>
</div>
</div>
@@ -302,7 +299,7 @@ declare const staticValues: {
<script lang="ts" setup>
import addMilliseconds from "date-fns/esm/addMilliseconds";
import addMinutes from "date-fns/esm/addMinutes";
import { isString, range } from "lodash";
import { clone, isString, min, range } from "lodash";
import { stringifyUrl } from "query-string";
import { onMounted, ref, watch } from "vue";
import { formatTime } from "@util/formatTime";
@@ -317,6 +314,7 @@ import CommandSelectForm from "@/components/CommandSelectForm.vue";
import { BButton, BButtonGroup, BDropdownItem, BDropdown, BDropdownText, BDropdownDivider } from "bootstrap-vue-3";
import { StoredActionsHelper } from "./util/StoredActionsHelper";
import type { TurnObj } from '@/defs';
import { unwrap } from "./util/unwrap";
type TurnObjWithTime = TurnObj & {
time: string;
@@ -645,17 +643,28 @@ async function reserveCommandDirect(args: [number[], TurnObj][], reload = true):
return success;
}
async function reserveCommand() {
let reqTurnList: number[];
if (turnList.value.size == 0) {
reqTurnList = Array.from(prevTurnList.value.values());
} else {
reqTurnList = Array.from(turnList.value.values());
function getSelectedTurnList(): number[] {
if (turnList.value.size) {
return Array.from(turnList.value);
}
if (prevTurnList.value.size) {
return Array.from(prevTurnList.value);
}
return [0];
}
if (reqTurnList.length == 0) {
reqTurnList.push(0);
function releaseSelectedTurnList() {
if (turnList.value.size > 0) {
prevTurnList.value.clear();
for (const v of turnList.value) {
prevTurnList.value.add(v);
}
turnList.value.clear();
}
}
async function reserveCommand() {
let reqTurnList: number[] = getSelectedTurnList();
const commandName = selectedCommand.value.value;
@@ -682,13 +691,9 @@ async function reserveCommand() {
arg: {}
});
if (turnList.value.size > 0) {
prevTurnList.value.clear();
for (const v of turnList.value) {
prevTurnList.value.add(v);
}
turnList.value.clear();
}
releaseSelectedTurnList();
} catch (e) {
console.error(e);
alert(`실패했습니다: ${e}`);
@@ -738,11 +743,221 @@ watch(isEditMode, newEditMode => {
}
});
const emptyTurnObj: TurnObj = { action: '휴식', brief: '휴식', arg: {} };
const storedActionsHelper = new StoredActionsHelper(staticValues.serverNick, 'general');
const recentActions = storedActionsHelper.recentActions;
const storedActions = storedActionsHelper.storedActions;
async function eraseSelectedTurnList(releaseSelect = true): Promise<boolean> {
const result = await reserveCommandDirect([[
getSelectedTurnList(),
emptyTurnObj
]]);
if (releaseSelect) {
releaseSelectedTurnList();
}
return result;
}
function refineQueryActions(): [number[], TurnObj][] {
const reqTurnList = getSelectedTurnList();
const selectedMinTurnIdx = unwrap(min<number>(reqTurnList));
const buffer: [number[], TurnObj][] = [];
for (const rawTurnIdx of reqTurnList) {
const turnIdx = rawTurnIdx - selectedMinTurnIdx;
const rawAction = reservedCommandList.value[rawTurnIdx]
buffer.push([[turnIdx], {
action: rawAction.action,
arg: clone(rawAction.arg),
brief: rawAction.brief
}]);
}
return buffer;
}
function amplifyQueryActions(rawActions: [number[], TurnObj][], reqTurnList: number[]): [number[], TurnObj][] {
if (reqTurnList.length < 1) {
return [];
}
let minQueryIdx = maxTurn;
let maxQueryIdx = 0;
for (const [turnList] of rawActions) {
for (const turnIdx of turnList) {
minQueryIdx = Math.min(minQueryIdx, turnIdx);
maxQueryIdx = Math.max(maxQueryIdx, turnIdx);
}
}
const queryLength = maxQueryIdx - minQueryIdx + 1;
const queryTurnList: number[] = [reqTurnList[0]];
for (const reqTurnIdx of reqTurnList) {
const last = queryTurnList[queryTurnList.length - 1];
if (reqTurnIdx < last + queryLength) {
continue;
}
queryTurnList.push(reqTurnIdx);
}
const actions: [number[], TurnObj][] = [];
for (const [baseTurnList, action] of rawActions) {
const subTurnList: number[] = [];
for (const baseTurnIdx of baseTurnList) {
for (const queryTurnIdx of queryTurnList) {
const targetTurn = baseTurnIdx + queryTurnIdx;
if (targetTurn >= maxTurn) {
continue;
}
subTurnList.push(baseTurnIdx + queryTurnIdx);
}
}
if (subTurnList.length == 0) {
continue;
}
actions.push([subTurnList, action]);
}
return actions;
}
const clipboard = ref<[number[], TurnObj][] | undefined>(undefined);
async function clipboardCut(releaseSelect = true) {
clipboardCopy(false);
return eraseSelectedTurnList(releaseSelect);
}
function clipboardCopy(releaseSelect = true) {
clipboard.value = refineQueryActions();
if (releaseSelect) {
releaseSelectedTurnList();
}
}
async function clipboardPaste(releaseSelect = true) {
const rawActions = clipboard.value;
if (rawActions === undefined) {
return;
}
const actions = amplifyQueryActions(rawActions, getSelectedTurnList());
if (actions.length === 0) {
return;
}
const result = await reserveCommandDirect(actions);
if (releaseSelect) {
releaseSelectedTurnList();
}
return result;
}
async function subRepeatCommand(releaseSelect = true): Promise<boolean> {
const reqTurnList = getSelectedTurnList().sort();
const selectedMinTurnIdx = reqTurnList[0];
const selectedMaxTurnIdx = reqTurnList[reqTurnList.length - 1];
const queryLength = selectedMaxTurnIdx - selectedMinTurnIdx + 1;
const rawActions = refineQueryActions();
const actions = amplifyQueryActions(rawActions, range(selectedMinTurnIdx, maxTurn, queryLength));
const result = await reserveCommandDirect(actions);
if (releaseSelect) {
releaseSelectedTurnList();
}
return result;
}
async function eraseAndPullCommand(releaseSelect = true): Promise<boolean> {
const reqTurnList = getSelectedTurnList().sort();
const selectedMinTurnIdx = reqTurnList[0];
const selectedMaxTurnIdx = reqTurnList[reqTurnList.length - 1];
const queryLength = selectedMaxTurnIdx - selectedMinTurnIdx + 1;
if (selectedMinTurnIdx === 0) {
await pushGeneralCommand(-queryLength);
return true;
}
if (selectedMinTurnIdx + queryLength == maxTurn) {
return eraseSelectedTurnList(releaseSelect);
}
const actions: [number[], TurnObj][] = [];
const emptyTurnList: number[] = [];
for (const srcTurnIdx of range(selectedMinTurnIdx + queryLength, maxTurn)) {
const rawAction = reservedCommandList.value[srcTurnIdx];
if (rawAction.action == emptyTurnObj.action) {
emptyTurnList.push(srcTurnIdx - queryLength);
continue;
}
actions.push([[srcTurnIdx - queryLength], {
action: rawAction.action,
arg: rawAction.arg,
brief: rawAction.brief
}]);
}
emptyTurnList.push(...range(maxTurn - queryLength, maxTurn));
actions.push([emptyTurnList, emptyTurnObj]);
const result = await reserveCommandDirect(actions);
if (releaseSelect) {
releaseSelectedTurnList();
}
return result;
}
async function pushEmptyCommand(releaseSelect = true): Promise<boolean> {
const reqTurnList = getSelectedTurnList().sort();
const selectedMinTurnIdx = reqTurnList[0];
const selectedMaxTurnIdx = reqTurnList[reqTurnList.length - 1];
const queryLength = selectedMaxTurnIdx - selectedMinTurnIdx + 1;
if (selectedMinTurnIdx === 0) {
await pushGeneralCommand(queryLength);
return true;
}
if (selectedMaxTurnIdx == maxTurn) {
return eraseSelectedTurnList(releaseSelect);
}
const actions: [number[], TurnObj][] = [];
const emptyTurnList: number[] = [];
for (const srcTurnIdx of range(selectedMinTurnIdx, maxTurn - queryLength)) {
const rawAction = reservedCommandList.value[srcTurnIdx];
if (rawAction.action == emptyTurnObj.action) {
emptyTurnList.push(srcTurnIdx + queryLength);
continue;
}
actions.push([[srcTurnIdx + queryLength], {
action: rawAction.action,
arg: rawAction.arg,
brief: rawAction.brief
}]);
}
emptyTurnList.push(...range(selectedMinTurnIdx, selectedMinTurnIdx + queryLength));
actions.push([emptyTurnList, emptyTurnObj]);
const result = await reserveCommandDirect(actions);
if (releaseSelect) {
releaseSelectedTurnList();
}
return result;
}
onMounted(() => {
void reloadCommandList();
});
+1 -1
View File
@@ -425,7 +425,7 @@ export default defineComponent({
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
length: this.maxTurn,
}).fill({
arg: null,
arg: {},
brief: "",
action: "",
year: undefined,
+3 -3
View File
@@ -4,7 +4,7 @@ import { ref } from 'vue';
export class StoredActionsHelper {
public readonly recentActions = ref<TurnObj[]>([]);
public readonly storedActions = ref(new Map<string, Record<number, TurnObj>>());
public readonly storedActions = ref(new Map<string, [number[], TurnObj][]>());
public readonly recentActionsKey: string;
public readonly storedActionsKey: string;
@@ -32,11 +32,11 @@ export class StoredActionsHelper {
}
loadStoredActions() {
const rawValue: [string, Record<number, TurnObj>][] = JSON.parse(localStorage.getItem(this.storedActionsKey) ?? '[]');
const rawValue: [string, [number[], TurnObj][]][] = JSON.parse(localStorage.getItem(this.storedActionsKey) ?? '[]');
this.storedActions.value = new Map(rawValue);
}
setStoredActions(actionKey: string, actions: Record<number, TurnObj>) {
setStoredActions(actionKey: string, actions: [number[], TurnObj][]) {
this.storedActions.value.set(actionKey, actions);
this.saveStoredActions();
}