feat(WIP): 턴 조작기 고급 기능
- 잘라내기, 복사하기, 붙여넣기 - 반복하기 - 비우기 - 지우고 당기기, 뒤로 밀기
This commit is contained in:
@@ -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> 잘라내기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="clipboardCopy">
|
||||
<i class="bi bi-files"></i> 복사하기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="clipboardPaste">
|
||||
<i class="bi bi-clipboard-fill"></i> 붙여넣기
|
||||
</BDropdownItem>
|
||||
<BDropdownDivider />
|
||||
<BDropdownItem>
|
||||
<i class="bi bi-bookmark-plus-fill"></i> 보관하기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="subRepeatCommand">
|
||||
<i class="bi bi-arrow-repeat"></i> 반복하기
|
||||
</BDropdownItem>
|
||||
<BDropdownDivider />
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="eraseSelectedTurnList">
|
||||
<i class="bi bi-eraser"></i> 비우기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="eraseAndPullCommand">
|
||||
<i class="bi bi-arrow-bar-up"></i> 지우고 당기기
|
||||
</BDropdownItem>
|
||||
<BDropdownItem>
|
||||
<BDropdownItem @click="pushEmptyCommand">
|
||||
<i class="bi bi-arrow-bar-down"></i> 뒤로 밀기
|
||||
</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();
|
||||
});
|
||||
|
||||
@@ -425,7 +425,7 @@ export default defineComponent({
|
||||
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
|
||||
length: this.maxTurn,
|
||||
}).fill({
|
||||
arg: null,
|
||||
arg: {},
|
||||
brief: "",
|
||||
action: "",
|
||||
year: undefined,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user