utils.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. if (typeof window === 'undefined') {
  2. global.window = {}
  3. }
  4. // Copy from https://github.com/primetwig/react-nestable/blob/dacea9dc191399a3520f5dc7623f5edebc83e7b7/dist/utils.js
  5. export var closest = function closest (target, selector) {
  6. // closest(e.target, '.field')
  7. while (target) {
  8. if (target.matches && target.matches(selector)) return target
  9. target = target.parentNode
  10. }
  11. return null
  12. }
  13. export var getOffsetRect = function getOffsetRect (elem) {
  14. // (1)
  15. var box = elem.getBoundingClientRect()
  16. var body = document.body
  17. var docElem = document.documentElement
  18. // (2)
  19. var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop
  20. var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft
  21. // (3)
  22. var clientTop = docElem.clientTop || body.clientTop || 0
  23. var clientLeft = docElem.clientLeft || body.clientLeft || 0
  24. // (4)
  25. var top = box.top + scrollTop - clientTop
  26. var left = box.left + scrollLeft - clientLeft
  27. return { top: Math.round(top), left: Math.round(left) }
  28. }
  29. // jquery focus
  30. export var focus = function (elem) {
  31. return elem === document.activeElement &&
  32. document.hasFocus() &&
  33. !!(elem.type || elem.href || ~elem.tabIndex)
  34. }
  35. // copied from https://stackoverflow.com/a/32180863
  36. export var timeConversion = function (millisec) {
  37. var seconds = (millisec / 1000).toFixed(0)
  38. var minutes = (millisec / (1000 * 60)).toFixed(0)
  39. var hours = (millisec / (1000 * 60 * 60)).toFixed(1)
  40. var days = (millisec / (1000 * 60 * 60 * 24)).toFixed(1)
  41. if (seconds < 60) {
  42. return seconds + 's'
  43. } else if (minutes < 60) {
  44. return minutes + 'm'
  45. } else if (hours < 24) {
  46. return hours + 'h'
  47. } else {
  48. return days + 'd'
  49. }
  50. }
  51. export var getSelectionText = function () {
  52. const selection = (window.getSelection() || '').toString().trim()
  53. if (selection) {
  54. return selection
  55. }
  56. // Firefox fix
  57. const activeElement = window.document.activeElement
  58. if (activeElement) {
  59. if (activeElement.tagName === 'INPUT' || activeElement.tagName === 'TEXTAREA') {
  60. const el = activeElement
  61. return el.value.slice(el.selectionStart || 0, el.selectionEnd || 0)
  62. }
  63. }
  64. return ''
  65. }
  66. // Modified from https://github.com/GoogleChromeLabs/browser-nativefs
  67. // because shadow-cljs doesn't handle this babel transform
  68. export var getFiles = async function (dirHandle, recursive, cb, path = dirHandle.name) {
  69. const dirs = []
  70. const files = []
  71. for await (const entry of dirHandle.values()) {
  72. const nestedPath = `${path}/${entry.name}`
  73. if (entry.kind === 'file') {
  74. cb(nestedPath, entry)
  75. files.push(
  76. entry.getFile().then((file) => {
  77. Object.defineProperty(file, 'webkitRelativePath', {
  78. configurable: true,
  79. enumerable: true,
  80. get: () => nestedPath,
  81. })
  82. Object.defineProperty(file, 'handle', {
  83. configurable: true,
  84. enumerable: true,
  85. get: () => entry,
  86. })
  87. return file
  88. }
  89. )
  90. )
  91. } else if (entry.kind === 'directory' && recursive) {
  92. cb(nestedPath, entry)
  93. dirs.push(getFiles(entry, recursive, cb, nestedPath))
  94. }
  95. }
  96. return [(await Promise.all(dirs)), (await Promise.all(files))]
  97. }
  98. export var verifyPermission = async function (handle, readWrite) {
  99. const options = {}
  100. if (readWrite) {
  101. options.mode = 'readwrite'
  102. }
  103. // Check if permission was already granted.
  104. if ((await handle.queryPermission(options)) === 'granted') {
  105. return
  106. }
  107. // Request permission. If the user grants permission, just return.
  108. if ((await handle.requestPermission(options)) === 'granted') {
  109. return
  110. }
  111. // The user didn't grant permission, throw an error.
  112. throw new Error('Permission is not granted')
  113. }
  114. // NOTE: Need externs to prevent `options.recursive` been munged
  115. // When building with release.
  116. export var openDirectory = async function (options = {}, cb) {
  117. options.recursive = options.recursive || false;
  118. const handle = await window.showDirectoryPicker({ mode: 'readwrite' });
  119. const _ask = await verifyPermission(handle, true);
  120. return [handle, getFiles(handle, options.recursive, cb)];
  121. };
  122. export var writeFile = async function (fileHandle, contents) {
  123. // Create a FileSystemWritableFileStream to write to.
  124. const writable = await fileHandle.createWritable()
  125. if (contents instanceof ReadableStream) {
  126. await contents.pipeTo(writable)
  127. } else {
  128. // Write the contents of the file to the stream.
  129. await writable.write(contents)
  130. // Close the file and write the contents to disk.
  131. await writable.close()
  132. }
  133. }
  134. export var nfsSupported = function () {
  135. if ('chooseFileSystemEntries' in self) {
  136. return 'chooseFileSystemEntries'
  137. } else if ('showOpenFilePicker' in self) {
  138. return 'showOpenFilePicker'
  139. }
  140. return false
  141. }
  142. const inputTypes = [
  143. window.HTMLInputElement,
  144. window.HTMLSelectElement,
  145. window.HTMLTextAreaElement,
  146. ]
  147. export const triggerInputChange = (node, value = '', name = 'change') => {
  148. // only process the change on elements we know have a value setter in their constructor
  149. if (inputTypes.indexOf(node.__proto__.constructor) > -1) {
  150. const setValue = Object.getOwnPropertyDescriptor(node.__proto__, 'value').set
  151. const event = new Event('change', { bubbles: true })
  152. setValue.call(node, value)
  153. node.dispatchEvent(event)
  154. }
  155. }
  156. // Copied from https://github.com/google/diff-match-patch/issues/29#issuecomment-647627182
  157. export const reversePatch = patch => {
  158. return patch.map(patchObj => ({
  159. diffs: patchObj.diffs.map(([ op, val ]) => [
  160. op * -1, // The money maker
  161. val
  162. ]),
  163. start1: patchObj.start2,
  164. start2: patchObj.start1,
  165. length1: patchObj.length2,
  166. length2: patchObj.length1
  167. }));
  168. };
  169. // Copied from https://github.com/sindresorhus/path-is-absolute/blob/main/index.js
  170. export const win32 = path => {
  171. // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56
  172. var splitDeviceRe = /^([a-zA-Z]:|[\\/]{2}[^\\/]+[\\/]+[^\\/]+)?([\\/])?([\s\S]*?)$/;
  173. var result = splitDeviceRe.exec(path);
  174. var device = result[1] || '';
  175. var isUnc = Boolean(device && device.charAt(1) !== ':');
  176. // UNC paths are always absolute
  177. return Boolean(result[2] || isUnc);
  178. };
  179. export const ios = function () {
  180. return [
  181. 'iPad Simulator',
  182. 'iPhone Simulator',
  183. 'iPod Simulator',
  184. 'iPad',
  185. 'iPhone',
  186. 'iPod'
  187. ].includes(navigator.platform)
  188. // iPad on iOS 13 detection
  189. || (navigator.userAgent.includes("Mac") && "ontouchend" in document)
  190. }
  191. export const getClipText = function (cb, errorHandler) {
  192. navigator.permissions.query({ name: "clipboard-read" }).then((result) => {
  193. if (result.state == "granted" || result.state == "prompt") {
  194. navigator.clipboard.readText()
  195. .then(text => {
  196. cb(text);
  197. })
  198. .catch(err => {
  199. errorHandler(err)
  200. });
  201. }
  202. })
  203. }