FloatingBox.vue 5.1 KB

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