Greasy Fork

lgx-tools

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

当前为 2023-03-14 提交的版本,查看 最新版本

    // ==UserScript==
    // @name         lgx-tools
    // @namespace    http://tampermonkey.net/
    // @version      0.2.3
    // @description  允许删除页面的元素,往页面上放置项目
    // @author       You
    // @match        *://*/*
    // @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
    // @grant        none
    // @license      MIT
    // ==/UserScript==
    // noinspection SpellCheckingInspection

    (function() {
        'use strict';

        // Your code here...
        if (!document.body) return
        let styleElement = document.createElement('style')
        styleElement.innerHTML = '.lgx-front-hover-block{position:fixed;background:#09f3;border:dashed 1px #06f9;trans' +
            'ition:all .3s cubic-bezier(.68,-0.55,.27,1.55);z-index:2147483647}.lgx-front-hover-block>div{position:abs' +
            'olute;border:1px dashed #f11a;background:#f553;transition:inherit}.lgx-front-hover-block>div:hover{backgr' +
            'ound:#f556}.lgx-ctrl-block{width:6px;height:6px;position:fixed;background:red;bottom:-3px;left:-3px;rotat' +
            'e:45deg;z-index:2147483647;cursor:pointer}.lgx-front-hover-no-transition,.lgx-front-hover-no-transition>*' +
            '{transition:none !important}@keyframes shake{0%{transform:translateX(0)}10%{transform:translateX(-10px)}2' +
            '0%{transform:translateX(10px)}35%{transform:translateX(-10px)}55%{transform:translateX(10px)}70%{transfor' +
            'm:translateX(-10px)}100%{transform:translateX(0)}}.lgx-float-div{position:fixed;opacity:.5;border:none;cu' +
            'rsor:grab;z-index:2147483647;user-select:none}.lgx-float-div>.lgx-resize{content:"";display:block;positio' +
            'n:absolute}.lgx-float-div>*{user-select:none}.lgx-float-div>.lgx-col-resize{width:6px;height:calc(100% - ' +
            '6px);right:-3px;top:0;cursor:col-resize}.lgx-float-div>.lgx-row-resize{width:calc(100% - 6px);height:6px;' +
            'left:0;bottom:-3px;cursor:row-resize}.lgx-float-div>.lgx-nwse-resize{width:12px;height:12px;right:-6px;bo' +
            'ttom:-6px;cursor:nwse-resize}.lgx-float-div>.lgx-float-item{box-shadow:0 0 5px #0005;width:100%;height:10' +
            '0%;display:block}.lgx-floating-menu{transition:all .1s;width:100px;padding:5px;opacity:.5;border-radius:5' +
            'px;box-shadow:2px 2px 5px #0005;position:fixed;z-index:2147483647;background-color:#fff;user-select:none}' +
            '.lgx-floating-menu li:hover{background-color:#f2f2f2}.lgx-removed{display:none !important}'
        document.head.appendChild(styleElement)
        styleElement = document.createElement('style')
        document.head.appendChild(styleElement)

        let el, k = !1, x = 0, y = 0
        const that = document.createElement("div")
        const ctrl = document.createElement('div')
        let removedElements = [], removedIndex = 0
        ctrl.className = 'lgx-ctrl-block'
        that.className = 'lgx-front-hover-block'

        function mouseMoveHandler(ev) {
            if (that === ev.target) {
                clearTimeout(mouseMoveHandler.timeout0)
                return
            }
            // if (el === (ev.target?.mock || ev.target)) return
            el = ev.target?.mock || ev.target

            that.classList[ ev.shiftKey ? 'add' : 'remove' ]('lgx-front-hover-no-transition')
            const fun1 = () => {
                for (const {style: s, l0, t0} of that.children) {
                    [s.left, s.top, s.width, s.height] = [l0, t0, 0, 0].map(j => j.px)
                }
                const fun2 = () => {
                    const {left: l0, top: t0, height: h, width: w} =
                    el?.getBoundingClientRect() ?? {left: 0, top: 0, height: 0, width: 0}, s = that.style, arr = []
                    const fun3 = () => {
                        for (const i of el?.children ?? []) {
                            if (i === that || i === ctrl) continue
                            const {left: l, top: t, right: r, bottom: b, height: h, width: w} = i.getBoundingClientRect()
                            const [dh, dw] = [document.documentElement.clientHeight, document.documentElement.clientWidth]
                            if (b < 0 || t > dh || l > dw || r < 0) continue
                            const d = document.createElement('div');
                            [d.mock, d.l0, d.t0, d.l, d.t, d.w, d.h] =
                                [i, l + w / 2 - l0, t + h / 2 - t0, l - l0, t - t0, w - 2, h - 2]
                            d.style = `left:${d.l0}px;top:${d.t0}px;width:0;height:0`
                            that.appendChild(d)
                            arr.push(d)
                        }
                        const fun4 = () => {
                            for (const d of arr) {
                                [d.style.left, d.style.top, d.style.height, d.style.width] =
                                    [d.l, d.t, d.h, d.w].map(i => i.px)
                            }
                        }
                        ev.shiftKey ? fun4() : setTimeout(fun4, 30)
                    }
                    [s.left, s.top, s.height, s.width] = [l0, t0, h - 2, w - 2].map(i => i.px)
                    that.innerHTML = ''
                    if (ev.shiftKey) {
                        fun3()
                    } else {
                        clearTimeout(that.timeout)
                        that.timeout = setTimeout(fun3, 300)
                    }
                }

                if (ev.shiftKey) {
                    fun2()
                } else {
                    clearTimeout(mouseMoveHandler.timeout)
                    mouseMoveHandler.timeout = setTimeout(fun2, 300)
                }
            }
            if (ev.shiftKey) {
                fun1()
            } else {
                clearTimeout(mouseMoveHandler.timeout0)
                mouseMoveHandler.timeout0 = setTimeout(fun1, ev.target?.mock ? 500 : 0)
            }
        }

        ctrl.onclick = () => {
            k = !k
            ctrl.style.background = k ? 'green' : 'red'
            if (k) {
                document.body?.addEventListener('mousemove', mouseMoveHandler)
                that.style.left = x + 'px'
                that.style.top = y + 'px'
                that.style.width = '0px'
                that.style.height = '0px'
                that.innerHTML = ''
                document.body?.insertBefore(that, ctrl)
                setTimeout(mouseMoveHandler, 20, new Event('mousemove'))
            } else {
                el = void 0
                that.remove()
                try { document.body?.removeEventListener('mousemove', mouseMoveHandler) } catch {}
            }
        }
        document.body.appendChild(ctrl)

        ctrl.addEventListener('contextmenu', ev => { ev.preventDefault(); console.clear() });

        window.addEventListener('mousemove', ev => {
            [x, y] = [ev.x, ev.y]
        })

        window.addEventListener('keydown', ev => {
            if (ev.key === 'Control') {
                that.style.display = 'none'
            }
        })

        window.addEventListener('keyup', ev => {
            if (ev.key === 'Control') {
                that.style.display = 'block'
            }
        })

        window.addEventListener('click', ev => {
            if (el === ctrl || el === document.body) return
            if (!ev.ctrlKey && k && el) {
                el.classList.add('lgx-removed')
                if (removedIndex < removedElements.length) {
                    removedElements = removedElements.slice(0, removedIndex)
                }
                removedElements.push(el)
                removedIndex++
            }
            that.style.left = x + 'px'
            that.style.top = y + 'px'
            that.style.width = '0px'
            that.style.height = '0px'
            that.innerHTML = ''
        })

        ctrl.addEventListener('wheel', ev => {
            ev.preventDefault()
            if (ev.deltaY > 0 && removedIndex < removedElements.length) {
                removedElements[removedIndex++].classList.add('lgx-removed')
            }
            if (ev.deltaY < 0 && removedIndex > 0) {
                removedElements[--removedIndex].classList.remove('lgx-removed')
            }
        });

        // !--- img dropping handler ---! //

        ['drop', 'dragleave', 'dragover', 'dragenter'].forEach(
            i => document.addEventListener(i, e => e.preventDefault())
        )

        const floatingContainer = document.createElement('div')
        document.body.insertBefore(floatingContainer, ctrl)

        Array( Number, String ).forEach(
            i => Object.defineProperty(i.prototype, 'px', {
                get() { return this + 'px' }
            })
        )

        Object.defineProperties(HTMLDivElement.prototype, {
            timeout: { value: 0, writable: true },
            width:  { get() { return parseFloat(this.style.width ) || this.clientWidth } },
            height: { get() { return parseFloat(this.style.height) || this.clientHeight } },
            left:   { get() { return parseFloat(this.style. left ) || window.innerWidth - parseFloat(this.style.width) - parseFloat(this.style.right) || this.offsetLeft } },
            right:  { get() { return parseFloat(this.style.right ) || window.innerWidth - parseFloat(this.style.width) - parseFloat(this.style.left) || window.innerWidth - this.clientWidth - this.offsetLeft } },
            top:    { get() { return parseFloat(this. style. top ) || window.innerHeight - parseFloat(this.style.height) - parseFloat(this.style.bottom) || this.offsetTop } },
            bottom: { get() { return parseFloat(this.style.bottom) || window.innerHeight - parseFloat(this.style.height) - parseFloat(this.style.top) || window.innerHeight - this.offsetTop - this.clientHeight } },
            item:   { get() {
                    const value = this.querySelector('.lgx-float-item')
                    Object.defineProperty(this, 'item', { value })
                    return value
                }, configurable: true },
            makeScaleable: {
                value() {
                    let x, y, delta
                    Object.defineProperties(this, {
                        // The method 'makeScaleable' is a once-call-method, once it has been called, it will be removed.
                        makeScaleable: { value: void 0 },
                        // scalingCoef: scaling coefficient
                        // The pointer closer to the center of this div element, the coefficient bigger.
                        scalingCoef: {
                            get() {
                                const d = Math.sqrt(
                                    Math.pow(x - this.left - this.width / 2, 2)
                                    + Math.pow(y - this.top - this.height / 2, 2)
                                )
                                const h = this.height, w = this.width
                                const r = Math.sqrt(h ** 2 + w ** 2) / 2
                                const v = (1 - d / r) * .2
                                return { x: w * v * delta, y: h * v * delta }
                            }
                        },
                        // pperc: the percentage of the location of the pointer in this div element
                        // e.g. coordinate of the pointer in this div is (2, 3), and the size of this div is (5, 6),
                        //      then the percentage (pperc) is { left: 2 / 5, right: 3 / 5, top: 1 / 2, bottom: 1 / 2 }
                        pperc: {
                            get() {
                                const l = (x - this.left) / this.width,
                                    t = (y - this.top) / this.height
                                return { left: l, right: 1 - l, top: t, bottom: 1 - t }
                            }
                        }
                    })
                    this.onwheel = ev => {
                        [ x, y, delta ] = [ ev.x, ev.y, -ev.deltaY / 114 ]
                        ev.preventDefault()
                        const coef = this.scalingCoef, pperc = this.pperc
                        const nw = this.width + coef.x, nh = this.height + coef.y
                        if (Math.max(nw, nh) > 3000 || Math.min(nw, nh) < 20) {
                            if (!this.onwheel.flag) {
                                this.onwheel.flag = true
                                this.item.style.animation = 'shake .2s forwards'
                                setTimeout(() => {
                                    this.item.style.animation = ''
                                    this.onwheel.flag = false
                                }, 250)
                            }
                            return
                        }
                        clearTimeout(this.onwheel.timeout)
                        if (!this.onwheel.running) {
                            this.onwheel.running = true
                            this.transition = this.style.transition
                            this.style.transition = 'all .1s'
                        }
                        this.style[ this.style.left ? 'left' : 'right' ] = limit(
                            this.style.left ? x - pperc.left * (this.width + coef.x) : window.innerWidth - x - pperc.right * (this.width + coef.x),
                            this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.width).toFixed(2) + 'px'
                        this.style[ this.style.top ? 'top' : 'bottom' ] = limit(
                            this.style.top ? y - pperc.top * (this.height + coef.y) : window.innerHeight - y - pperc.bottom * (this.height + coef.y),
                            this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.height).toFixed(2) + 'px'
                        this.style.width = (this.width + coef.x).toFixed(2) + 'px'
                        this.style.height = (this.height + coef.y).toFixed(2) + 'px'
                        this.onwheel.timeout = setTimeout(() => {
                            this.onwheel.running = false
                            this.style.transition = this.transition
                            this.transition = ''
                        }, 100)
                    }
                    this.ondblclick = ev => {
                        [ x, y, delta ] = [ ev.x, ev.y, 0 ]
                        clearTimeout(this.onwheel?.timeout)
                        if (this.onwheel && !this.onwheel.running) {
                            this.onwheel.running = true
                            this.transition = this.style.transition
                            this.style.transition = 'all .1s'
                        }
                        const pperc = this.pperc
                        this.style[ this.style.left ? 'left' : 'right' ] = limit(
                            this.style.left ? x - pperc.left * this.originalWidth : window.innerWidth - x - pperc.right * this.originalWidth,
                            this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.width).toFixed(2) + 'px'
                        this.style[ this.style.top ? 'top' : 'bottom' ] = limit(
                            this.style.top ? y - pperc.top * this.originalHeight : window.innerHeight - y - pperc.bottom * this.originalHeight,
                            this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.height).toFixed(2) + 'px'
                        this.style.width = this.originalWidth + 'px'
                        this.style.height = this.originalHeight + 'px'
                        this.onwheel && (this.onwheel.timeout = setTimeout(() => {
                            this.onwheel.running = false
                            this.style.transition = this.transition
                            this.transition = ''
                        }, 100))
                    }
                },
                configurable: true
            },
            makeResizeable: {
                value() {
                    // The method 'makeResizeable' is a once-call-method, once it has been called, it will be removed.
                    Object.defineProperty(this, 'makeResizeable', { value: void 0 })
                    const [ rowResizeBar, colResizeBar, nwseResizeBlock ] = [ document.createElement('div'),
                        document.createElement('div'), document.createElement('div') ]
                    rowResizeBar.className = 'lgx-resize lgx-row-resize'
                    colResizeBar.className = 'lgx-resize lgx-col-resize'
                    nwseResizeBlock.className = 'lgx-resize lgx-nwse-resize'
                    this.append(rowResizeBar, colResizeBar, nwseResizeBlock)

                    Object.defineProperties(this, {
                        originalHeight: { value: this.clientHeight },
                        originalWidth: { value: this.clientWidth }
                    })

                    rowResizeBar.onmousedown = e => {
                        e.cancelBubble = true
                        if (rowResizeBar.ondblclick.flag || e.button !== 0 || this.onwheel?.running) return
                        rowResizeBar.onmousedown.flag = true
                        const height = this.clientHeight, s = this.style, t = 'transform'
                        // 是否上下翻转
                        rowResizeBar.onmousedown.reversed = (s[t] === 'scaleY(-1)' || s[t] === 'scale(-1)') && (s.scale || 1) > 0

                        const rowResize = ev => {
                            let h = height + (rowResizeBar.onmousedown.reversed ? e.y - ev.y : ev.y - e.y)

                            if (rowResizeBar.onmousedown.reversed ? h > 0 : h < 0) {
                                if (this.style.bottom === '') {
                                    s[t] = s[t] === 'scaleX(-1)' ? 'scale(-1)' : 'scaleY(-1)'
                                    this.style.bottom = window.innerHeight - this.offsetTop + 'px'
                                    this.style.top = ''
                                }
                            } else {
                                if (this.style.top === '') {
                                    s[t] = s[t] === 'scale(-1)' ? 'scaleX(-1)' : s[t] === 'scaleY(-1)' ? '' : s[t]
                                    this.style.top = this.offsetTop + this.clientHeight + 'px'
                                    this.style.bottom = ''
                                }
                            }

                            if (h > 2) {
                                this.style.height = h + 'px'
                            } else if (h >= -2) {
                                this.style.height = '2px'
                            } else {
                                this.style.height = -h + 'px'
                            }
                        }, rowRes_ = e => {
                            if (e.button !== 0) return
                            styleElement.innerHTML = ''
                            const t = this.style.transform
                            rowResizeBar.onmousedown.flag = false
                            nwseResizeBlock.style.cursor = t === 'scaleX(-1)' || t === 'scaleY(-1)' ? 'nesw-resize' : ''
                            try {
                                window.removeEventListener('mousemove', rowResize)
                                window.removeEventListener('mouseup', rowRes_)
                            } catch {}
                        }
                        styleElement.innerHTML = '*{cursor:row-resize !important}'
                        window.addEventListener('mousemove', rowResize)
                        window.addEventListener('mouseup', rowRes_)
                    }
                    rowResizeBar.oncontextmenu = ev => {
                        ev.cancelBubble = true
                        ev.preventDefault()
                        if (rowResizeBar.oncontextmenu.flag || rowResizeBar.onmousedown.flag || this.onwheel?.running) return
                        rowResizeBar.oncontextmenu.flag = true

                        const oldT = this.style.transition
                        if (!ev.transition) {
                            this.style.transition = 'height .1s ease-out, top .1s ease-out, bottom .1s ease-out'
                        }
                        setTimeout(() => {
                            this.style.transition = oldT
                            rowResizeBar.oncontextmenu.flag = false
                        }, 100)

                        this.style.height = this.originalHeight + 'px'
                        this.style[ this.style.top ? 'top' : 'bottom' ] =
                            limit(this.style.top ? this.offsetTop : window.innerHeight - this.clientHeight - this.offsetTop,
                                this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.clientHeight) + 'px'
                    }
                    rowResizeBar.ondblclick = ev => {
                        ev.cancelBubble = true
                        if (rowResizeBar.ondblclick.flag || this.onwheel?.running) return
                        rowResizeBar.ondblclick.flag = true
                        const oldT = this.style.transition
                        if (!ev.transition) {
                            this.style.transition = 'height .1s ease-out, top .1s ease-out, bottom .1s ease-out'
                        }
                        setTimeout(() => {
                            this.style.transition = oldT
                            rowResizeBar.ondblclick.flag = false
                        }, 100)

                        const h = this.style.transform === 'scaleY(-1)' || this.style.transform === 'scale(-1)'
                            ? this.offsetTop + this.clientHeight : window.innerHeight - this.offsetTop
                        this.style.height = h + 'px'
                        this.style[ this.style.top ? 'top' : 'bottom' ] =
                            limit(this.style.top ? this.offsetTop : window.innerHeight - this.clientHeight - this.offsetTop,
                                this.scope?.top ?? 0, this.scope?.bottom ?? window.innerHeight - this.clientHeight) + 'px'
                    }

                    colResizeBar.onmousedown = e => {
                        e.cancelBubble = true
                        if (colResizeBar.ondblclick.flag || e.button !== 0 || this.onwheel?.running) return
                        colResizeBar.onmousedown.flag = true
                        const width = this.clientWidth, s = this.style, t = 'transform'
                        // 是否左右翻转
                        colResizeBar.onmousedown.reversed = (s[t] === 'scaleX(-1)' || s[t] === 'scale(-1)') && (s.scale || 1) > 0

                        const colResize = ev => {
                            let w = width + (colResizeBar.onmousedown.reversed ? e.x - ev.x : ev.x - e.x)

                            if (colResizeBar.onmousedown.reversed ? w > 0 : w < 0) {
                                if (this.style.right === '') {
                                    s[t] = s[t] === 'scaleY(-1)' ? 'scale(-1)' : 'scaleX(-1)'
                                    this.style.right = window.innerWidth - this.offsetLeft + 'px'
                                    this.style.left = ''
                                }
                            } else {
                                if (this.style.left === '') {
                                    s[t] = s[t] === 'scale(-1)' ? 'scaleY(-1)' : s[t] === 'scaleX(-1)' ? '' : s[t]
                                    this.style.left = this.offsetLeft + this.clientWidth + 'px'
                                    this.style.right = ''
                                }
                            }

                            if (w > 2) {
                                this.style.width = w + 'px'
                            } else if (w >= -2) {
                                this.style.width = '2px'
                            } else {
                                this.style.width = -w + 'px'
                            }

                        }, colRes_ = e => {
                            if (e.button !== 0) return
                            styleElement.innerHTML = ''
                            const t = this.style.transform
                            colResizeBar.onmousedown.flag = false
                            nwseResizeBlock.style.cursor = t === 'scaleX(-1)' || t === 'scaleY(-1)' ? 'nesw-resize' : ''
                            try {
                                window.removeEventListener('mousemove', colResize)
                                window.removeEventListener('mouseup', colRes_)
                            } catch {}
                        }
                        styleElement.innerHTML = '*{cursor:col-resize !important}'
                        window.addEventListener('mousemove', colResize)
                        window.addEventListener('mouseup', colRes_)
                    }
                    colResizeBar.oncontextmenu = ev => {
                        ev.cancelBubble = true
                        ev.preventDefault()
                        if (colResizeBar.oncontextmenu.flag || colResizeBar.onmousedown.flag || this.onwheel?.running) return
                        colResizeBar.oncontextmenu.flag = true

                        const oldT = this.style.transition
                        if (!ev.transition) {
                            this.style.transition = 'width .1s ease-out, left .1s ease-out, right .1s ease-out'
                        }
                        setTimeout(() => {
                            this.style.transition = oldT
                            colResizeBar.oncontextmenu.flag = false
                        }, 100)

                        this.style.width = this.originalWidth + 'px'
                        this.style[ this.style.left ? 'left' : 'right' ] =
                            limit(this.style.left ? this.offsetLeft : window.innerWidth - this.clientWidth - this.offsetLeft,
                                this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.clientWidth) + 'px'
                    }
                    colResizeBar.ondblclick = ev => {
                        ev.cancelBubble = true
                        if (colResizeBar.ondblclick.flag || this.onwheel?.running) return
                        colResizeBar.ondblclick.flag = true
                        const oldT = this.style.transition
                        if (!ev.transition) {
                            this.style.transition = 'width .1s ease-out, left .1s ease-out, right .1s ease-out'
                        }
                        setTimeout(() => {
                            this.style.transition = oldT
                            colResizeBar.ondblclick.flag = false
                        }, 100)

                        const w = this.style.transform === 'scaleX(-1)' || this.style.transform === 'scale(-1)'
                            ? this.offsetLeft + this.clientWidth : window.innerWidth - this.offsetLeft
                        this.style.width = w + 'px'
                        this.style[ this.style.left ? 'left' : 'right' ] =
                            limit(this.style.left ? this.offsetLeft : window.innerWidth - this.clientWidth - this.offsetLeft,
                                this.scope?.left ?? 0, this.scope?.right ?? window.innerWidth - this.clientWidth) + 'px'
                    }

                    nwseResizeBlock.onmousedown = e => {
                        rowResizeBar.onmousedown(e)
                        colResizeBar.onmousedown(e)
                        const nwseResize = () => {
                            const t = this.style.transform
                            styleElement.innerHTML = t === 'scaleX(-1)' || t === 'scaleY(-1)'
                                ? '*{cursor:nesw-resize !important}' : '*{cursor:nwse-resize !important}'
                        }, nwseRes_ = () => {
                            styleElement.innerHTML = ''
                            try {
                                window.removeEventListener('mousemove', nwseResize)
                                window.removeEventListener('mouseup', nwseRes_)
                            } catch {}
                        }
                        nwseResize()
                        window.addEventListener('mousemove', nwseResize)
                        window.addEventListener('mouseup', nwseRes_)
                    }
                    nwseResizeBlock.oncontextmenu = e => {
                        const oldT = this.style.transition
                        e.transition = true
                        this.style.transition = 'all .1s ease-out'
                        rowResizeBar.oncontextmenu(e)
                        colResizeBar.oncontextmenu(e)
                        setTimeout(() => {
                            this.style.transition = oldT
                        }, 100)
                    }
                    nwseResizeBlock.ondblclick = e => {
                        const oldT = this.style.transition
                        e.transition = true
                        this.style.transition = 'all .1s ease-out'
                        rowResizeBar.ondblclick(e)
                        colResizeBar.ondblclick(e)
                        setTimeout(() => {
                            this.style.transition = oldT
                        }, 100)
                    }
                },
                configurable: true
            },
            makeMoveable: {
                value(scope) {
                    let dx = 0, dy = 0
                    scope = scope || new Scope({ left: 0, right: () => window.innerWidth - this.width, top: 0, bottom: () => window.innerHeight - this.height });
                    this.scope = scope
                    Object.defineProperty(this, 'makeMoveable', { value: void 0 })
                    const mousemoveF = e => {
                        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'
                        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'
                    }, mouseupF = () => {
                        this.style.cursor = 'grab'
                        styleElement.innerHTML = ''
                        try {
                            window.removeEventListener('mousemove', mousemoveF)
                        } catch {}
                    }
                    this.onmousedown = ev => {
                        this.parentElement.appendChild(this)
                        if (ev.buttons !== 1) return
                        styleElement.innerHTML = '*{cursor:grabbing !important}';
                        [ dx, dy ] = [ this.offsetLeft - ev.x, this.offsetTop - ev.y ]
                        this.style.cursor = 'grabbing'
                        window.addEventListener('mousemove', mousemoveF)
                        window.addEventListener('mouseup', mouseupF, { once: true })
                    }
                    this.oncontextmenu = e => {
                        e.preventDefault()
                        this.oncontextmenu = e => e.preventDefault()
                        mouseupF()
                        this.style.transition = 'all .2s cubic-bezier(.68,-0.55,.5,.5)'
                        this.style.scale = (this.style.scale || 1) / 2 + ''
                        this.style.opacity = '0'
                        setTimeout(() => this.remove(), 250)
                    }
                },
                configurable: true
            }
        })

        class Scope {
            constructor(o) {
                const props = {}, mapper = { 'function': 'get', 'number': 'value' };
                [ 'left', 'right', 'top', 'bottom' ].forEach(i => Object.keys(mapper)
                    .includes(typeof o?.[i]) && (props[i] = { [ mapper[typeof o[i]] ]: o[i] }))
                Object.defineProperties(this, props)
            }
        }

        // noinspection JSUnusedGlobalSymbols
        class ItemCreater {

            image(x, y, data) {
                const img = document.createElement('img'),
                    div = document.createElement('div')
                img.classList.add('lgx-float-item')
                div.classList.add('lgx-float-div')
                div.appendChild(img)

                let w, h
                img.draggable = false
                img.src = typeof data === 'string' ? data : URL.createObjectURL(data)
                img.onload = () => {
                    // init
                    [w, h] = [img.width, img.height];
                    [div.style.left, div.style.top, div.style.width, div.style.height] =
                        [x - w / 4 + 'px', y - h / 4 + 'px', w / 2 + 'px', h / 2 + 'px']
                    div.style.transition = 'all .3s cubic-bezier(.5,.5,.27,1.55)'
                    setTimeout(() => {
                        [div.style.width, div.style.height] = [w + 'px', h + 'px']
                        div.style.left = limit(0, x - w / 2, window.innerWidth - w) + 'px'
                        div.style.top = limit(0, y - h / 2, window.innerHeight - h) + 'px'
                        div.style.opacity = '1'
                    }, 50)
                    // add event handler
                    setTimeout(() => {
                        const scope = new Scope({
                            left:   () => -div.width / 2,
                            top:    () => -div.height / 2,
                            right:  () => window.innerWidth - div.width / 2,
                            bottom: () => window.innerHeight - div.height / 2
                        })
                        div.style.transition = ''
                        div.makeScaleable()
                        div.makeResizeable()
                        div.makeMoveable(scope)
                    }, 400)
                }
                return div
            }

            audio() {

            }

            video() {

            }

            text() {

            }

            file() {

            }

            link(x, y, url) {
                console.log(url)
            }

            contentmenu(x, y, handlers) {
                const menu = document.createElement('div');
                Object.defineProperty(menu, 'close', {
                    value(ev) {
                        function targetInMenu(target) {
                            if (!target) return false;
                            if (target === menu) return true;
                            return targetInMenu(target.parentElement);
                        }
                        if (targetInMenu(ev?.target) && this !== menu) return false;

                        [ [ menu, 'click' ], [ window, 'mousedown' ], [ window, 'keydown' ] ].forEach(ls => {
                            try { ls[0].removeEventListener(ls[1], menu.close) } catch {} });
                        menu.style.opacity = '.5';
                        menu.style.top = y + 20 + 'px';
                        setTimeout(() => menu.remove(), 100);
                        return true;
                    }
                });
                menu.className = 'lgx-floating-menu';
                menu.style = `left:${x + 110 > window.innerWidth ? x - 110 : x}px;top:${y - 20}px`;
                setTimeout(() => {
                    menu.style.opacity = '1';
                    menu.style.top = y + 'px';
                }, 100);
                menu.innerHTML = '<ul>' + Object.keys(handlers).map(h => h !== '未知类型' ? `<li>${h}</li>` :
                    `<li title="由于同源策略禁止读取该文件所在地址的远程资源,无法得知该文件的类型">${h}</li>`).join('') + '</ul>';
                [ ...menu.children[0].children ].forEach(c => (c.onclick = handlers[c.innerText]));
                menu.addEventListener("click", menu.close);
                window.addEventListener("mousedown", menu.close);
                window.addEventListener("keydown", menu.close);
                floatingContainer.appendChild(menu);
            }

        }

        const creater = new ItemCreater

        Object.defineProperty(DataTransfer.prototype, 'toElements', {
            value({ x, y }) {
                const retArr = []
                if (this.files.length) {
                    for (const f of this.files) {
                        console.log(f.name, f.type, URL.createObjectURL(f))
                        const item = creater[f.type.substring(0, f.type.indexOf('/'))]?.(x, y, f)
                        item && retArr.push(item)
                    }
                    return Promise.resolve(retArr)
                } else {
                    return new Promise(resolve => {
                        const that = this;
                        const url = that.getData("application/x-moz-file-promise-url").replace('http:', 'https:')
                            || that.getData("text/x-moz-url-data").replace('http:', 'https:');
                        // console.log(that.types.reduce((a, b) => (a[b] = that.getData(b)) ?? a, {}));
                        const contentType = getWebFileType(url);
                        const contentTypeStart = contentType?.substring(0, contentType.indexOf('/')) ?? 'file';

                        function getWebFileType(url) {
                            if (!url) return null;
                            try {
                                const req = new XMLHttpRequest();
                                req.open('GET', url, false);
                                req.send(null);
                                return req.getResponseHeader('content-type');
                            } catch {

                            }
                        }

                        function handleFile() {
                            console.log('handle file');
                            const elem = creater[contentTypeStart]?.(x, y, url);
                            resolve(elem ? [ elem ] : []);
                        }

                        function handleLink() {
                            console.log('handle link')
                            console.log(that)
                            const url = that.getData("text/x-moz-url-data").replace('http:', 'https:'),
                                elem = creater.link(x, y, url)
                            resolve(elem ? [ elem ] : []);
                        }

                        const flagFile = this.types.includes("application/x-moz-file-promise-url"),
                            flagLink = that.types.includes("text/x-moz-url-data"),
                            type = { 'image': '图片', 'audio': '音频', 'vedio': '视频', 'text': '文本' }[contentTypeStart]
                                || '未知类型';

                        if (flagFile && flagLink) {
                            creater.contentmenu(x, y, { [type]: handleFile, '链接': handleLink });
                        } else if (flagFile) {
                            handleFile();
                        } else {
                            handleLink();
                        }
                    });

                }
            },
        })

        window.addEventListener('drop', ev => {
            ev.preventDefault()
            ev.dataTransfer.toElements(ev).then(
                elements => elements.forEach(el => floatingContainer.appendChild(el)))
        })

        window.addEventListener('resize', () => {
            for (const div of floatingContainer.children) {
                div.style[ div.style.left ? 'left' : 'right' ] =
                    limit(div.style.left ? div.offsetLeft : window.innerWidth - div.clientWidth - div.offsetLeft,
                        div.scope?.left ?? 0, div.scope?.right ?? window.innerWidth - div.clientWidth) + 'px'
                div.style[ div.style.top ? 'top' : 'bottom' ] =
                    limit(div.style.top ? div.offsetTop : window.innerHeight - div.clientHeight - div.offsetTop,
                        div.scope?.top ?? 0, div.scope?.bottom ?? window.innerHeight - div.clientHeight) + 'px'
            }
        })

        function limit(value, min, max) {
            return Math.min(Math.max(value, min), max)
        }

        setInterval(() => {
            if (document.body.lastChild !== ctrl) {
                document.body.appendChild(ctrl)
                document.body.insertBefore(floatingContainer, ctrl)
            }
        }, 10000)

    })();