utility.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. /* eslint-disable no-param-reassign */
  2. /* eslint-disable @typescript-eslint/no-var-requires */
  3. /* eslint-disable no-underscore-dangle */
  4. import { fromEvent } from 'rxjs';
  5. import { debounceTime, throttleTime } from 'rxjs/operators';
  6. export const objIsEmpty = (obj: Record<string, unknown>): boolean =>
  7. !Object.keys(obj).length;
  8. export const watchScroll = (
  9. viewAreaElement: HTMLElement | null,
  10. cb: (state: ScrollStateType) => void,
  11. ): ScrollStateType => {
  12. let rAF: number | null = null;
  13. const element = viewAreaElement as HTMLElement;
  14. const state = {
  15. right: true,
  16. down: true,
  17. lastX: element.scrollLeft,
  18. lastY: element.scrollTop,
  19. subscriber: {},
  20. };
  21. const debounceScroll = (): void => {
  22. if (rAF) {
  23. return;
  24. }
  25. // schedule an invocation of scroll for next animation frame.
  26. rAF = window.requestAnimationFrame(() => {
  27. rAF = null;
  28. const currentX = element.scrollLeft;
  29. const { lastX } = state;
  30. if (currentX !== lastX) {
  31. state.right = currentX > lastX;
  32. }
  33. state.lastX = currentX;
  34. const currentY = element.scrollTop;
  35. const { lastY } = state;
  36. if (currentY !== lastY) {
  37. state.down = currentY > lastY;
  38. }
  39. state.lastY = currentY;
  40. cb(state);
  41. });
  42. };
  43. const subscriber = fromEvent(element, 'scroll')
  44. .pipe(throttleTime(100), debounceTime(100))
  45. .subscribe(debounceScroll);
  46. state.subscriber = subscriber;
  47. return state;
  48. };
  49. export const scrollIntoView = (
  50. element: HTMLElement,
  51. spot?: { top: number },
  52. skipOverflowHiddenElements = false,
  53. ): void => {
  54. let parent = element.offsetParent as HTMLElement;
  55. let offsetY = element.offsetTop + element.clientTop;
  56. if (!parent) {
  57. return; // no need to scroll
  58. }
  59. while (
  60. (parent.clientHeight === parent.scrollHeight &&
  61. parent.clientWidth === parent.scrollWidth) ||
  62. (skipOverflowHiddenElements &&
  63. getComputedStyle(parent).overflow === 'hidden')
  64. ) {
  65. if (parent.dataset._scaleY) {
  66. offsetY /= parseInt(parent.dataset._scaleY, 10);
  67. }
  68. offsetY += parent.offsetTop;
  69. parent = parent.offsetParent as HTMLElement;
  70. if (!parent) {
  71. return; // no need to scroll
  72. }
  73. }
  74. if (spot) {
  75. if (spot.top !== undefined) {
  76. offsetY += spot.top;
  77. }
  78. }
  79. parent.scrollTop = offsetY;
  80. };
  81. export const scaleCheck = (scale: number): number => {
  82. if (typeof scale === 'number' && scale >= 50 && scale <= 250) {
  83. return Math.round(scale * 100) / 10000;
  84. }
  85. if (scale < 50) {
  86. return 0.5;
  87. }
  88. return 2.5;
  89. };
  90. export const downloadFileWithUri = (name: string, uri: string): void => {
  91. const ele = document.createElement('a');
  92. ele.download = name;
  93. ele.href = uri;
  94. ele.target = '_blank';
  95. document.body.appendChild(ele);
  96. ele.click();
  97. document.body.removeChild(ele);
  98. ele.remove();
  99. };
  100. export const uploadFile = (extension: string): Promise<string> =>
  101. new Promise((resolve) => {
  102. const fileInput = document.createElement('input');
  103. fileInput.type = 'file';
  104. fileInput.accept = extension;
  105. fileInput.onchange = (): void => {
  106. if (fileInput.files) {
  107. const reader = new FileReader();
  108. reader.onload = (): void => {
  109. const contents = reader.result as string;
  110. resolve(contents);
  111. };
  112. if (extension.includes('xfdf')) {
  113. reader.readAsText(fileInput.files[0]);
  114. } else {
  115. reader.readAsDataURL(fileInput.files[0]);
  116. }
  117. }
  118. };
  119. document.body.appendChild(fileInput);
  120. fileInput.click();
  121. });
  122. const componentToHex = (c: number): string => {
  123. const hex = c.toString(16);
  124. return hex.length === 1 ? `0${hex}` : hex;
  125. };
  126. export const hexToRgb = (
  127. hex: string,
  128. ): { r: number; g: number; b: number } | null => {
  129. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  130. return result
  131. ? {
  132. r: parseInt(result[1], 16),
  133. g: parseInt(result[2], 16),
  134. b: parseInt(result[3], 16),
  135. }
  136. : null;
  137. };
  138. export const rgbToHex = (r: number, g: number, b: number): string =>
  139. `#${componentToHex(r)}${componentToHex(g)}${componentToHex(b)}`;
  140. export const floatToHex = (r: number, g: number, b: number): string =>
  141. rgbToHex(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255));
  142. type ScaleDataType = CoordType & {
  143. operator: string;
  144. clickX: number;
  145. clickY: number;
  146. moveX: number;
  147. moveY: number;
  148. };
  149. export const calcDragAndDropScale = ({
  150. top,
  151. left,
  152. width = 0,
  153. height = 0,
  154. operator,
  155. clickX,
  156. clickY,
  157. moveX,
  158. moveY,
  159. }: ScaleDataType): CoordType => {
  160. let scaleCoord = {
  161. left,
  162. top,
  163. width,
  164. height,
  165. };
  166. switch (operator) {
  167. case 'top':
  168. scaleCoord = {
  169. ...scaleCoord,
  170. top: moveY - (clickY - top),
  171. height: height + (clickY - moveY),
  172. };
  173. break;
  174. case 'top-left':
  175. scaleCoord = {
  176. top: moveY - (clickY - top),
  177. left: moveX - (clickX - left),
  178. height: height + (clickY - moveY),
  179. width: width + (clickX - moveX),
  180. };
  181. break;
  182. case 'left':
  183. scaleCoord = {
  184. ...scaleCoord,
  185. left: moveX - (clickX - left),
  186. width: width + (clickX - moveX),
  187. };
  188. break;
  189. case 'bottom-left':
  190. scaleCoord = {
  191. ...scaleCoord,
  192. left: moveX - (clickX - left),
  193. width: width + (clickX - moveX),
  194. height: height + moveY - clickY,
  195. };
  196. break;
  197. case 'bottom':
  198. scaleCoord = {
  199. ...scaleCoord,
  200. height: height + moveY - clickY,
  201. };
  202. break;
  203. case 'bottom-right':
  204. scaleCoord = {
  205. ...scaleCoord,
  206. width: width + moveX - clickX,
  207. height: height + moveY - clickY,
  208. };
  209. break;
  210. case 'right':
  211. scaleCoord = {
  212. ...scaleCoord,
  213. width: width + moveX - clickX,
  214. };
  215. break;
  216. case 'top-right':
  217. scaleCoord = {
  218. ...scaleCoord,
  219. top: moveY - (clickY - top),
  220. width: width + moveX - clickX,
  221. height: height + (clickY - moveY),
  222. };
  223. break;
  224. default:
  225. break;
  226. }
  227. return scaleCoord;
  228. };
  229. const FRACTIONDIGITS = 2;
  230. type NormalizeRoundFunc = (num: number, fractionDigits?: number) => number;
  231. export const normalizeRound: NormalizeRoundFunc = (num, fractionDigits) => {
  232. const frac = fractionDigits || FRACTIONDIGITS;
  233. return Math.round(num * 10 ** frac) / 10 ** frac;
  234. };
  235. const printDocument = (documentId: string): void => {
  236. const iframe = document.getElementById(documentId) as HTMLIFrameElement;
  237. const doc = iframe.contentWindow as { print: () => void };
  238. if (doc && typeof doc.print === 'undefined') {
  239. setTimeout(() => {
  240. printDocument(documentId);
  241. }, 1000);
  242. } else {
  243. setTimeout(() => {
  244. doc.print();
  245. }, 3000);
  246. }
  247. };
  248. export const printPdf = async (url: string): Promise<void> => {
  249. // eslint-disable-next-line global-require
  250. const printJS = require('print-js');
  251. const isIE11 = !!(window.navigator && window.navigator.msSaveOrOpenBlob);
  252. if (!isIE11) {
  253. printJS(url);
  254. } else {
  255. const wrapper = document.getElementById('embed-wrapper') as HTMLElement;
  256. wrapper.style.display = 'flex';
  257. const ele = document.createElement('embed');
  258. ele.setAttribute('type', 'application/pdf');
  259. ele.setAttribute('id', 'pdf-embed');
  260. ele.style.width = '70%';
  261. ele.style.height = '70%';
  262. ele.style.position = 'fixed';
  263. ele.style.top = '0';
  264. ele.style.left = '0';
  265. ele.style.bottom = '0';
  266. ele.style.right = '0';
  267. ele.style.margin = 'auto';
  268. ele.setAttribute('src', url);
  269. wrapper.appendChild(ele);
  270. printDocument('pdf-embed');
  271. }
  272. };
  273. export const strip = (number: number): number => {
  274. return parseFloat(parseFloat(number.toString()).toPrecision(12));
  275. };