FloatingBox.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <script setup lang="ts">
  2. import { onMounted, onUnmounted, ref, nextTick } from "vue";
  3. import { useDragBox } from "@/composables/useDragBox";
  4. const props = defineProps({
  5. id: { type: String, default: null },
  6. column: { type: Boolean, default: true },
  7. title: { type: String, default: null },
  8. persist: { type: Boolean, default: false },
  9. initial: { type: String, default: "align-top" },
  10. minWidth: { type: Number, default: 100 },
  11. maxWidth: { type: Number, default: 1000 },
  12. minHeight: { type: Number, default: 100 },
  13. maxHeight: { type: Number, default: 1000 }
  14. });
  15. const {
  16. dragBox,
  17. setInitialBox,
  18. onDragBox,
  19. resetBoxPosition,
  20. setOnDragBoxUpdate
  21. } = useDragBox();
  22. const debounceTimeout = ref();
  23. const shown = ref(false);
  24. const box = ref();
  25. const saveBox = () => {
  26. if (props.id === null) return;
  27. localStorage.setItem(
  28. `box:${props.id}`,
  29. JSON.stringify({
  30. height: dragBox.value.height,
  31. width: dragBox.value.width,
  32. top: dragBox.value.top,
  33. left: dragBox.value.left,
  34. shown: shown.value
  35. })
  36. );
  37. setInitialBox({
  38. top:
  39. props.initial === "align-bottom"
  40. ? Math.max(
  41. document.body.clientHeight - 10 - dragBox.value.height,
  42. 0
  43. )
  44. : 10,
  45. left: 10
  46. });
  47. };
  48. const setBoxDimensions = (width, height) => {
  49. dragBox.value.height = Math.min(
  50. Math.max(height, props.minHeight),
  51. props.maxHeight,
  52. document.body.clientHeight
  53. );
  54. dragBox.value.width = Math.min(
  55. Math.max(width, props.minWidth),
  56. props.maxWidth,
  57. document.body.clientWidth
  58. );
  59. };
  60. const onResizeBox = e => {
  61. if (e.target !== box.value) return;
  62. document.onmouseup = () => {
  63. document.onmouseup = null;
  64. const { width, height } = e.target.style;
  65. setBoxDimensions(
  66. width
  67. .split("")
  68. .splice(0, width.length - 2)
  69. .join(""),
  70. height
  71. .split("")
  72. .splice(0, height.length - 2)
  73. .join("")
  74. );
  75. saveBox();
  76. };
  77. };
  78. const toggleBox = () => {
  79. shown.value = !shown.value;
  80. saveBox();
  81. };
  82. const resetBox = () => {
  83. resetBoxPosition();
  84. setBoxDimensions(200, 200);
  85. saveBox();
  86. };
  87. const onWindowResize = () => {
  88. if (debounceTimeout.value) clearTimeout(debounceTimeout.value);
  89. debounceTimeout.value = setTimeout(() => {
  90. const { width, height } = dragBox.value;
  91. setBoxDimensions(width + 0, height + 0);
  92. saveBox();
  93. }, 50);
  94. };
  95. const onDragBoxUpdate = () => {
  96. onWindowResize();
  97. };
  98. setOnDragBoxUpdate(onDragBoxUpdate);
  99. onMounted(async () => {
  100. let initial = {
  101. top: 10,
  102. left: 10,
  103. width: 200,
  104. height: 400
  105. };
  106. if (props.id !== null && localStorage[`box:${props.id}`]) {
  107. const json = JSON.parse(localStorage.getItem(`box:${props.id}`));
  108. initial = { ...initial, ...json };
  109. shown.value = json.shown;
  110. } else {
  111. initial.top =
  112. props.initial === "align-bottom"
  113. ? Math.max(document.body.clientHeight - 10 - initial.height, 0)
  114. : 10;
  115. }
  116. setInitialBox(initial, true);
  117. await nextTick();
  118. onWindowResize();
  119. window.addEventListener("resize", onWindowResize);
  120. });
  121. onUnmounted(() => {
  122. window.removeEventListener("resize", onWindowResize);
  123. if (debounceTimeout.value) clearTimeout(debounceTimeout.value);
  124. });
  125. defineExpose({
  126. resetBox,
  127. toggleBox
  128. });
  129. </script>
  130. <template>
  131. <div
  132. ref="box"
  133. :class="{
  134. 'floating-box': true,
  135. column
  136. }"
  137. :id="id"
  138. v-if="persist || shown"
  139. :style="{
  140. width: dragBox.width + 'px',
  141. height: dragBox.height + 'px',
  142. top: dragBox.top + 'px',
  143. left: dragBox.left + 'px'
  144. }"
  145. @mousedown.left="onResizeBox"
  146. >
  147. <div class="box-header item-draggable" @mousedown.left="onDragBox">
  148. <span class="drag material-icons" @dblclick="resetBox()"
  149. >drag_indicator</span
  150. >
  151. <span v-if="title" class="box-title" :title="title">{{
  152. title
  153. }}</span>
  154. <span
  155. v-if="!persist"
  156. class="delete material-icons"
  157. @click="toggleBox()"
  158. >highlight_off</span
  159. >
  160. </div>
  161. <div class="box-body">
  162. <slot name="body"></slot>
  163. </div>
  164. </div>
  165. </template>
  166. <style lang="less" scoped>
  167. .night-mode .floating-box {
  168. background-color: var(--dark-grey-2) !important;
  169. border: 0 !important;
  170. .box-body b {
  171. color: var(--light-grey-2) !important;
  172. }
  173. }
  174. .floating-box {
  175. display: flex;
  176. flex-direction: column;
  177. background-color: var(--white);
  178. color: var(--black);
  179. position: fixed;
  180. z-index: 10000000;
  181. resize: both;
  182. overflow: auto;
  183. border: 1px solid var(--light-grey-2);
  184. border-radius: @border-radius;
  185. padding: 0;
  186. .box-header {
  187. display: flex;
  188. height: 30px;
  189. width: 100%;
  190. background-color: var(--primary-color);
  191. color: var(--white);
  192. z-index: 100000001;
  193. .box-title {
  194. font-size: 16px;
  195. font-weight: 600;
  196. line-height: 30px;
  197. margin-right: 5px;
  198. text-overflow: ellipsis;
  199. white-space: nowrap;
  200. overflow: hidden;
  201. }
  202. .material-icons {
  203. font-size: 20px;
  204. line-height: 30px;
  205. &:hover,
  206. &:focus {
  207. filter: brightness(90%);
  208. }
  209. &.drag {
  210. margin: 0 5px;
  211. }
  212. &.delete {
  213. margin: 0 5px 0 auto;
  214. cursor: pointer;
  215. }
  216. }
  217. }
  218. .box-body {
  219. display: flex;
  220. flex-wrap: wrap;
  221. padding: 10px;
  222. height: calc(100% - 30px); /* 30px is the height of the box-header */
  223. overflow: auto;
  224. span {
  225. padding: 3px 6px;
  226. }
  227. }
  228. &.column .box-body {
  229. flex-flow: column;
  230. span {
  231. flex: 1;
  232. display: block;
  233. }
  234. }
  235. }
  236. </style>