lgx-tools

允许删除页面的元素,往页面上放置项目

  1. // ==UserScript==
  2. // @name lgx-tools
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2.4.2
  5. // @description 允许删除页面的元素,往页面上放置项目
  6. // @author lgx
  7. // @match *://*/*
  8. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12. // noinspection SpellCheckingInspection
  13.  
  14. (function() {
  15. 'use strict';
  16.  
  17. if (!document.body) return
  18. document.head.appendChild(document.createElement('style'));
  19. document.head.lastChild.innerHTML = '.lgx-front-hover-block{position:fixed;background:#09f3;border:dashed 1px ' +
  20. '#06f9;transition:all .3s cubic-bezier(.68,-0.55,.27,1.55);z-index:2147483647}.lgx-front-hover-block>div-l' +
  21. 'gx{position:absolute;border:1px dashed #f11a;background:#f553;transition:inherit}.lgx-front-hover-block>d' +
  22. 'iv-lgx:hover{background:#f556}.lgx-ctrl-block-container{width:60px;height:60px;position:fixed;left:-30px;' +
  23. 'bottom:-30px;border-radius:50%;z-index:2147483647}.lgx-ctrl-block{width:6px;height:6px;position:fixed;bac' +
  24. 'kground:red;bottom:-6px;left:-6px;z-index:2147483647;cursor:pointer;transition:all .5s}.lgx-ctrl-block-co' +
  25. 'ntainer:hover>.lgx-ctrl-block{left:18px;bottom:18px;rotate:765deg}@keyframes k{0%{left:-6px;bottom:-6px;r' +
  26. 'otate:0}25%{left:0;bottom:0;rotate:191.25deg}50%{left:6px;bottom:6px;rotate:382.5deg}75%{left:12px;bottom' +
  27. ':12px;rotate:573.75deg}100%{left:18px;bottom:18px;rotate:765deg}}.lgx-front-hover-no-transition,.lgx-fron' +
  28. 't-hover-no-transition>*{transition:none !important}@keyframes shake{0%{transform:translateX(0)}10%{transf' +
  29. 'orm:translateX(-10px)}20%{transform:translateX(10px)}35%{transform:translateX(-10px)}55%{transform:transl' +
  30. 'ateX(10px)}70%{transform:translateX(-10px)}100%{transform:translateX(0)}}.lgx-float-div{position:fixed;op' +
  31. 'acity:.5;border:none;cursor:grab;z-index:2147483647;user-select:none}.lgx-float-div>.lgx-resize{content:"' +
  32. '";display:block;position:absolute}.lgx-float-div>*{user-select:none}.lgx-float-div>.lgx-col-resize{width:' +
  33. '6px;height:calc(100% - 6px);right:-3px;top:0;cursor:col-resize}.lgx-float-div>.lgx-row-resize{width:calc(' +
  34. '100% - 6px);height:6px;left:0;bottom:-3px;cursor:row-resize}.lgx-float-div>.lgx-nwse-resize{width:12px;he' +
  35. 'ight:12px;right:-6px;bottom:-6px;cursor:nwse-resize}.lgx-float-div>.lgx-float-item{box-shadow:0 0 5px #00' +
  36. '05;width:100%;height:100%;display:block;font-size:14px}.lgx-floating-menu{transition:all .1s;width:100px;' +
  37. 'padding:5px;opacity:.5;border-radius:5px;box-shadow:2px 2px 5px #0005;position:fixed;z-index:2147483647;b' +
  38. 'ackground-color:#fff;user-select:none}.lgx-floating-menu ul{padding:0;margin:0;list-style:none}.lgx-float' +
  39. 'ing-menu li:hover{background-color:#f2f2f2}.lgx-removed{display:none !important}.lgx-front-hover-block>sp' +
  40. 'an{white-space:nowrap;border-radius:0 0 5px 0;padding:2px 6px;background-color:#fffc}.lgx-front-hover-blo' +
  41. 'ck>span>span{color:#fff;text-shadow:0 0 2px #555,1px 0 5px #000;font-weight:bold}.selected-span{color:#fc' +
  42. 'c !important;text-shadow:0 0 2px #500,1px 0 5px #500 !important}'
  43.  
  44. const styleElement = document.createElement('style')
  45. document.head.appendChild(styleElement)
  46. let el, k = !1, x = 0, y = 0
  47. const that = document.createElement("div-lgx")
  48. const ctrl = document.createElement('div-lgx')
  49. const ctrlContainer = document.createElement('div-lgx')
  50. let removedElements = [], removedIndex = 0
  51. ctrlContainer.appendChild(ctrl);
  52. document.body.appendChild(ctrlContainer);
  53.  
  54. ctrl.classList.add('lgx-ctrl-block');
  55. that.classList.add('lgx-front-hover-block');
  56. ctrlContainer.classList.add('lgx-ctrl-block-container');
  57. function mouseMoveHandler(ev) {
  58. if (ev.target?.classList.contains('undeletable')) {
  59. clearTimeout(mouseMoveHandler.timeout0)
  60. return
  61. }
  62. // if (el === (ev.target?.mock || ev.target)) return
  63. el = ev.target?.mock || ev.target
  64.  
  65. that.classList[ ev.shiftKey ? 'add' : 'remove' ]('lgx-front-hover-no-transition')
  66. const fun1 = () => {
  67. for (const {style: s, l0, t0} of that.querySelectorAll('div-lgx')) {
  68. [s.left, s.top, s.width, s.height] = [l0, t0, 0, 0].map(j => j.px)
  69. }
  70. const fun2 = () => {
  71. const {left: l0, top: t0, height: h, width: w} =
  72. el?.getBoundingClientRect() ?? {left: 0, top: 0, height: 0, width: 0}, s = that.style, arr = []
  73. const fun3 = () => {
  74. if (!el) return;
  75. that.innerHTML = `<span><span>${el.tagName.toLocaleLowerCase()}</span>` + (el.id ? `<span>#${el.id}</span>` : '')
  76. + (el.classList.length ? [...el.classList].map(i => `<span>.${i}</span>`).join('') : '') + '</span>'
  77. that.selectedItem = -1
  78. that.querySelectorAll('span').forEach(i => {
  79. i.classList.add('undeletable');
  80. i.addEventListener('click', ev => ev.stopPropagation());
  81. });
  82. that.items = [...that.querySelectorAll('span>span')]
  83. for (const i of el?.children ?? []) {
  84. if (i === that || i === ctrl) continue
  85. const {left: l, top: t, right: r, bottom: b, height: h, width: w} = i.getBoundingClientRect()
  86. const [dh, dw] = [document.documentElement.clientHeight, document.documentElement.clientWidth]
  87. if (b < 0 || t > dh || l > dw || r < 0) continue
  88. const d = document.createElement('div-lgx');
  89. [d.mock, d.l0, d.t0, d.l, d.t, d.w, d.h] =
  90. [i, l + w / 2 - l0, t + h / 2 - t0, l - l0, t - t0, w - 2, h - 2]
  91. d.style = `left:${d.l0}px;top:${d.t0}px;width:0;height:0;z-index:-1`
  92. that.appendChild(d)
  93. arr.push(d)
  94. }
  95. const fun4 = () => {
  96. for (const d of arr) {
  97. [d.style.left, d.style.top, d.style.height, d.style.width] =
  98. [d.l, d.t, d.h, d.w].map(i => i.px)
  99. }
  100. }
  101. ev.shiftKey ? fun4() : setTimeout(fun4, 30)
  102. }
  103. [s.left, s.top, s.height, s.width] = [l0, t0, h - 2, w - 2].map(i => i.px)
  104. that.innerHTML = ''
  105. if (ev.shiftKey) {
  106. fun3()
  107. } else {
  108. clearTimeout(that.timeout)
  109. that.timeout = setTimeout(fun3, 300)
  110. }
  111. }
  112.  
  113. if (ev.shiftKey) {
  114. fun2()
  115. } else {
  116. clearTimeout(mouseMoveHandler.timeout)
  117. mouseMoveHandler.timeout = setTimeout(fun2, 300)
  118. }
  119. }
  120. if (ev.shiftKey) {
  121. fun1()
  122. } else {
  123. clearTimeout(mouseMoveHandler.timeout0)
  124. mouseMoveHandler.timeout0 = setTimeout(fun1, ev.target?.mock ? 500 : 0)
  125. }
  126. }
  127.  
  128. ctrl.onclick = () => {
  129. k = !k
  130. ctrl.style.background = k ? 'green' : 'red'
  131. if (k) {
  132. document.body?.addEventListener('mousemove', mouseMoveHandler)
  133. that.style.left = x + 'px'
  134. that.style.top = y + 'px'
  135. that.style.width = '0px'
  136. that.style.height = '0px'
  137. that.innerHTML = ''
  138. document.body?.insertBefore(that, ctrlContainer)
  139. setTimeout(mouseMoveHandler, 20, new Event('mousemove'))
  140. } else {
  141. el = void 0
  142. that.remove()
  143. try { document.body?.removeEventListener('mousemove', mouseMoveHandler) } catch {}
  144. }
  145. }
  146.  
  147. ctrl.addEventListener('contextmenu', ev => { ev.preventDefault(); console.clear() });
  148.  
  149. window.addEventListener('mousemove', ev => {
  150. [x, y] = [ev.x, ev.y]
  151. })
  152.  
  153. window.addEventListener('keydown', ev => {
  154. if (ev.key === 'Control') {
  155. that.style.display = 'none'
  156. }
  157. })
  158.  
  159. window.addEventListener('keyup', ev => {
  160. if (ev.key === 'Control') {
  161. that.style.display = 'block'
  162. }
  163. })
  164.  
  165. window.addEventListener('click', ev => {
  166. if (el?.classList.contains('undeletable')) return
  167. if (!ev.ctrlKey && k && el) {
  168. if (that.selectedItem === -1) {
  169. el.classList.add('lgx-removed')
  170. if (removedIndex < removedElements.length) {
  171. removedElements = removedElements.slice(0, removedIndex)
  172. }
  173. removedElements.push([el])
  174. } else {
  175. const els = [...document.querySelectorAll(that.items[that.selectedItem].innerText)]
  176. els.forEach(el => el !== ctrl && el !== that && el.classList.add('lgx-removed'))
  177. removedElements.push(els)
  178. }
  179. removedIndex++
  180. }
  181. that.style.left = x + 'px'
  182. that.style.top = y + 'px'
  183. that.style.width = '0px'
  184. that.style.height = '0px'
  185. that.innerHTML = ''
  186. })
  187.  
  188. that.addEventListener('wheel', ev => {
  189. if (el === ctrl || el === document.body) return
  190. if (((that.lastWheel + 200) || 0) > +new Date()) return;
  191. that.lastWheel = +new Date();
  192. if (!ev.ctrlKey && k && el) {
  193. ev.preventDefault()
  194. if (ev.deltaY > 0) {
  195. that.items[that.selectedItem++]?.classList.toggle('selected-span')
  196. that.selectedItem = (that.selectedItem + 1) % (that.items.length + 1) - 1
  197. that.items[that.selectedItem]?.classList.toggle('selected-span')
  198. }
  199. if (ev.deltaY < 0) {
  200. that.items[that.selectedItem--]?.classList.toggle('selected-span')
  201. that.selectedItem = (that.selectedItem + that.items.length + 2) % (that.items.length + 1) - 1
  202. that.items[that.selectedItem]?.classList.toggle('selected-span')
  203. }
  204. }
  205. })
  206.  
  207. ctrl.addEventListener('wheel', ev => {
  208. ev.preventDefault()
  209. if (((ctrl.lastWheel + 200) || 0) > +new Date()) return;
  210. ctrl.lastWheel = +new Date();
  211. if (ev.deltaY > 0 && removedIndex < removedElements.length) {
  212. removedElements[removedIndex++].forEach(i=>i.classList.add('lgx-removed'))
  213. }
  214. if (ev.deltaY < 0 && removedIndex > 0) {
  215. removedElements[--removedIndex].forEach(i=>i.classList.remove('lgx-removed'))
  216. }
  217. });
  218.  
  219. // !--- img dropping handler ---! //
  220.  
  221. ['drop', 'dragleave', 'dragover', 'dragenter'].forEach(
  222. i => document.addEventListener(i, e => e.preventDefault())
  223. )
  224.  
  225. const floatingContainer = document.createElement('div-lgx')
  226. document.body.insertBefore(floatingContainer, ctrlContainer)
  227.  
  228. Array( Number, String ).forEach(
  229. i => Object.defineProperty(i.prototype, 'px', {
  230. get() { return this + 'px' }
  231. })
  232. )
  233.  
  234. Object.defineProperties(HTMLElement.prototype, {
  235. timeout: { value: 0, writable: true },
  236. width: { get() { return parseFloat(this.style.width ) || this.clientWidth } },
  237. height: { get() { return parseFloat(this.style.height) || this.clientHeight } },
  238. left: { get() { return parseFloat(this.style. left ) || window.innerWidth - parseFloat(this.style.width) - parseFloat(this.style.right) || this.offsetLeft } },
  239. right: { get() { return parseFloat(this.style.right ) || window.innerWidth - parseFloat(this.style.width) - parseFloat(this.style.left) || window.innerWidth - this.clientWidth - this.offsetLeft } },
  240. top: { get() { return parseFloat(this. style. top ) || window.innerHeight - parseFloat(this.style.height) - parseFloat(this.style.bottom) || this.offsetTop } },
  241. bottom: { get() { return parseFloat(this.style.bottom) || window.innerHeight - parseFloat(this.style.height) - parseFloat(this.style.top) || window.innerHeight - this.offsetTop - this.clientHeight } },
  242. item: { get() {
  243. const value = this.querySelector('.lgx-float-item')
  244. Object.defineProperty(this, 'item', { value })
  245. return value
  246. }, configurable: true },
  247. makeScaleable: {
  248. value() {
  249. let x, y, delta
  250. Object.defineProperties(this, {
  251. // The method 'makeScaleable' is a once-call-method, once it has been called, it will be removed.
  252. makeScaleable: { value: void 0 },
  253. // scalingCoef: scaling coefficient
  254. // The pointer closer to the center of this div element, the coefficient bigger.
  255. scalingCoef: {
  256. get() {
  257. const d = Math.sqrt(
  258. Math.pow(x - this.left - this.width / 2, 2)
  259. + Math.pow(y - this.top - this.height / 2, 2)
  260. )
  261. const h = this.height, w = this.width
  262. const r = Math.sqrt(h ** 2 + w ** 2) / 2
  263. const v = (1 - d / r) * .2
  264. return { x: w * v * delta, y: h * v * delta }
  265. }
  266. },
  267. // pperc: the percentage of the location of the pointer in this div element
  268. // e.g. coordinate of the pointer in this div is (2, 3), and the size of this div is (5, 6),
  269. // then the percentage (pperc) is { left: 2 / 5, right: 3 / 5, top: 1 / 2, bottom: 1 / 2 }
  270. pperc: {
  271. get() {
  272. const l = (x - this.left) / this.width,
  273. t = (y - this.top) / this.height
  274. return { left: l, right: 1 - l, top: t, bottom: 1 - t }
  275. }
  276. }
  277. })
  278. this.onwheel = ev => {
  279. [ x, y, delta ] = [ ev.x, ev.y, -ev.deltaY / 114 ]
  280. ev.preventDefault()
  281. const coef = this.scalingCoef, pperc = this.pperc
  282. const nw = this.width + coef.x, nh = this.height + coef.y
  283. if (Math.max(nw, nh) > 3000 || Math.min(nw, nh) < 20) {
  284. if (!this.onwheel.flag) {
  285. this.onwheel.flag = true
  286. this.item.style.animation = 'shake .2s forwards'
  287. setTimeout(() => {
  288. this.item.style.animation = ''
  289. this.onwheel.flag = false
  290. }, 250)
  291. }
  292. return
  293. }
  294. clearTimeout(this.onwheel.timeout)
  295. if (!this.onwheel.running) {
  296. this.onwheel.running = true
  297. this.transition = this.style.transition
  298. this.style.transition = 'all .1s'
  299. }
  300. this.style[ this.style.left ? 'left' : 'right' ] = limit(
  301. this.style.left ? x - pperc.left * (this.width + coef.x) : window.innerWidth - x - pperc.right * (this.width + coef.x),
  302. this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.width).toFixed(2) + 'px'
  303. this.style[ this.style.top ? 'top' : 'bottom' ] = limit(
  304. this.style.top ? y - pperc.top * (this.height + coef.y) : window.innerHeight - y - pperc.bottom * (this.height + coef.y),
  305. this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.height).toFixed(2) + 'px'
  306. this.style.width = (this.width + coef.x).toFixed(2) + 'px'
  307. this.style.height = (this.height + coef.y).toFixed(2) + 'px'
  308. this.onwheel.timeout = setTimeout(() => {
  309. this.onwheel.running = false
  310. this.style.transition = this.transition
  311. this.transition = ''
  312. }, 100)
  313. }
  314. this.ondblclick = ev => {
  315. [ x, y, delta ] = [ ev.x, ev.y, 0 ]
  316. clearTimeout(this.onwheel?.timeout)
  317. if (this.onwheel && !this.onwheel.running) {
  318. this.onwheel.running = true
  319. this.transition = this.style.transition
  320. this.style.transition = 'all .1s'
  321. }
  322. const pperc = this.pperc
  323. this.style[ this.style.left ? 'left' : 'right' ] = limit(
  324. this.style.left ? x - pperc.left * this.originalWidth : window.innerWidth - x - pperc.right * this.originalWidth,
  325. this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.width).toFixed(2) + 'px'
  326. this.style[ this.style.top ? 'top' : 'bottom' ] = limit(
  327. this.style.top ? y - pperc.top * this.originalHeight : window.innerHeight - y - pperc.bottom * this.originalHeight,
  328. this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.height).toFixed(2) + 'px'
  329. this.style.width = this.originalWidth + 'px'
  330. this.style.height = this.originalHeight + 'px'
  331. this.onwheel && (this.onwheel.timeout = setTimeout(() => {
  332. this.onwheel.running = false
  333. this.style.transition = this.transition
  334. this.transition = ''
  335. }, 100))
  336. }
  337. },
  338. configurable: true
  339. },
  340. makeResizeable: {
  341. value() {
  342. // The method 'makeResizeable' is a once-call-method, once it has been called, it will be removed.
  343. Object.defineProperty(this, 'makeResizeable', { value: void 0 })
  344. const [ rowResizeBar, colResizeBar, nwseResizeBlock ] = [ document.createElement('div-lgx'),
  345. document.createElement('div-lgx'), document.createElement('div-lgx') ]
  346. rowResizeBar.className = 'lgx-resize lgx-row-resize'
  347. colResizeBar.className = 'lgx-resize lgx-col-resize'
  348. nwseResizeBlock.className = 'lgx-resize lgx-nwse-resize'
  349. this.append(rowResizeBar, colResizeBar, nwseResizeBlock)
  350.  
  351. Object.defineProperties(this, {
  352. originalHeight: { value: this.clientHeight },
  353. originalWidth: { value: this.clientWidth }
  354. })
  355.  
  356. rowResizeBar.onmousedown = e => {
  357. ev.stopPropagation()
  358. if (rowResizeBar.ondblclick.flag || e.button !== 0 || this.onwheel?.running) return
  359. rowResizeBar.onmousedown.flag = true
  360. const height = this.clientHeight, s = this.style, t = 'transform'
  361. // 是否上下翻转
  362. rowResizeBar.onmousedown.reversed = (s[t] === 'scaleY(-1)' || s[t] === 'scale(-1)') && (s.scale || 1) > 0
  363.  
  364. const rowResize = ev => {
  365. let h = height + (rowResizeBar.onmousedown.reversed ? e.y - ev.y : ev.y - e.y)
  366.  
  367. if (rowResizeBar.onmousedown.reversed ? h > 0 : h < 0) {
  368. if (this.style.bottom === '') {
  369. s[t] = s[t] === 'scaleX(-1)' ? 'scale(-1)' : 'scaleY(-1)'
  370. this.style.bottom = window.innerHeight - this.offsetTop + 'px'
  371. this.style.top = ''
  372. }
  373. } else {
  374. if (this.style.top === '') {
  375. s[t] = s[t] === 'scale(-1)' ? 'scaleX(-1)' : s[t] === 'scaleY(-1)' ? '' : s[t]
  376. this.style.top = this.offsetTop + this.clientHeight + 'px'
  377. this.style.bottom = ''
  378. }
  379. }
  380.  
  381. if (h > 2) {
  382. this.style.height = h + 'px'
  383. } else if (h >= -2) {
  384. this.style.height = '2px'
  385. } else {
  386. this.style.height = -h + 'px'
  387. }
  388. }, rowRes_ = e => {
  389. if (e.button !== 0) return
  390. styleElement.innerHTML = ''
  391. const t = this.style.transform
  392. rowResizeBar.onmousedown.flag = false
  393. nwseResizeBlock.style.cursor = t === 'scaleX(-1)' || t === 'scaleY(-1)' ? 'nesw-resize' : ''
  394. try {
  395. window.removeEventListener('mousemove', rowResize)
  396. window.removeEventListener('mouseup', rowRes_)
  397. } catch {}
  398. }
  399. styleElement.innerHTML = '*{cursor:row-resize !important}'
  400. window.addEventListener('mousemove', rowResize)
  401. window.addEventListener('mouseup', rowRes_)
  402. }
  403. rowResizeBar.oncontextmenu = ev => {
  404. ev.stopPropagation()
  405. ev.preventDefault()
  406. if (rowResizeBar.oncontextmenu.flag || rowResizeBar.onmousedown.flag || this.onwheel?.running) return
  407. rowResizeBar.oncontextmenu.flag = true
  408.  
  409. const oldT = this.style.transition
  410. if (!ev.transition) {
  411. this.style.transition = 'height .1s ease-out, top .1s ease-out, bottom .1s ease-out'
  412. }
  413. setTimeout(() => {
  414. this.style.transition = oldT
  415. rowResizeBar.oncontextmenu.flag = false
  416. }, 100)
  417.  
  418. this.style.height = this.originalHeight + 'px'
  419. this.style[ this.style.top ? 'top' : 'bottom' ] =
  420. limit(this.style.top ? this.offsetTop : window.innerHeight - this.clientHeight - this.offsetTop,
  421. this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.clientHeight) + 'px'
  422. }
  423. rowResizeBar.ondblclick = ev => {
  424. ev.stopPropagation()
  425. if (rowResizeBar.ondblclick.flag || this.onwheel?.running) return
  426. rowResizeBar.ondblclick.flag = true
  427. const oldT = this.style.transition
  428. if (!ev.transition) {
  429. this.style.transition = 'height .1s ease-out, top .1s ease-out, bottom .1s ease-out'
  430. }
  431. setTimeout(() => {
  432. this.style.transition = oldT
  433. rowResizeBar.ondblclick.flag = false
  434. }, 100)
  435.  
  436. const h = this.style.transform === 'scaleY(-1)' || this.style.transform === 'scale(-1)'
  437. ? this.offsetTop + this.clientHeight : window.innerHeight - this.offsetTop
  438. this.style.height = h + 'px'
  439. this.style[ this.style.top ? 'top' : 'bottom' ] =
  440. limit(this.style.top ? this.offsetTop : window.innerHeight - this.clientHeight - this.offsetTop,
  441. this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.clientHeight) + 'px'
  442. }
  443.  
  444. colResizeBar.onmousedown = e => {
  445. e.stopPropagation()
  446. if (colResizeBar.ondblclick.flag || e.button !== 0 || this.onwheel?.running) return
  447. colResizeBar.onmousedown.flag = true
  448. const width = this.clientWidth, s = this.style, t = 'transform'
  449. // 是否左右翻转
  450. colResizeBar.onmousedown.reversed = (s[t] === 'scaleX(-1)' || s[t] === 'scale(-1)') && (s.scale || 1) > 0
  451.  
  452. const colResize = ev => {
  453. let w = width + (colResizeBar.onmousedown.reversed ? e.x - ev.x : ev.x - e.x)
  454.  
  455. if (colResizeBar.onmousedown.reversed ? w > 0 : w < 0) {
  456. if (this.style.right === '') {
  457. s[t] = s[t] === 'scaleY(-1)' ? 'scale(-1)' : 'scaleX(-1)'
  458. this.style.right = window.innerWidth - this.offsetLeft + 'px'
  459. this.style.left = ''
  460. }
  461. } else {
  462. if (this.style.left === '') {
  463. s[t] = s[t] === 'scale(-1)' ? 'scaleY(-1)' : s[t] === 'scaleX(-1)' ? '' : s[t]
  464. this.style.left = this.offsetLeft + this.clientWidth + 'px'
  465. this.style.right = ''
  466. }
  467. }
  468.  
  469. if (w > 2) {
  470. this.style.width = w + 'px'
  471. } else if (w >= -2) {
  472. this.style.width = '2px'
  473. } else {
  474. this.style.width = -w + 'px'
  475. }
  476.  
  477. }, colRes_ = e => {
  478. if (e.button !== 0) return
  479. styleElement.innerHTML = ''
  480. const t = this.style.transform
  481. colResizeBar.onmousedown.flag = false
  482. nwseResizeBlock.style.cursor = t === 'scaleX(-1)' || t === 'scaleY(-1)' ? 'nesw-resize' : ''
  483. try {
  484. window.removeEventListener('mousemove', colResize)
  485. window.removeEventListener('mouseup', colRes_)
  486. } catch {}
  487. }
  488. styleElement.innerHTML = '*{cursor:col-resize !important}'
  489. window.addEventListener('mousemove', colResize)
  490. window.addEventListener('mouseup', colRes_)
  491. }
  492. colResizeBar.oncontextmenu = ev => {
  493. ev.stopPropagation()
  494. ev.preventDefault()
  495. if (colResizeBar.oncontextmenu.flag || colResizeBar.onmousedown.flag || this.onwheel?.running) return
  496. colResizeBar.oncontextmenu.flag = true
  497.  
  498. const oldT = this.style.transition
  499. if (!ev.transition) {
  500. this.style.transition = 'width .1s ease-out, left .1s ease-out, right .1s ease-out'
  501. }
  502. setTimeout(() => {
  503. this.style.transition = oldT
  504. colResizeBar.oncontextmenu.flag = false
  505. }, 100)
  506.  
  507. this.style.width = this.originalWidth + 'px'
  508. this.style[ this.style.left ? 'left' : 'right' ] =
  509. limit(this.style.left ? this.offsetLeft : window.innerWidth - this.clientWidth - this.offsetLeft,
  510. this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.clientWidth) + 'px'
  511. }
  512. colResizeBar.ondblclick = ev => {
  513. ev.stopPropagation()
  514. if (colResizeBar.ondblclick.flag || this.onwheel?.running) return
  515. colResizeBar.ondblclick.flag = true
  516. const oldT = this.style.transition
  517. if (!ev.transition) {
  518. this.style.transition = 'width .1s ease-out, left .1s ease-out, right .1s ease-out'
  519. }
  520. setTimeout(() => {
  521. this.style.transition = oldT
  522. colResizeBar.ondblclick.flag = false
  523. }, 100)
  524.  
  525. const w = this.style.transform === 'scaleX(-1)' || this.style.transform === 'scale(-1)'
  526. ? this.offsetLeft + this.clientWidth : window.innerWidth - this.offsetLeft
  527. this.style.width = w + 'px'
  528. this.style[ this.style.left ? 'left' : 'right' ] =
  529. limit(this.style.left ? this.offsetLeft : window.innerWidth - this.clientWidth - this.offsetLeft,
  530. this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.clientWidth) + 'px'
  531. }
  532.  
  533. nwseResizeBlock.onmousedown = e => {
  534. rowResizeBar.onmousedown(e)
  535. colResizeBar.onmousedown(e)
  536. const nwseResize = () => {
  537. const t = this.style.transform
  538. styleElement.innerHTML = t === 'scaleX(-1)' || t === 'scaleY(-1)'
  539. ? '*{cursor:nesw-resize !important}' : '*{cursor:nwse-resize !important}'
  540. }, nwseRes_ = () => {
  541. styleElement.innerHTML = ''
  542. try {
  543. window.removeEventListener('mousemove', nwseResize)
  544. window.removeEventListener('mouseup', nwseRes_)
  545. } catch {}
  546. }
  547. nwseResize()
  548. window.addEventListener('mousemove', nwseResize)
  549. window.addEventListener('mouseup', nwseRes_)
  550. }
  551. nwseResizeBlock.oncontextmenu = e => {
  552. const oldT = this.style.transition
  553. e.transition = true
  554. this.style.transition = 'all .1s ease-out'
  555. rowResizeBar.oncontextmenu(e)
  556. colResizeBar.oncontextmenu(e)
  557. setTimeout(() => {
  558. this.style.transition = oldT
  559. }, 100)
  560. }
  561. nwseResizeBlock.ondblclick = e => {
  562. const oldT = this.style.transition
  563. e.transition = true
  564. this.style.transition = 'all .1s ease-out'
  565. rowResizeBar.ondblclick(e)
  566. colResizeBar.ondblclick(e)
  567. setTimeout(() => {
  568. this.style.transition = oldT
  569. }, 100)
  570. }
  571. },
  572. configurable: true
  573. },
  574. makeMoveable: {
  575. value(scope) {
  576. let dx = 0, dy = 0
  577. scope = scope || new Scope({ left: 0, right: () => window.innerWidth - this.width, top: 0, bottom: () => window.innerHeight - this.height });
  578. this.scope = scope
  579. Object.defineProperty(this, 'makeMoveable', { value: void 0 })
  580. const mousemoveF = e => {
  581. this.style[ this.style.left ? 'left' : 'right' ] = limit(this.style.left ? dx + e.x : window.innerWidth - e.x - dx - this.clientWidth, scope.left, scope.right) + 'px'
  582. this.style[ this.style.top ? 'top' : 'bottom' ] = limit(this.style.top ? dy + e.y : window.innerHeight - e.y - dy - this.clientHeight, scope.top, scope.bottom) + 'px'
  583. }, mouseupF = () => {
  584. this.style.cursor = 'grab'
  585. styleElement.innerHTML = ''
  586. try {
  587. window.removeEventListener('mousemove', mousemoveF)
  588. } catch {}
  589. }
  590. this.onmousedown = ev => {
  591. this.parentElement.appendChild(this)
  592. if (ev.buttons !== 1) return
  593. styleElement.innerHTML = '*{cursor:grabbing !important}';
  594. [ dx, dy ] = [ this.offsetLeft - ev.x, this.offsetTop - ev.y ]
  595. this.style.cursor = 'grabbing'
  596. window.addEventListener('mousemove', mousemoveF)
  597. window.addEventListener('mouseup', mouseupF, { once: true })
  598. }
  599. this.oncontextmenu = e => {
  600. e.preventDefault()
  601. this.oncontextmenu = e => e.preventDefault()
  602. mouseupF()
  603. this.style.transition = 'all .2s cubic-bezier(.68,-0.55,.5,.5)'
  604. this.style.scale = (this.style.scale || 1) / 2 + ''
  605. this.style.opacity = '0'
  606. setTimeout(() => this.remove(), 250)
  607. }
  608. },
  609. configurable: true
  610. }
  611. })
  612.  
  613. class Scope {
  614. constructor(o) {
  615. const props = {}, mapper = { 'function': 'get', 'number': 'value' };
  616. [ 'left', 'right', 'top', 'bottom' ].forEach(i => Object.keys(mapper)
  617. .includes(typeof o?.[i]) && (props[i] = { [ mapper[typeof o[i]] ]: o[i] }))
  618. Object.defineProperties(this, props)
  619. }
  620. }
  621.  
  622. // noinspection JSUnusedGlobalSymbols
  623. class ItemCreater {
  624.  
  625. image(x, y, data) {
  626. const img = document.createElement('img'),
  627. div = document.createElement('div-lgx')
  628. img.classList.add('lgx-float-item')
  629. div.classList.add('lgx-float-div')
  630. div.appendChild(img)
  631.  
  632. let w, h
  633. img.draggable = false
  634. console.log(data)
  635. img.src = typeof data === 'string' ? data : URL.createObjectURL(data)
  636. img.onload = () => {
  637. // init
  638. [w, h] = [img.width, img.height];
  639. [div.style.left, div.style.top, div.style.width, div.style.height] =
  640. [x - w / 4 + 'px', y - h / 4 + 'px', w / 2 + 'px', h / 2 + 'px']
  641. div.style.transition = 'all .3s cubic-bezier(.5,.5,.27,1.55)'
  642. setTimeout(() => {
  643. [div.style.width, div.style.height] = [w + 'px', h + 'px']
  644. div.style.left = limit(0, x - w / 2, window.innerWidth - w) + 'px'
  645. div.style.top = limit(0, y - h / 2, window.innerHeight - h) + 'px'
  646. div.style.opacity = '1'
  647. }, 50)
  648. // add event handler
  649. setTimeout(() => {
  650. const scope = new Scope({
  651. left: () => -div.width / 2,
  652. top: () => -div.height / 2,
  653. right: () => window.innerWidth - div.width / 2,
  654. bottom: () => window.innerHeight - div.height / 2
  655. })
  656. div.style.transition = ''
  657. div.makeScaleable()
  658. div.makeResizeable()
  659. div.makeMoveable(scope)
  660. }, 400)
  661. }
  662. return div
  663. }
  664.  
  665. audio() {
  666.  
  667. }
  668.  
  669. video() {
  670.  
  671. }
  672.  
  673. text() {
  674.  
  675. }
  676.  
  677. file() {
  678.  
  679. }
  680.  
  681. link(x, y, url) {
  682. console.log(url)
  683. }
  684.  
  685. contentmenu(x, y, handlers) {
  686. const menu = document.createElement('div-lgx');
  687. Object.defineProperty(menu, 'close', {
  688. value(ev) {
  689. function targetInMenu(target) {
  690. if (!target) return false;
  691. if (target === menu) return true;
  692. return targetInMenu(target.parentElement);
  693. }
  694. if (targetInMenu(ev?.target) && this !== menu) return false;
  695.  
  696. [ [ menu, 'click' ], [ window, 'mousedown' ], [ window, 'keydown' ] ].forEach(ls => {
  697. try { ls[0].removeEventListener(ls[1], menu.close) } catch {} });
  698. menu.style.opacity = '.5';
  699. menu.style.top = y + 20 + 'px';
  700. setTimeout(() => menu.remove(), 100);
  701. return true;
  702. }
  703. });
  704. menu.className = 'lgx-floating-menu';
  705. menu.style = `left:${x + 110 > window.innerWidth ? x - 110 : x}px;top:${y - 20}px`;
  706. setTimeout(() => {
  707. menu.style.opacity = '1';
  708. menu.style.top = y + 'px';
  709. }, 100);
  710. menu.innerHTML = '<ul>' + Object.keys(handlers).map(h => h !== '未知类型' ? `<li>${h}</li>` :
  711. `<li title="由于同源策略禁止读取该文件所在地址的远程资源,无法得知该文件的类型">${h}</li>`).join('') + '</ul>';
  712. [ ...menu.children[0].children ].forEach(c => (c.onclick = handlers[c.innerText]));
  713. menu.addEventListener("click", menu.close);
  714. window.addEventListener("mousedown", menu.close);
  715. window.addEventListener("keydown", menu.close);
  716. floatingContainer.appendChild(menu);
  717. }
  718.  
  719. }
  720.  
  721. const creater = new ItemCreater
  722.  
  723. Object.defineProperty(DataTransfer.prototype, 'toElements', {
  724. value({ x, y }) {
  725. const retArr = []
  726. if (this.files.length) {
  727. if (this.files.length > 20) {
  728. return Promise.reject('There are too many files (> 20)')
  729. }
  730. for (const f of this.files) {
  731. console.log(f.name, f.type, URL.createObjectURL(f))
  732. const item = creater[f.type.substring(0, f.type.indexOf('/'))]?.(x, y, f)
  733. item && retArr.push(item)
  734. }
  735. return Promise.resolve(retArr)
  736. } else {
  737. return new Promise(resolve => {
  738.  
  739. function getWebFileType(url) {
  740. if (!url) return null;
  741. return new Promise((res, rej) => {
  742. try {
  743. const req = new XMLHttpRequest();
  744. req.open('GET', url, false);
  745. req.send(null);
  746. res(req.getResponseHeader('content-type'));
  747. } catch {
  748. // inference file type image
  749. const img = new Image();
  750. img.src = url;
  751. img.crossorigin = "Anonymous";
  752. img.style = 'position:absolute;left:99999px;top:999999px';
  753. img.onerror = () => { rej(); img.remove() };
  754. img.onload = () => { res('image/'); img.remove() };
  755. document.body.appendChild(img);
  756. }
  757. });
  758. }
  759.  
  760. let contentTypeStart;
  761. const that = this,
  762. url = that.getData("application/x-moz-file-promise-url").replace('http:', 'https:')
  763. || that.getData("text/x-moz-url-data").replace('http:', 'https:');
  764. // console.log(that.types.reduce((a, b) => (a[b] = that.getData(b)) ?? a, {}));
  765. function handleFile() {
  766. console.log('handle file', contentTypeStart);
  767. const elem = creater[contentTypeStart]?.(x, y, url);
  768. resolve(elem ? [ elem ] : []);
  769. }
  770.  
  771. function handleLink() {
  772. console.log('handle link')
  773. console.log(that)
  774. const url = that.getData("text/x-moz-url-data").replace('http:', 'https:'),
  775. elem = creater.link(x, y, url)
  776. resolve(elem ? [ elem ] : []);
  777. }
  778.  
  779. getWebFileType(url).then(contentType => {
  780.  
  781. contentTypeStart = contentType?.substring(0, contentType.indexOf('/')) ?? 'file';
  782. const flagFile = this.types.includes("application/x-moz-file-promise-url"),
  783. flagLink = that.types.includes("text/x-moz-url-data"),
  784. type = { 'image': '图片', 'audio': '音频', 'vedio': '视频', 'text': '文本' }[contentTypeStart]
  785. || '未知类型';
  786.  
  787. if (flagFile && flagLink) {
  788. handleFile();
  789. return;
  790. creater.contentmenu(x, y, { [type]: handleFile, '链接': handleLink });
  791. } else if (flagFile) {
  792. handleFile();
  793. } else {
  794. handleLink();
  795. }
  796. });
  797. });
  798.  
  799. }
  800. },
  801. })
  802.  
  803. window.addEventListener('drop', ev => {
  804. ev.preventDefault()
  805. ev.dataTransfer.toElements(ev).then(
  806. elements => elements.forEach(el => floatingContainer.appendChild(el)))
  807. })
  808.  
  809. window.addEventListener('resize', () => {
  810. for (const div of floatingContainer.children) {
  811. div.style[ div.style.left ? 'left' : 'right' ] =
  812. limit(div.style.left ? div.offsetLeft : window.innerWidth - div.clientWidth - div.offsetLeft,
  813. div.scope?.left ?? 0, div.scope?.right ?? window.innerWidth - div.clientWidth) + 'px'
  814. div.style[ div.style.top ? 'top' : 'bottom' ] =
  815. limit(div.style.top ? div.offsetTop : window.innerHeight - div.clientHeight - div.offsetTop,
  816. div.scope?.top ?? 0, div.scope?.bottom ?? window.innerHeight - div.clientHeight) + 'px'
  817. }
  818. })
  819.  
  820. function limit(value, min, max) {
  821. return Math.min(Math.max(value, min), max)
  822. }
  823.  
  824. let flagI = 1;
  825. setInterval(() => {
  826. if (document.body.lastChild !== ctrlContainer) {
  827. document.body.appendChild(ctrlContainer);
  828. document.body.insertBefore(floatingContainer, ctrlContainer)
  829. if (k) {
  830. document.body.insertBefore(that, ctrlContainer)
  831. }
  832. flagI = Math.max(100, flagI + 1)
  833. }
  834. }, 10000 / flagI)
  835. const lgxBlocks = [ ctrl, that, ctrlContainer, floatingContainer, document.body ];
  836. lgxBlocks.forEach(i => i.classList.add('undeletable'));
  837.  
  838. })();