SelectPanel.vue 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <template>
  2. <MoveablePanel
  3. class="select-panel"
  4. :width="200"
  5. :height="360"
  6. :title="selectTitle"
  7. :left="-270"
  8. :top="90"
  9. @close="close()"
  10. >
  11. <div class="handler" v-if="elements.length">
  12. <div class="btns">
  13. <Button size="small" style="margin-right: 5px;" @click="showAllElements()">{{ lang.ssShowAll }}</Button>
  14. <Button size="small" @click="hideAllElements()">{{ lang.ssHideAll }}</Button>
  15. </div>
  16. <div class="icon-btns" v-if="handleElement">
  17. <IconDown class="icon-btn" @click="orderElement(handleElement!, ElementOrderCommands.UP)" />
  18. <IconUp class="icon-btn" @click="orderElement(handleElement!, ElementOrderCommands.DOWN)" />
  19. </div>
  20. </div>
  21. <div class="element-list">
  22. <template v-for="item in elements" :key="item.id">
  23. <div class="group-els" v-if="item.type === 'group'">
  24. <div class="group-title">{{ lang.ssGroup }}</div>
  25. <div
  26. class="item"
  27. :class="{
  28. 'active': activeElementIdList.includes(groupItem.id),
  29. 'group-active': activeGroupElementId.includes(groupItem.id),
  30. }"
  31. v-for="groupItem in item.elements"
  32. :key="groupItem.id"
  33. @click="selectGroupEl(item, groupItem.id)"
  34. @dblclick="enterEdit(groupItem.id)"
  35. >
  36. <input
  37. :id="`select-panel-input-${groupItem.id}`"
  38. :value="groupItem.name || getElementTypeZh()[groupItem.type]"
  39. class="input"
  40. type="text"
  41. v-if="editingElId === groupItem.id"
  42. @blur="$event => saveElementName($event, groupItem.id)"
  43. @keydown.enter="$event => saveElementName($event, groupItem.id)"
  44. >
  45. <div v-else class="name">{{ groupItem.name || getElementTypeZh()[groupItem.type] }}</div>
  46. <div class="icons">
  47. <IconPreviewClose style="font-size: 17px;" @click.stop="toggleHideElement(groupItem.id)" v-if="hiddenElementIdList.includes(groupItem.id)" />
  48. <IconPreviewOpen style="font-size: 17px;" @click.stop="toggleHideElement(groupItem.id)" v-else />
  49. </div>
  50. </div>
  51. </div>
  52. <div
  53. class="item"
  54. :class="{ 'active': activeElementIdList.includes(item.id) }"
  55. v-else
  56. @click="selectElement(item.id)"
  57. @dblclick="enterEdit(item.id)"
  58. >
  59. <input
  60. :id="`select-panel-input-${item.id}`"
  61. :value="item.name || getElementTypeZh()[item.type]"
  62. class="input"
  63. type="text"
  64. v-if="editingElId === item.id"
  65. @blur="$event => saveElementName($event, item.id)"
  66. @keydown.enter="$event => saveElementName($event, item.id)"
  67. >
  68. <div v-else class="name">{{ item.name || getElementTypeZh()[item.type] }}</div>
  69. <div class="icons">
  70. <IconPreviewClose style="font-size: 17px;" @click.stop="toggleHideElement(item.id)" v-if="hiddenElementIdList.includes(item.id)" />
  71. <IconPreviewOpen style="font-size: 17px;" @click.stop="toggleHideElement(item.id)" v-else />
  72. </div>
  73. </div>
  74. </template>
  75. </div>
  76. </MoveablePanel>
  77. </template>
  78. <script lang="ts" setup>
  79. import { computed, nextTick, ref } from 'vue'
  80. import { storeToRefs } from 'pinia'
  81. import { useSlidesStore, useMainStore } from '@/store'
  82. import type { PPTElement } from '@/types/slides'
  83. import { getElementTypeZh } from '@/configs/element'
  84. import useOrderElement from '@/hooks/useOrderElement'
  85. import useHideElement from '@/hooks/useHideElement'
  86. import useSelectElement from '@/hooks/useSelectElement'
  87. import { ElementOrderCommands } from '@/types/edit'
  88. import { lang } from '@/main'
  89. import MoveablePanel from '@/components/MoveablePanel.vue'
  90. import Button from '@/components/Button.vue'
  91. const slidesStore = useSlidesStore()
  92. const mainStore = useMainStore()
  93. const { currentSlide } = storeToRefs(slidesStore)
  94. const { handleElement, handleElementId, activeElementIdList, activeGroupElementId, hiddenElementIdList } = storeToRefs(mainStore)
  95. const { orderElement } = useOrderElement()
  96. const { selectElement } = useSelectElement()
  97. const { toggleHideElement, showAllElements, hideAllElements } = useHideElement()
  98. const selectTitle = computed(() => {
  99. const values = [activeElementIdList.value.length, currentSlide.value.elements.length]
  100. let i = 0
  101. return lang.ssSelectCnt.replace(/\*/g, () => String(values[i++] ?? ''))
  102. })
  103. interface GroupElements {
  104. type: 'group'
  105. id: string
  106. elements: PPTElement[]
  107. }
  108. type ElementItem = PPTElement | GroupElements
  109. const elements = computed<ElementItem[]>(() => {
  110. const _elements: ElementItem[] = []
  111. for (const el of currentSlide.value.elements) {
  112. if (el.groupId) {
  113. const lastItem = _elements[_elements.length - 1]
  114. if (lastItem && lastItem.type === 'group' && lastItem.id && lastItem.id === el.groupId) {
  115. lastItem.elements.push(el)
  116. }
  117. else _elements.push({ type: 'group', id: el.groupId, elements: [el] })
  118. }
  119. else _elements.push(el)
  120. }
  121. return _elements
  122. })
  123. const selectGroupEl = (item: GroupElements, id: string) => {
  124. if (handleElementId.value === id) return
  125. if (hiddenElementIdList.value.includes(id)) return
  126. const idList = item.elements.map(el => el.id)
  127. mainStore.setActiveElementIdList(idList)
  128. mainStore.setHandleElementId(id)
  129. nextTick(() => mainStore.setActiveGroupElementId(id))
  130. }
  131. const editingElId = ref('')
  132. const saveElementName = (e: FocusEvent | KeyboardEvent, id: string) => {
  133. const name = (e.target as HTMLInputElement).value
  134. slidesStore.updateElement({ id, props: { name } })
  135. editingElId.value = ''
  136. }
  137. const enterEdit = (id: string) => {
  138. editingElId.value = id
  139. nextTick(() => {
  140. const inputRef = document.querySelector(`#select-panel-input-${id}`) as HTMLInputElement
  141. inputRef.focus()
  142. })
  143. }
  144. const close = () => {
  145. mainStore.setSelectPanelState(false)
  146. }
  147. </script>
  148. <style lang="scss" scoped>
  149. .select-panel {
  150. height: 100%;
  151. font-size: 12px;
  152. user-select: none;
  153. }
  154. .handler {
  155. height: 24px;
  156. margin-bottom: 8px;
  157. display: flex;
  158. align-items: center;
  159. justify-content: space-between;
  160. .icon-btns {
  161. height: 100%;
  162. flex: 1;
  163. display: flex;
  164. align-items: center;
  165. justify-content: flex-end;
  166. }
  167. .icon-btn {
  168. width: 16px;
  169. height: 100%;
  170. display: flex;
  171. align-items: center;
  172. justify-content: center;
  173. cursor: pointer;
  174. &:hover {
  175. color: $themeColor;
  176. }
  177. }
  178. }
  179. .element-list {
  180. height: calc(100% - 32px);
  181. padding-right: 10px;
  182. margin-right: -10px;
  183. overflow: auto;
  184. }
  185. .item {
  186. padding: 5px;
  187. font-size: 12px;
  188. border-radius: $borderRadius;
  189. display: flex;
  190. align-items: center;
  191. cursor: pointer;
  192. &.active {
  193. background-color: rgba($color: $themeColor, $alpha: .1);
  194. }
  195. &.group-active {
  196. background-color: rgba($color: $themeColor, $alpha: .2);
  197. }
  198. &:hover {
  199. background-color: rgba($color: $themeColor, $alpha: .25);
  200. }
  201. .name {
  202. height: 18px;
  203. line-height: 18px;
  204. flex: 1;
  205. @include ellipsis-oneline();
  206. }
  207. .icons {
  208. width: 20px;
  209. display: flex;
  210. align-items: center;
  211. justify-content: center;
  212. margin-left: 5px;
  213. }
  214. }
  215. .group-els {
  216. padding: 5px 0;
  217. .group-title {
  218. margin-bottom: 5px;
  219. padding: 0 5px;
  220. }
  221. .item {
  222. margin-left: 15px;
  223. }
  224. }
  225. .input {
  226. width: 100%;
  227. height: 16px;
  228. border: 0;
  229. outline: 0;
  230. padding-left: 0;
  231. padding-right: 0;
  232. flex: 1;
  233. font-size: 12px;
  234. background-color: transparent;
  235. }
  236. </style>