feat: 턴 선택기를 재 작성 (#211)
- select form에서 유사 dialog 버튼 방식으로 변경 - 카테고리마다 페이지 이동 - 일반 모드 / 고급 모드 분리 - 일반 모드에서는 턴별 즉시 설정 - 고급 모드에서는 드래그를 포함한 각종 기능 제공 - 최근 실행 턴 - 잘라내기, 복사하기, 붙여넣기 - 반복하기 - 비우기 - 지우고 당기기, 뒤로 밀기 - 보관하기, 보관한 턴 사용하기 Co-authored-by: Hide_D <hided62@gmail.com> Reviewed-on: https://storage.hided.net/gitea/devsam/core/pulls/211 Co-authored-by: hide_d <hided62@gmail.com> Co-committed-by: hide_d <hided62@gmail.com>
This commit was merged in pull request #211.
This commit is contained in:
@@ -425,7 +425,7 @@ export default defineComponent({
|
||||
const emptyTurn: TurnObjWithTime[] = Array.from<TurnObjWithTime>({
|
||||
length: this.maxTurn,
|
||||
}).fill({
|
||||
arg: null,
|
||||
arg: {},
|
||||
brief: "",
|
||||
action: "",
|
||||
year: undefined,
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<teleport :to="anchor">
|
||||
<div v-if="showForm" class="my-1">
|
||||
<div class="commandCategory row gx-0 gy-1">
|
||||
<div
|
||||
class="categoryItem col-4 d-grid"
|
||||
v-for="[categoryKey, { deco: categoryDeco }] of commandList"
|
||||
:key="categoryKey"
|
||||
>
|
||||
<BButton
|
||||
variant="success"
|
||||
@click="chosenCategory = categoryKey"
|
||||
:active="chosenCategory == categoryKey"
|
||||
>{{ categoryDeco.altName ?? categoryDeco.name }}</BButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="commandList row gx-1 gy-1 my-1">
|
||||
<div
|
||||
class="col-6 d-grid"
|
||||
v-for="commandItem of chosenSubList"
|
||||
:key="commandItem.value"
|
||||
@click="close(commandItem.value)"
|
||||
>
|
||||
<div class="commandItem">
|
||||
<p :class="['center', 'my-0', commandItem.possible ? '' : 'commandImpossible']">
|
||||
{{ commandItem.simpleName }}
|
||||
<span
|
||||
class="compensatePositive"
|
||||
v-if="commandItem.compensation > 0"
|
||||
>▲</span>
|
||||
<span
|
||||
class="compensateNegative"
|
||||
v-else-if="commandItem.compensation < 0"
|
||||
>▼</span>
|
||||
</p>
|
||||
<small class="center" :style="{ display: 'block' }">
|
||||
{{
|
||||
commandItem.title.startsWith(commandItem.simpleName)
|
||||
? commandItem.title.substring(commandItem.simpleName.length)
|
||||
: commandItem.title
|
||||
}}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!hideClose" class="commandBottom row mt-1 mb-1">
|
||||
<div class="offset-8 col-4 d-grid">
|
||||
<BButton @click="close()">닫기</BButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
/*
|
||||
<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>
|
||||
*/
|
||||
import type { CommandItem } from "@/defs";
|
||||
import { BButton } from "bootstrap-vue-3";
|
||||
import { ref, defineProps, defineEmits, defineExpose, type PropType, watch, onMounted } from "vue";
|
||||
|
||||
const chosenCategory = ref<string>("-");
|
||||
const chosenSubList = ref<CommandItem[]>([]);
|
||||
|
||||
interface CategoryDecoration {
|
||||
name: string,
|
||||
altName?: string,
|
||||
//icon?: string,
|
||||
//color?: string,
|
||||
//backgroundColor?: string,
|
||||
}
|
||||
|
||||
const props = defineProps({
|
||||
categoryInfo: {
|
||||
type: Object as PropType<Record<string, Omit<CategoryDecoration, 'name'>>>,
|
||||
required: false,
|
||||
},
|
||||
commandList: {
|
||||
type: Object as PropType<{
|
||||
category: string;
|
||||
values: CommandItem[];
|
||||
}[]>,
|
||||
required: true,
|
||||
},
|
||||
anchor: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '.commandSelectFormAnchor',
|
||||
},
|
||||
hideClose: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true,
|
||||
}
|
||||
})
|
||||
|
||||
const showForm = ref(false);
|
||||
|
||||
function convCategoryDeco(category: string): CategoryDecoration {
|
||||
const itemInfo = props.categoryInfo?.[category];
|
||||
if (!itemInfo) {
|
||||
return {
|
||||
name: category,
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: category,
|
||||
...itemInfo
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
|
||||
const commandList = ref(new Map<string, {
|
||||
deco: CategoryDecoration,
|
||||
values: CommandItem[],
|
||||
}>());
|
||||
|
||||
function updateCommandList(rawCommandList: typeof props.commandList) {
|
||||
commandList.value.clear();
|
||||
for (const { category, values } of rawCommandList) {
|
||||
commandList.value.set(category, {
|
||||
deco: convCategoryDeco(category),
|
||||
values
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch(() => props.commandList, updateCommandList);
|
||||
updateCommandList(props.commandList);
|
||||
|
||||
watch(chosenCategory, (category) => {
|
||||
console.log('sel', category);
|
||||
const itemInfo = commandList.value?.get(category);
|
||||
if (itemInfo === undefined) {
|
||||
console.error(`category 없음: ${category}`);
|
||||
return;
|
||||
}
|
||||
chosenSubList.value = itemInfo.values;
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
chosenCategory.value = props.commandList[0].category;
|
||||
});
|
||||
|
||||
function show(): void {
|
||||
showForm.value = true;
|
||||
}
|
||||
|
||||
function toggle(): void {
|
||||
showForm.value = !showForm.value;
|
||||
if (showForm.value === false) {
|
||||
emits('onClose');
|
||||
}
|
||||
}
|
||||
|
||||
function close(category?: string): void {
|
||||
showForm.value = false;
|
||||
emits('onClose', category);
|
||||
}
|
||||
|
||||
const emits = defineEmits<{
|
||||
(event: 'onClose', command?: string): void,
|
||||
}>();
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
close,
|
||||
toggle
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.commandItem {
|
||||
border: gray 1px solid;
|
||||
border-radius: 0.5em;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
padding: 0.1em;
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,12 +1,14 @@
|
||||
<template>
|
||||
<div
|
||||
ref="container"
|
||||
style="
|
||||
position: relative;
|
||||
user-select: none;
|
||||
overflow: hidden;
|
||||
touch-action: none;
|
||||
:style="{
|
||||
position: 'relative',
|
||||
userSelect: disabled ? undefined : 'none',
|
||||
overflow: 'hidden',
|
||||
touchAction: disabled ? undefined : 'none',
|
||||
}
|
||||
"
|
||||
:class="{ disabledDrag: disabled }"
|
||||
>
|
||||
<slot :selected="intersected" />
|
||||
</div>
|
||||
@@ -58,6 +60,11 @@ export default defineComponent({
|
||||
required: false,
|
||||
default: () => ref(new Set()),
|
||||
},
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
}
|
||||
},
|
||||
emits: ["update:modelValue", "dragDone", "dragStart"],
|
||||
setup(props, { emit }) {
|
||||
@@ -138,6 +145,9 @@ export default defineComponent({
|
||||
|
||||
let isMine = false;
|
||||
function startDrag(e: MouseEvent | Touch) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
containerRect = uContainer.getBoundingClientRect();
|
||||
children = uContainer.children;
|
||||
start = getCoords(e);
|
||||
@@ -152,6 +162,9 @@ export default defineComponent({
|
||||
emit("dragStart");
|
||||
}
|
||||
function drag(e: MouseEvent | Touch) {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
end = getCoords(e);
|
||||
const dimensions = getDimensions(start, end);
|
||||
if (end.x < start.x) {
|
||||
@@ -165,6 +178,9 @@ export default defineComponent({
|
||||
intersection();
|
||||
}
|
||||
function endDrag() {
|
||||
if (props.disabled) {
|
||||
return;
|
||||
}
|
||||
start = { x: 0, y: 0 };
|
||||
end = { x: 0, y: 0 };
|
||||
box.style.width = "0";
|
||||
@@ -172,16 +188,34 @@ export default defineComponent({
|
||||
document.removeEventListener("mousemove", drag);
|
||||
document.removeEventListener("touchmove", touchMove);
|
||||
box.remove();
|
||||
if(isMine){
|
||||
emit("dragDone", intersected.value);
|
||||
if (isMine) {
|
||||
emit("dragDone", intersected.value);
|
||||
}
|
||||
isMine = false;
|
||||
}
|
||||
|
||||
uContainer.addEventListener("mousedown", startDrag);
|
||||
uContainer.addEventListener("touchstart", touchStart);
|
||||
document.addEventListener("mouseup", endDrag);
|
||||
document.addEventListener("touchend", endDrag);
|
||||
watch(() => props.disabled, disabledNext => {
|
||||
if (disabledNext) {
|
||||
uContainer.removeEventListener("mousedown", startDrag);
|
||||
uContainer.removeEventListener("touchstart", touchStart);
|
||||
document.removeEventListener("mouseup", endDrag);
|
||||
document.removeEventListener("touchend", endDrag);
|
||||
}
|
||||
else {
|
||||
uContainer.addEventListener("mousedown", startDrag);
|
||||
uContainer.addEventListener("touchstart", touchStart);
|
||||
document.addEventListener("mouseup", endDrag);
|
||||
document.addEventListener("touchend", endDrag);
|
||||
}
|
||||
});
|
||||
|
||||
if (!props.disabled) {
|
||||
uContainer.addEventListener("mousedown", startDrag);
|
||||
uContainer.addEventListener("touchstart", touchStart);
|
||||
document.addEventListener("mouseup", endDrag);
|
||||
document.addEventListener("touchend", endDrag);
|
||||
}
|
||||
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
uContainer.removeEventListener("mousedown", startDrag);
|
||||
|
||||
Reference in New Issue
Block a user