<template> <!-- sectionを利用しているのは、deck.vue側でcolumnに対してfirst-of-typeを効かせるため --> <section class="dnpfarvg _panel _narrow_" :class="{ paged: isMainColumn, naked, _inContainer_: !isMainColumn, active, isStacked, draghover, dragging, dropready }" @dragover.prevent.stop="onDragover" @dragleave="onDragleave" @drop.prevent.stop="onDrop" v-hotkey="keymap" :style="{ '--deckColumnHeaderHeight': deckStore.reactiveState.columnHeaderHeight.value + 'px' }" > <header :class="{ indicated }" draggable="true" @click="goTop" @dragstart="onDragstart" @dragend="onDragend" @contextmenu.prevent.stop="onContextmenu" > <button class="toggleActive _button" @click="toggleActive" v-if="isStacked && !isMainColumn"> <template v-if="active"><Fa :icon="faAngleUp"/></template> <template v-else><Fa :icon="faAngleDown"/></template> </button> <div class="action"> <slot name="action"></slot> </div> <span class="header"><slot name="header"></slot></span> <button v-if="!isMainColumn" class="menu _button" ref="menu" @click.stop="showMenu"><Fa :icon="faCaretDown"/></button> </header> <div ref="body" v-show="active"> <slot></slot> </div> </section> </template> <script lang="ts"> import { defineComponent } from 'vue'; import { faArrowUp, faArrowDown, faAngleUp, faAngleDown, faCaretDown, faArrowRight, faArrowLeft, faPencilAlt } from '@fortawesome/free-solid-svg-icons'; import { faWindowMaximize, faTrashAlt, faWindowRestore } from '@fortawesome/free-regular-svg-icons'; import * as os from '@/os'; import { updateColumn, swapLeftColumn, swapRightColumn, swapUpColumn, swapDownColumn, stackLeftColumn, popRightColumn, removeColumn, swapColumn } from './deck-store'; import { deckStore } from './deck-store'; export default defineComponent({ props: { column: { type: Object, required: false, default: null }, isStacked: { type: Boolean, required: false, default: false }, menu: { type: Array, required: false, default: null }, naked: { type: Boolean, required: false, default: false }, indicated: { type: Boolean, required: false, default: false }, }, data() { return { deckStore, active: true, dragging: false, draghover: false, dropready: false, faArrowUp, faArrowDown, faAngleUp, faAngleDown, faCaretDown, }; }, computed: { isMainColumn(): boolean { return this.column.type === 'main'; }, keymap(): any { return { 'shift+up': () => this.$parent.$emit('parent-focus', 'up'), 'shift+down': () => this.$parent.$emit('parent-focus', 'down'), 'shift+left': () => this.$parent.$emit('parent-focus', 'left'), 'shift+right': () => this.$parent.$emit('parent-focus', 'right'), }; } }, watch: { active(v) { this.$emit('change-active-state', v); }, dragging(v) { os.deckGlobalEvents.emit(v ? 'column.dragStart' : 'column.dragEnd'); } }, mounted() { os.deckGlobalEvents.on('column.dragStart', this.onOtherDragStart); os.deckGlobalEvents.on('column.dragEnd', this.onOtherDragEnd); }, beforeUnmount() { os.deckGlobalEvents.off('column.dragStart', this.onOtherDragStart); os.deckGlobalEvents.off('column.dragEnd', this.onOtherDragEnd); }, methods: { onOtherDragStart() { this.dropready = true; }, onOtherDragEnd() { this.dropready = false; }, toggleActive() { if (!this.isStacked) return; this.active = !this.active; }, getMenu() { const items = [{ icon: faPencilAlt, text: this.$ts.edit, action: async () => { const { canceled, result } = await os.form(this.column.name, { name: { type: 'string', label: this.$ts.name, default: this.column.name }, width: { type: 'number', label: this.$ts.width, default: this.column.width }, flexible: { type: 'boolean', label: this.$ts.flexible, default: this.column.flexible } }); if (canceled) return; updateColumn(this.column.id, result); } }, null, { icon: faArrowLeft, text: this.$ts._deck.swapLeft, action: () => { swapLeftColumn(this.column.id); } }, { icon: faArrowRight, text: this.$ts._deck.swapRight, action: () => { swapRightColumn(this.column.id); } }, this.isStacked ? { icon: faArrowUp, text: this.$ts._deck.swapUp, action: () => { swapUpColumn(this.column.id); } } : undefined, this.isStacked ? { icon: faArrowDown, text: this.$ts._deck.swapDown, action: () => { swapDownColumn(this.column.id); } } : undefined, null, { icon: faWindowRestore, text: this.$ts._deck.stackLeft, action: () => { stackLeftColumn(this.column.id); } }, this.isStacked ? { icon: faWindowMaximize, text: this.$ts._deck.popRight, action: () => { popRightColumn(this.column.id); } } : undefined, null, { icon: faTrashAlt, text: this.$ts.remove, action: () => { removeColumn(this.column.id); } }]; if (this.menu) { for (const i of this.menu.reverse()) { items.unshift(i); } } return items; }, onContextmenu(e) { os.contextMenu(this.getMenu(), e); }, showMenu() { os.modalMenu(this.getMenu(), this.$refs.menu); }, goTop() { this.$refs.body.scrollTo({ top: 0, behavior: 'smooth' }); }, onDragstart(e) { e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData(_DATA_TRANSFER_DECK_COLUMN_, this.column.id); // Chromeのバグで、Dragstartハンドラ内ですぐにDOMを変更する(=リアクティブなプロパティを変更する)とDragが終了してしまう // SEE: https://stackoverflow.com/questions/19639969/html5-dragend-event-firing-immediately setTimeout(() => { this.dragging = true; }, 10); }, onDragend(e) { this.dragging = false; }, onDragover(e) { // 自分自身がドラッグされている場合 if (this.dragging) { // 自分自身にはドロップさせない e.dataTransfer.dropEffect = 'none'; return; } const isDeckColumn = e.dataTransfer.types[0] == _DATA_TRANSFER_DECK_COLUMN_; e.dataTransfer.dropEffect = isDeckColumn ? 'move' : 'none'; if (!this.dragging && isDeckColumn) this.draghover = true; }, onDragleave() { this.draghover = false; }, onDrop(e) { this.draghover = false; os.deckGlobalEvents.emit('column.dragEnd'); const id = e.dataTransfer.getData(_DATA_TRANSFER_DECK_COLUMN_); if (id != null && id != '') { swapColumn(this.column.id, id); } } } }); </script> <style lang="scss" scoped> .dnpfarvg { --section-padding: 10px; height: 100%; overflow: hidden; contain: content; &.draghover { box-shadow: 0 0 0 2px var(--focus); &:after { content: ""; display: block; position: absolute; z-index: 1000; top: 0; left: 0; width: 100%; height: 100%; background: var(--focus); } } &.dragging { box-shadow: 0 0 0 2px var(--focus); } &.dropready { * { pointer-events: none; } } &:not(.active) { flex-basis: var(--deckColumnHeaderHeight); min-height: var(--deckColumnHeaderHeight); > header.indicated { box-shadow: 4px 0px var(--accent) inset; } } &.naked { background: var(--acrylicBg) !important; -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px); > header { background: transparent; box-shadow: none; > button { color: var(--fg); } } } &.paged { > div { background: var(--bg); } } > header { position: relative; display: flex; z-index: 2; line-height: var(--deckColumnHeaderHeight); height: var(--deckColumnHeaderHeight); padding: 0 16px; font-size: 0.9em; color: var(--panelHeaderFg); background: var(--panelHeaderBg); box-shadow: 0 1px 0 0 var(--panelHeaderDivider); cursor: pointer; &, * { user-select: none; } &.indicated { box-shadow: 0 3px 0 0 var(--accent); } > .header { display: inline-block; align-items: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } > span:only-of-type { width: 100%; } > .toggleActive, > .action > *, > .menu { z-index: 1; width: var(--deckColumnHeaderHeight); line-height: var(--deckColumnHeaderHeight); font-size: 16px; color: var(--faceTextButton); &:hover { color: var(--faceTextButtonHover); } &:active { color: var(--faceTextButtonActive); } } > .toggleActive, > .action { margin-left: -16px; } > .action { z-index: 1; } > .action:empty { display: none; } > .menu { margin-left: auto; margin-right: -16px; } } > div { height: calc(100% - var(--deckColumnHeaderHeight)); overflow: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; box-sizing: border-box; } } </style>