FloatingBox.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <template>
  2. <div
  3. ref="box"
  4. :class="{
  5. 'floating-box': true,
  6. column
  7. }"
  8. :id="id"
  9. v-if="persist || shown"
  10. :style="{
  11. width: dragBox.width + 'px',
  12. height: dragBox.height + 'px',
  13. top: dragBox.top + 'px',
  14. left: dragBox.left + 'px'
  15. }"
  16. @mousedown.left="onResizeBox"
  17. >
  18. <div class="box-header item-draggable" @mousedown.left="onDragBox">
  19. <span class="drag material-icons" @dblclick="resetBoxPosition()"
  20. >drag_indicator</span
  21. >
  22. <span v-if="title" class="box-title" :title="title">{{
  23. title
  24. }}</span>
  25. <span
  26. v-if="!persist"
  27. class="delete material-icons"
  28. @click="toggleBox()"
  29. >highlight_off</span
  30. >
  31. </div>
  32. <div class="box-body">
  33. <slot name="body"></slot>
  34. </div>
  35. </div>
  36. </template>
  37. <script>
  38. import DragBox from "@/mixins/DragBox.vue";
  39. export default {
  40. mixins: [DragBox],
  41. props: {
  42. id: { type: String, default: null },
  43. column: { type: Boolean, default: true },
  44. title: { type: String, default: null },
  45. persist: { type: Boolean, default: false },
  46. initial: { type: String, default: "align-top" },
  47. minWidth: { type: Number, default: 100 },
  48. maxWidth: { type: Number, default: 1000 },
  49. minHeight: { type: Number, default: 100 },
  50. maxHeight: { type: Number, default: 1000 }
  51. },
  52. data() {
  53. return {
  54. shown: false,
  55. debounceTimeout: null
  56. };
  57. },
  58. mounted() {
  59. let initial = {
  60. top: 10,
  61. left: 10,
  62. width: 200,
  63. height: 400
  64. };
  65. if (this.id !== null && localStorage[`box:${this.id}`]) {
  66. const json = JSON.parse(localStorage.getItem(`box:${this.id}`));
  67. initial = { ...initial, ...json };
  68. this.shown = json.shown;
  69. } else {
  70. initial.top =
  71. this.initial === "align-bottom"
  72. ? Math.max(
  73. document.body.clientHeight - 10 - initial.height,
  74. 0
  75. )
  76. : 10;
  77. }
  78. this.setInitialBox(initial, true);
  79. this.$nextTick(() => {
  80. this.onWindowResize();
  81. window.addEventListener("resize", this.onWindowResize);
  82. });
  83. },
  84. unmounted() {
  85. window.removeEventListener("resize", this.onWindowResize);
  86. if (this.debounceTimeout) clearTimeout(this.debounceTimeout);
  87. },
  88. methods: {
  89. setBoxDimensions(width, height) {
  90. this.dragBox.height = Math.min(
  91. Math.max(height, this.minHeight),
  92. this.maxHeight,
  93. document.body.clientHeight
  94. );
  95. this.dragBox.width = Math.min(
  96. Math.max(width, this.minWidth),
  97. this.maxWidth,
  98. document.body.clientWidth
  99. );
  100. },
  101. onResizeBox(e) {
  102. if (e.target !== this.$refs.box) return;
  103. document.onmouseup = () => {
  104. document.onmouseup = null;
  105. const { width, height } = e.target.style;
  106. this.setBoxDimensions(
  107. width
  108. .split("")
  109. .splice(0, width.length - 2)
  110. .join(""),
  111. height
  112. .split("")
  113. .splice(0, height.length - 2)
  114. .join("")
  115. );
  116. this.saveBox();
  117. };
  118. },
  119. toggleBox() {
  120. this.shown = !this.shown;
  121. this.saveBox();
  122. },
  123. resetBoxDimensions() {
  124. this.setBoxDimensions(200, 200);
  125. this.saveBox();
  126. },
  127. saveBox() {
  128. if (this.id === null) return;
  129. localStorage.setItem(
  130. `box:${this.id}`,
  131. JSON.stringify({
  132. height: this.dragBox.height,
  133. width: this.dragBox.width,
  134. top: this.dragBox.top,
  135. left: this.dragBox.left,
  136. shown: this.shown
  137. })
  138. );
  139. this.setInitialBox({
  140. top:
  141. this.initial === "align-bottom"
  142. ? Math.max(
  143. document.body.clientHeight -
  144. 10 -
  145. this.dragBox.height,
  146. 0
  147. )
  148. : 10,
  149. left: 10
  150. });
  151. },
  152. onDragBoxUpdate() {
  153. this.onWindowResize();
  154. },
  155. onWindowResize() {
  156. if (this.debounceTimeout) clearTimeout(this.debounceTimeout);
  157. this.debounceTimeout = setTimeout(() => {
  158. const { width, height } = this.dragBox;
  159. this.setBoxDimensions(width + 0, height + 0);
  160. this.saveBox();
  161. }, 50);
  162. }
  163. }
  164. };
  165. </script>
  166. <style lang="less">
  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>