├── py ├── __init__.py ├── crawler │ ├── testa │ │ ├── 1605524615137 │ │ ├── 1605524617138 │ │ └── 1605524619136 │ └── testa.json ├── requirements.txt ├── urls.txt ├── crawler.py ├── web.py ├── clientexec.py ├── case │ ├── test.json │ └── 受控事务.json └── controller.py ├── .gitignore ├── images ├── .DS_Store ├── reward.png ├── robot.png └── robot_pic.jpg ├── fonts ├── FontAwesome.otf ├── fontawesome-webfont.eot ├── fontawesome-webfont.ttf ├── fontawesome-webfont.woff └── fontawesome-webfont.woff2 ├── css ├── jquery.gridly.css ├── fonts.css ├── custom.css ├── gridstack.min.css ├── font-awesome.min.css └── gridstack-extra.css ├── script.py ├── main.sh ├── js ├── layout.js ├── monitor_cs.js ├── monitor_bg.js ├── external │ ├── clipboard.min.js │ └── jquery.gridly.js └── newtab.js ├── LICENSE ├── manifest.json ├── feature.md ├── html ├── newtab.html ├── layout.html └── popup.html └── README.md /py/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | 3 | venv/ 4 | 5 | *.pyc 6 | 7 | .DS_Store -------------------------------------------------------------------------------- /images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/images/.DS_Store -------------------------------------------------------------------------------- /images/reward.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/images/reward.png -------------------------------------------------------------------------------- /images/robot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/images/robot.png -------------------------------------------------------------------------------- /py/crawler/testa/1605524615137: -------------------------------------------------------------------------------- 1 | [{"\u7ed3\u679c1": "test - \u767e\u5ea6\u7ffb\u8bd1"}] -------------------------------------------------------------------------------- /py/crawler/testa/1605524617138: -------------------------------------------------------------------------------- 1 | [{"\u7ed3\u679c1": "test - \u767e\u5ea6\u7ffb\u8bd1"}] -------------------------------------------------------------------------------- /images/robot_pic.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/images/robot_pic.jpg -------------------------------------------------------------------------------- /fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webgjc/web_robot/HEAD/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /py/crawler/testa/1605524619136: -------------------------------------------------------------------------------- 1 | [{"\u7ed3\u679c1": "test\u662f\u4ec0\u4e48\u610f\u601d - \u767e\u5ea6\u77e5\u9053"}] -------------------------------------------------------------------------------- /py/crawler/testa.json: -------------------------------------------------------------------------------- 1 | [{"\u7ed3\u679c1": "test - \u767e\u5ea6\u7ffb\u8bd1"}, {"\u7ed3\u679c1": "test - \u767e\u5ea6\u7ffb\u8bd1"}, {"\u7ed3\u679c1": "test\u662f\u4ec0\u4e48\u610f\u601d - \u767e\u5ea6\u77e5\u9053"}] -------------------------------------------------------------------------------- /css/jquery.gridly.css: -------------------------------------------------------------------------------- 1 | /* jQuery Gridly 2 | * Copyright 2020 Kevin Sylvestre 3 | * 1.3.0 4 | */ 5 | .gridly, .gridly > :not(.dragging) { 6 | transition: all 0.4s ease-in-out; } 7 | .gridly .dragging { 8 | z-index: 800; } 9 | -------------------------------------------------------------------------------- /py/requirements.txt: -------------------------------------------------------------------------------- 1 | click==7.1.2 2 | Flask==1.1.2 3 | itsdangerous==1.1.0 4 | Jinja2==2.11.2 5 | MarkupSafe==1.1.1 6 | pynput==1.6.8 7 | pyobjc-core==5.3 8 | pyobjc-framework-Cocoa==5.3 9 | pyobjc-framework-Quartz==5.3 10 | pyperclip==1.8.0 11 | six==1.15.0 12 | Werkzeug==1.0.1 13 | -------------------------------------------------------------------------------- /py/urls.txt: -------------------------------------------------------------------------------- 1 | https://www.baidu.com/s?wd=test&pn=0 2 | https://www.baidu.com/s?wd=test&pn=10 3 | https://www.baidu.com/s?wd=test&pn=20 4 | https://www.baidu.com/s?wd=test&pn=30 5 | https://www.baidu.com/s?wd=test&pn=40 6 | https://www.baidu.com/s?wd=test&pn=50 7 | https://www.baidu.com/s?wd=test&pn=60 8 | https://www.baidu.com/s?wd=test&pn=70 9 | https://www.baidu.com/s?wd=test&pn=80 10 | https://www.baidu.com/s?wd=test&pn=90 11 | https://www.baidu.com/s?wd=test&pn=100 12 | https://www.baidu.com/s?wd=test&pn=110 -------------------------------------------------------------------------------- /css/fonts.css: -------------------------------------------------------------------------------- 1 | /* fallback */ 2 | @font-face { 3 | font-family: 'Material Icons'; 4 | font-style: normal; 5 | font-weight: 400; 6 | src: url(https://fonts.gstatic.com/s/materialicons/v80/flUhRq6tzZclQEJ-Vdg-IuiaDsNcIhQ8tQ.woff2) format('woff2'); 7 | } 8 | 9 | .material-icons { 10 | font-family: 'Material Icons'; 11 | font-weight: normal; 12 | font-style: normal; 13 | font-size: 24px; 14 | line-height: 1; 15 | letter-spacing: normal; 16 | text-transform: none; 17 | display: inline-block; 18 | white-space: nowrap; 19 | word-wrap: normal; 20 | direction: ltr; 21 | -webkit-font-feature-settings: 'liga'; 22 | -webkit-font-smoothing: antialiased; 23 | } 24 | -------------------------------------------------------------------------------- /script.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import json 3 | 4 | 5 | def close_dashboard(): 6 | filename = "./manifest.json" 7 | data = json.loads(open(filename, "r").read()) 8 | data["chrome_url_overrides"] = {} 9 | json.dump(data, open(filename, "w")) 10 | 11 | 12 | def open_dashboard(): 13 | filename = "./manifest.json" 14 | data = json.loads(open(filename, "r").read()) 15 | data["chrome_url_overrides"] = {"newtab": "html/newtab.html"} 16 | json.dump(data, open(filename, "w")) 17 | 18 | 19 | if __name__ == "__main__": 20 | if sys.argv[1] == "close_dashboard": 21 | close_dashboard() 22 | elif sys.argv[1] == "open_dashboard": 23 | open_dashboard() 24 | -------------------------------------------------------------------------------- /main.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | 4 | usage(){ 5 | echo "usage:" 6 | echo 'bash main.sh 参数' 7 | echo '请确保存在python3命令' 8 | echo 'start : 启动本地python web服务' 9 | echo 'open_dashboard : 开启仪表盘' 10 | echo 'close_dashboard : 关闭仪表盘' 11 | echo '示例 : bash main.sh start' 12 | } 13 | 14 | 15 | case $1 in 16 | start) 17 | python3 py/web.py 18 | ;; 19 | open_dashboard) 20 | python3 script.py open_dashboard 21 | echo '请重新加载插件生效' 22 | ;; 23 | close_dashboard) 24 | python3 script.py close_dashboard 25 | echo '请重新加载插件生效' 26 | ;; 27 | *) 28 | usage 29 | ;; 30 | esac 31 | -------------------------------------------------------------------------------- /js/layout.js: -------------------------------------------------------------------------------- 1 | 2 | var grid = GridStack.init({ 3 | alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 4 | navigator.userAgent 5 | ), 6 | resizable: { 7 | handles: 'e, se, s, sw, w' 8 | }, 9 | minRow: 1, 10 | cellHeight: 'auto', 11 | // float: true, 12 | enableMove: true, 13 | enableResize: true, 14 | margin: 2 15 | }); 16 | const serializedData = [ 17 | { 18 | "x": 0, 19 | "y": 0, 20 | "w": 1, 21 | "h": 1, 22 | "content": "事件1" 23 | }, 24 | { 25 | "x": 1, 26 | "y": 1, 27 | "w": 1, 28 | "h": 1, 29 | "content": "事件2" 30 | } 31 | ]; 32 | 33 | grid.load(serializedData); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 ganjiacheng 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /py/crawler.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import json 4 | import shutil 5 | 6 | 7 | class CrawlerData(object): 8 | def __init__(self, case_name): 9 | self.case_name = case_name 10 | self.path = "./py/crawler" 11 | self.case_path = "./py/crawler/{}".format(case_name) 12 | self.summary_file = "{}/{}.json".format(self.path, self.case_name) 13 | if not os.path.exists(self.case_path): 14 | os.mkdir(self.case_path) 15 | 16 | def clear(self): 17 | if os.path.exists(self.case_path): 18 | shutil.rmtree(self.case_path) 19 | if os.path.exists(self.summary_file): 20 | os.remove(self.summary_file) 21 | 22 | def save(self, data): 23 | with open("{}/{}".format(self.case_path, int(time.time()*1000)), "w") as f: 24 | print(data) 25 | f.write(json.dumps(data)) 26 | 27 | def summary(self): 28 | fs = os.listdir(self.case_path) 29 | fs.sort() 30 | data = [] 31 | for f in fs: 32 | data.extend(json.loads(open("{}/{}".format(self.case_path, f), "r").read())) 33 | json.dump(data, open("{}/{}.json".format(self.path, self.case_name), "w")) 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Web Robot", 4 | "version": "2.8.0", 5 | "description": "网页自动化操作机器人,网页自动化流程的定义与运行,有助于自动化测试和自动化脚本的执行,帮助web开发者提高效率,降低页面的重复劳动。", 6 | "browser_action": { 7 | "default_popup": "html/popup.html", 8 | "default_icon": { 9 | "128": "images/robot.png" 10 | } 11 | }, 12 | "background": { 13 | "scripts": [ 14 | "js/background.js" 15 | ] 16 | }, 17 | "icons": { 18 | "128": "images/robot.png" 19 | }, 20 | "content_scripts": [ 21 | { 22 | "matches": [ 23 | "" 24 | ], 25 | "js": [ 26 | "js/external/jquery.min.js", 27 | "js/content_script.js" 28 | ], 29 | "css": [ 30 | "css/custom.css" 31 | ], 32 | "all_frames": true 33 | } 34 | ], 35 | "permissions": [ 36 | "notifications", 37 | "webRequest", 38 | "webRequestBlocking", 39 | "storage", 40 | "", 41 | "tabs", 42 | "clipboardRead" 43 | ], 44 | "web_accessible_resources": [ 45 | "html/popup.html" 46 | ], 47 | "chrome_url_overrides": {} 48 | } -------------------------------------------------------------------------------- /css/custom.css: -------------------------------------------------------------------------------- 1 | .chrome-plugin-simple-tip { 2 | position: fixed; 3 | left: 20px; 4 | padding: 16px 10px; 5 | top: 30px; 6 | color: #000; 7 | border: 1px solid #000; 8 | min-width: 150px; 9 | max-width: 700px; 10 | border-radius: 3px; 11 | text-align: center; 12 | font-size: 16px; 13 | background: rgba(255, 255, 255, 0.8); 14 | transition: top .4s; 15 | z-index: 9999999; 16 | } 17 | 18 | .animated { 19 | -webkit-animation-duration: .5s; 20 | animation-duration: .5s; 21 | -webkit-animation-fill-mode: both; 22 | animation-fill-mode: both 23 | } 24 | 25 | @-webkit-keyframes slideInLeft { 26 | 0% { 27 | -webkit-transform: translate3d(-100%, 0, 0); 28 | transform: translate3d(-100%, 0, 0); 29 | visibility: visible 30 | } 31 | 100% { 32 | -webkit-transform: translate3d(0, 0, 0); 33 | transform: translate3d(0, 0, 0) 34 | } 35 | } 36 | 37 | @keyframes slideInLeft { 38 | 0% { 39 | -webkit-transform: translate3d(-100%, 0, 0); 40 | transform: translate3d(-100%, 0, 0); 41 | visibility: visible 42 | } 43 | 100% { 44 | -webkit-transform: translate3d(0, 0, 0); 45 | transform: translate3d(0, 0, 0) 46 | } 47 | } 48 | 49 | .slideInLeft { 50 | -webkit-animation-name: slideInLeft; 51 | animation-name: slideInLeft 52 | } -------------------------------------------------------------------------------- /py/web.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from flask import Flask 4 | from flask import request 5 | from clientexec import WebClientExec 6 | from crawler import CrawlerData 7 | 8 | 9 | app = Flask(__name__) 10 | PYTHON_ENV = "./venv/bin/python" 11 | 12 | 13 | @app.route("/", methods=["GET"]) 14 | def health(): 15 | return "success" 16 | 17 | 18 | @app.route("/webexec/", methods=["POST"]) 19 | def web_simulation(): 20 | client = WebClientExec() 21 | data = json.loads(request.get_data(as_text=True)) 22 | client.run(data) 23 | return "success" 24 | 25 | 26 | @app.route("/record/", methods=["GET"]) 27 | def controller_listen(): 28 | case_name = request.args.get('case_name') 29 | os.system(PYTHON_ENV + " py/controller.py record " + case_name + " 2>&1 &") 30 | return "success" 31 | 32 | 33 | @app.route("/recover/", methods=["GET"]) 34 | def controller_recover(): 35 | case_name = request.args.get('case_name') 36 | os.system(PYTHON_ENV + " py/controller.py recover " + case_name + " 2>&1 &") 37 | return "success" 38 | 39 | 40 | @app.route("/crawler/", methods=["POST"]) 41 | def controller_save(): 42 | data = json.loads(request.get_data(as_text=True)) 43 | cd = CrawlerData(data["case_name"]) 44 | if data["opera"] == "clear": 45 | cd.clear() 46 | elif data["opera"] == "summary": 47 | cd.summary() 48 | elif data["opera"] == "save": 49 | cd.save(data["data"]) 50 | else: 51 | return "fail" 52 | return "success" 53 | 54 | 55 | urls = open("py/urls.txt", "r").readlines() 56 | n = 0 57 | 58 | # 外部爬虫url输入用例demo 59 | @app.route("/crawler/url/", methods=["GET"]) 60 | def controller_crawler_url(): 61 | import random 62 | global n 63 | n += 1 64 | if n >= 50: 65 | return "" 66 | return urls[random.randint(0, 10)].strip() 67 | 68 | 69 | if __name__ == "__main__": 70 | app.run("127.0.0.1", "12580", debug=True) 71 | -------------------------------------------------------------------------------- /py/clientexec.py: -------------------------------------------------------------------------------- 1 | import time 2 | import pynput 3 | import pyperclip 4 | 5 | 6 | class WebClientExec(object): 7 | """ 8 | 还原点击设值操作 9 | record: { 10 | "x": 0, 11 | "y": 0, 12 | "opera": "click", 13 | "value": "asd" 14 | } 15 | """ 16 | def __init__(self): 17 | self.mouse = pynput.mouse.Controller() 18 | self.keyboard = pynput.keyboard.Controller() 19 | self.button = pynput.mouse.Button.left 20 | 21 | def deal_click(self, record): 22 | """ 23 | 处理鼠标点击事件 24 | """ 25 | self.mouse.position = (record.get("x"), record.get("y")) 26 | time.sleep(0.1) 27 | self.mouse.click(self.button) 28 | 29 | def deal_set_value(self, record): 30 | """ 31 | 处理设值事件 32 | """ 33 | self.deal_click(record) 34 | time.sleep(0.3) 35 | self.keyboard.press(eval("Key.cmd", {}, { 36 | "Key": pynput.keyboard.Key 37 | })) 38 | self.keyboard.press("a") 39 | self.keyboard.release("a") 40 | self.keyboard.release(eval("Key.cmd", {}, { 41 | "Key": pynput.keyboard.Key 42 | })) 43 | pyperclip.copy(record.get("value")) 44 | time.sleep(0.1) 45 | self.keyboard.press(eval("Key.cmd", {}, { 46 | "Key": pynput.keyboard.Key 47 | })) 48 | self.keyboard.press("v") 49 | self.keyboard.release("v") 50 | self.keyboard.release(eval("Key.cmd", {}, { 51 | "Key": pynput.keyboard.Key 52 | })) 53 | 54 | def deal_mouseover(self, record): 55 | self.mouse.position = (record.get("x"), record.get("y")) 56 | 57 | def run(self, record): 58 | """ 59 | 处理事件入口 60 | """ 61 | if record.get("opera") == "click": 62 | self.deal_click(record) 63 | if record.get("opera") == "value": 64 | self.deal_set_value(record) 65 | if record.get("opera") == "mouseover": 66 | self.deal_mouseover(record) 67 | 68 | 69 | if __name__ == '__main__': 70 | pass -------------------------------------------------------------------------------- /feature.md: -------------------------------------------------------------------------------- 1 | # 网页自动化 + 数据爬取与处理 2 | 3 | ## 定义数据源 4 | 5 | 数据源列表: 6 | - 唯一key 7 | - 中文名 8 | - 数据源类型(接口/网页/自定义) 9 | 10 | 元数据配置: 11 | - 字段名 12 | - 字段中文名 13 | 14 | 数据源详情: 15 | - 爬虫流程 16 | - 单线程爬虫 17 | - 并发爬虫 18 | - 发送请求 19 | - 请求定义 20 | - 数据解析 21 | - 测试运行 22 | 23 | 数据源定义,数据源配置,类似配置一个表结构,有数据源名和数据源的各个字段。每个数据源中的数据都是一个对象数组的表[{},{}] 24 | 25 | 同时还需要配置数据源的获取方式,这边有两种爬虫流程,直接请求。 26 | 27 | 爬虫流程主要用于网页的上数据的获取,也就是获取dom的innerText或者innerHtml。并发使用页面iframe多开实现。 28 | 单线程适用如翻页场景,并发适用每页url不一样可以批定义的场景。 29 | 30 | 发送请求主要是直接获取服务器的数据,由于插件使用的是浏览器发送请求的方式,所以只要手动登录一次,请求会带上cookie以跳过后端验证。 31 | 32 | ## 数据爬取 33 | 34 | - 立即运行 35 | - 定时运行 36 | - 运行状态 37 | - 最近一次运行时间 38 | - 数据数量 39 | - 运行日志 40 | 41 | 数据源运行列表,所有运行都为后台运行。立即运行则马上运行一次。定时运行则可以设置运行时间。运行状态使用最近一次运行的状态。 42 | 运行日志记录详细的获取数据地址和每次获取到的数据,解析后的数据。 43 | 44 | ## 数据处理 45 | 46 | - js脚本任务 -> 新的数据源 47 | 48 | js脚本中会默认将所有数据源的作为key变量赋值数据, 49 | 可在其之上进行批处理操作。 50 | 51 | ## 数据导出/可视化 52 | 53 | - 导出文件(json) 54 | - 数据可视化页 55 | - 操作元数据作图 56 | 57 | # 流程定义,调试与编排 58 | 59 | 开启流程定义与编排页入口 60 | 61 | 打开插件页,点击开启流程定义与编排按钮 62 | 63 | 将开启一个新的空白标签页面,作为定义测试目标页 64 | 65 | 流程定义与编排页面将会由一个新的chrome窗口打开。 66 | 67 | 两个窗口相互独立(可自行排版左右或两块屏幕定义) 68 | 69 | ## [左] 流程事件列表 70 | 71 | **功能要点** 72 | 73 | ### 展示事件列表 74 | - 一行一个展示,事件名超出展示... 75 | - 可拖拽进行上下编排 76 | - 右侧有竖状点,包括(展示详情,运行单个,运行至这个) 77 | - 展示详情点击则弹出modal列出事件详情,modal中有编辑 78 | - 运行单个则表示单个事件,运行至这个则从头运行至此 79 | - 上方有运行全部icon,点击运行当前所有事件 80 | 81 | ### 事件运行参数 82 | 83 | #### 系统运行参数 84 | 系统运行参数开关,会影响运行的日志输出 85 | 86 | - 开启接口监听,配置接口匹配路由 87 | - 开启页面变化监控 88 | - 开启数据监控 89 | 90 | #### 自定义参数 91 | - 上方点击配置运行参数,弹出参数配置框textarea 92 | - 使用一行一个key=v表示参数 93 | 94 | ### 事件运行数据可视化 95 | 96 | - 上方有icon,点击使用k=v展示运行过程中爬的数据 97 | 98 | ### 运行日志 99 | 运行前检查结果 100 | 打印运行过程中上报的日志 101 | 102 | ## [右] 事件库 103 | 104 | ### 全部临时事件展示 105 | - 每个方形代表一个事件,展示事件名,点击展示事件详情modal 106 | - 事件可以拖到左侧 107 | - 拖进左侧前进行测试是否定义全 108 | 109 | ### 事件定义 110 | - 右下角有,开始录制添加事件,页面选取添加事件,自定义事件按钮 111 | 112 | ### 自定义事件 113 | - 自定义浏览器事件,点击打开modal 114 | - 使用queryselectAll自定义选择器 115 | 116 | ### 全局配置 117 | 左下角有全局配置按钮,点击打开全局配置项,影响新定义的事件 118 | 119 | - 默认选择器:混合选择器,值选择器,元素选择器 120 | - 默认开启dom检查:开/关 121 | - 默认延时:0 122 | -------------------------------------------------------------------------------- /html/newtab.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 我的看板 14 | 61 | 62 | 63 | 64 |
65 | 排版 66 | 重置 67 |
68 |
69 |
70 | 71 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /js/monitor_cs.js: -------------------------------------------------------------------------------- 1 | /** 2 | * { 3 | * "type": "事件类型", 4 | * "selecter": "事件元素", 5 | * "context": "包含内容", 6 | * "url": "事件新url" 7 | * } 8 | */ 9 | 10 | let EVENTS = []; 11 | 12 | // 事件类型枚举 13 | let EVENT_TYPE_ENUM = { 14 | CLICK: "CLICK", // 点击 15 | INPUT: "INPUT", // 设值 16 | NEWPAGE: "NEWPAGE", // 新开页面 17 | CLOSEPAGE: "CLOSEPAGE", // 关闭页面 18 | SCROLL: "SCROLL" // 滚动 19 | } 20 | 21 | // 根据节点获取选择器 22 | function dom_to_selector(dom) { 23 | let names = []; 24 | let dombak = dom; 25 | do { 26 | if (!dom || !dom.parentElement) break; 27 | if (dom.id && isNaN(Number(dom.id[0]))) { 28 | names.unshift(`${dom.tagName}#${dom.id}`); 29 | break; 30 | } else { 31 | let tmp; 32 | let classNames = []; 33 | for (let i = 0; i < dom.classList.length; i++) { 34 | classNames.push(dom.classList[i]); 35 | } 36 | if (classNames.length > 0) { 37 | tmp = `${dom.tagName}.${classNames.join(".")}`; 38 | } else { 39 | tmp = `${dom.tagName}`; 40 | } 41 | names.unshift(tmp); 42 | } 43 | dom = dom.parentElement; 44 | } while (dom !== null); 45 | let selector = names.join(" > "); 46 | let nodes = document.querySelectorAll(selector); 47 | for (let i = 0; i < nodes.length; i++) { 48 | if (nodes[i] === dombak) { 49 | return [selector, i]; 50 | } 51 | } 52 | } 53 | 54 | // 发送通知 55 | function notify(data, cb) { 56 | chrome.runtime.sendMessage({ 57 | type: "SEND_MSG", 58 | data: data 59 | }, function(resp) { 60 | cb && cb(resp); 61 | }); 62 | } 63 | 64 | // 添加事件 65 | function add_event(data, cb) { 66 | chrome.runtime.sendMessage({ 67 | type: "ADD_EVENT", 68 | data: data 69 | }, function(resp) { 70 | cb && cb(resp); 71 | }); 72 | } 73 | 74 | // 监控点击事件 75 | function click_monitor() { 76 | document.addEventListener("click", function (e) { 77 | add_event({ 78 | type: EVENT_TYPE_ENUM.CLICK, 79 | selector: dom_to_selector(e.target), 80 | context: e.target.innerText.slice(0 ,100) 81 | }); 82 | }, false); 83 | } 84 | 85 | 86 | // 监控input textarea设值事件 87 | function input_monitor() { 88 | let inputs = document.getElementsByTagName("input"); 89 | let textareas = document.getElementsByTagName("textarea"); 90 | let doms = []; 91 | for(let i = 0; i < inputs.length; i++) { 92 | doms.push(inputs[i]); 93 | } 94 | for(let i = 0; i < textareas.length; i++) { 95 | doms.push(textareas[i]); 96 | } 97 | for(let i = 0; i < doms.length; i++) { 98 | doms[i].addEventListener("change", function(e) { 99 | add_event({ 100 | type: EVENT_TYPE_ENUM.INPUT, 101 | selector: dom_to_selector(e.target), 102 | context: e.target.value 103 | }); 104 | }); 105 | } 106 | } 107 | 108 | // 监控主入口 109 | function monitor() { 110 | click_monitor(); 111 | input_monitor(); 112 | } 113 | 114 | monitor(); -------------------------------------------------------------------------------- /js/monitor_bg.js: -------------------------------------------------------------------------------- 1 | let TAB_INFO = {}; 2 | 3 | let CONFIG = { 4 | notify: true, 5 | key: "my_robot_monitor" 6 | } 7 | 8 | let EVENT_TYPE_ENUM = { 9 | CLICK: "CLICK", 10 | INPUT: "INPUT", 11 | NEWPAGE: "NEWPAGE", 12 | CHANGEURL: "CHANGEURL", 13 | CLOSEPAGE: "CLOSEPAGE", 14 | SCROLL: "SCROLL" 15 | }; 16 | 17 | let EVENT_TYPE_TITLE = { 18 | CLICK: "点击事件", 19 | INPUT: "设值事件", 20 | NEWPAGE: "新开页面", 21 | CHANGEURL: "修改链接", 22 | CLOSEPAGE: "关闭页面", 23 | SCROLL: "滚动事件" 24 | }; 25 | 26 | // 发送通知 27 | function notify(data) { 28 | let sb = ""; 29 | if(data.type === EVENT_TYPE_ENUM.CLICK) { 30 | sb = "点击" + " " + data.context; 31 | }else if(data.type === EVENT_TYPE_ENUM.INPUT) { 32 | sb = "设值" + " " + data.context; 33 | }else if(data.type in [EVENT_TYPE_ENUM.NEWPAGE, EVENT_TYPE_ENUM.CHANGEURL, EVENT_TYPE_ENUM.CLOSEPAGE]) { 34 | sb = EVENT_TYPE_TITLE[data.type] + " " + data.url; 35 | } 36 | chrome.notifications.create(null, { 37 | type: "basic", 38 | iconUrl: "/images/robot.png", 39 | title: EVENT_TYPE_TITLE[data.type], 40 | message: sb 41 | }); 42 | } 43 | 44 | // 获取数据存储 45 | function get_my_monitor(callback) { 46 | chrome.storage.local.get([CONFIG.key], function (res) { 47 | if (callback) callback(res[CONFIG.key]); 48 | }); 49 | } 50 | 51 | // 设置数据存储 52 | function set_my_monitor(new_monitor, cb) { 53 | chrome.storage.local.set({ 54 | [CONFIG.key]: new_monitor 55 | }, function () { 56 | cb && cb(); 57 | }); 58 | } 59 | 60 | // 增加事件 61 | function add_event(data) { 62 | get_my_monitor(monitor => { 63 | monitor.push(data); 64 | console.log(monitor); 65 | set_my_monitor(monitor, () => { 66 | CONFIG.notify && notify(data); 67 | }); 68 | }); 69 | } 70 | 71 | 72 | // tab状态监控 73 | function tab_monitor() { 74 | setInterval(() => { 75 | console.log(1) 76 | let now_tab = {}; 77 | chrome.tabs.query({ 78 | currentWindow: true 79 | }, function(tabs) { 80 | if(tabs.length == 0) return; 81 | for(let i = 0; i < tabs.length; i++) { 82 | now_tab[tabs[i].id] = tabs[i].url; 83 | if(!TAB_INFO[tabs[i].id]) { 84 | add_event({ 85 | type: EVENT_TYPE_ENUM.OPENPAGE, 86 | url: tabs[i].url 87 | }); 88 | }else if(TAB_INFO[tabs[i].id] != tabs[i].url) { 89 | add_event({ 90 | type: EVENT_TYPE_ENUM.CHANGEURL, 91 | url: tabs[i].url 92 | }); 93 | } 94 | } 95 | let old_tab_ids = Object.keys(TAB_INFO); 96 | console.log(old_tab_ids, now_tab) 97 | for(let i = 0; i < old_tab_ids.length; i++) { 98 | if(!now_tab[parseInt(old_tab_ids[i])]) { 99 | add_event({ 100 | type: EVENT_TYPE_ENUM.CLOSEPAGE, 101 | url: TAB_INFO[i] 102 | }); 103 | } 104 | } 105 | TAB_INFO = now_tab; 106 | }); 107 | }, 3000); 108 | } 109 | 110 | // 初始化 111 | function init() { 112 | get_my_monitor(monitor => { 113 | if(!monitor) { 114 | set_my_monitor([]); 115 | } 116 | }); 117 | chrome.tabs.query({ 118 | currentWindow: true 119 | }, function(tabs) { 120 | for(let i = 0; i < tabs.length; i++) { 121 | TAB_INFO[tabs[i].id] = tabs[i].url; 122 | } 123 | console.log(TAB_INFO) 124 | }); 125 | } 126 | 127 | // 监听来自monitor_cs的消息 128 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 129 | if(request.type === "SEND_MSG") { 130 | notify(request.data); 131 | sendResponse(""); 132 | } else if(request.type === "ADD_EVENT") { 133 | add_event(request.data) 134 | } 135 | }); 136 | 137 | 138 | init(); 139 | tab_monitor(); 140 | 141 | -------------------------------------------------------------------------------- /html/layout.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 流程定义与编排 14 | 21 | 22 | 23 |
24 | 33 |
34 |
35 | 41 | 42 |
43 | 60 |
61 | 62 |
63 | 64 |
65 |
66 |
67 | 72 |
73 |
74 | 75 |
76 |
77 |
78 |
79 |
80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /py/case/test.json: -------------------------------------------------------------------------------- 1 | [{"opera": "move", "posix": 528.140625, "posiy": 283.02734375, "stime": 0.24104881286621094}, {"opera": "move", "posix": 527.87890625, "posiy": 284.9375, "stime": 0.2412402629852295}, {"opera": "move", "posix": 527.5625, "posiy": 285.875, "stime": 0.2412700653076172}, {"opera": "move", "posix": 527.2734375, "posiy": 287.015625, "stime": 0.24143218994140625}, {"opera": "move", "posix": 523.796875, "posiy": 293.8203125, "stime": 0.24153709411621094}, {"opera": "move", "posix": 520.97265625, "posiy": 300.02734375, "stime": 0.24155807495117188}, {"opera": "move", "posix": 514.984375, "posiy": 311.4453125, "stime": 0.24160385131835938}, {"opera": "move", "posix": 509.671875, "posiy": 321.9765625, "stime": 0.24164628982543945}, {"opera": "click", "posix": 496.015625, "posiy": 347.25, "button": "Button.left", "stime": 0.896353006362915}, {"opera": "press", "key": "t", "stime": 1.540827989578247}, {"opera": "press", "key": "e", "stime": 1.6467258930206299}, {"opera": "release", "key": "t", "stime": 1.646888017654419}, {"opera": "press", "key": "s", "stime": 1.7391910552978516}, {"opera": "release", "key": "e", "stime": 1.758594036102295}, {"opera": "release", "key": "s", "stime": 1.8666901588439941}, {"opera": "press", "key": "t", "stime": 1.8747169971466064}, {"opera": "release", "key": "t", "stime": 1.9389410018920898}, {"opera": "move", "posix": 497.671875, "posiy": 326.6015625, "stime": 2.4190211296081543}, {"opera": "move", "posix": 498.6875, "posiy": 321.515625, "stime": 2.4280331134796143}, {"opera": "move", "posix": 502.37890625, "posiy": 304.625, "stime": 2.4514009952545166}, {"opera": "move", "posix": 506.9296875, "posiy": 286.8828125, "stime": 2.4682178497314453}, {"opera": "move", "posix": 518.984375, "posiy": 252.328125, "stime": 2.49219012260437}, {"opera": "move", "posix": 532.16796875, "posiy": 224.4296875, "stime": 2.5085461139678955}, {"opera": "move", "posix": 551.93359375, "posiy": 184.0390625, "stime": 2.5576882362365723}, {"opera": "move", "posix": 554.36328125, "posiy": 178.3046875, "stime": 2.5909981727600098}, {"opera": "move", "posix": 559.7890625, "posiy": 178.04296875, "stime": 2.679896116256714}, {"opera": "move", "posix": 567.734375, "posiy": 177.55859375, "stime": 2.696211099624634}, {"opera": "move", "posix": 584.2109375, "posiy": 173.921875, "stime": 2.7124991416931152}, {"opera": "move", "posix": 592.89453125, "posiy": 172.6796875, "stime": 2.720440149307251}, {"opera": "move", "posix": 636.1953125, "posiy": 171.57421875, "stime": 2.7854862213134766}, {"opera": "move", "posix": 646.21484375, "posiy": 171.57421875, "stime": 2.8265340328216553}, {"opera": "move", "posix": 647.35546875, "posiy": 171.28515625, "stime": 2.8428139686584473}, {"opera": "move", "posix": 647.87109375, "posiy": 171.28515625, "stime": 2.8595731258392334}, {"opera": "click", "posix": 647.87109375, "posiy": 171.28515625, "button": "Button.left", "stime": 2.885261058807373}, {"opera": "move", "posix": 657.62109375, "posiy": 171.05078125, "stime": 4.783452033996582}, {"opera": "move", "posix": 665.3203125, "posiy": 169.86328125, "stime": 4.791637182235718}, {"opera": "move", "posix": 714.79296875, "posiy": 164.19921875, "stime": 4.816267013549805}, {"opera": "move", "posix": 803.078125, "posiy": 153.23828125, "stime": 4.848268032073975}, {"opera": "move", "posix": 827.90625, "posiy": 148.4609375, "stime": 4.856910943984985}, {"opera": "move", "posix": 852.8203125, "posiy": 141.75, "stime": 4.86481499671936}, {"opera": "move", "posix": 925.63671875, "posiy": 119.02734375, "stime": 4.9052650928497314}, {"opera": "move", "posix": 938.08984375, "posiy": 114.62890625, "stime": 4.913571834564209}, {"opera": "move", "posix": 955.72265625, "posiy": 107.81640625, "stime": 4.93859601020813}, {"opera": "move", "posix": 963.55859375, "posiy": 104.46875, "stime": 4.962939977645874}, {"opera": "move", "posix": 965.06640625, "posiy": 103.55078125, "stime": 4.978845119476318}, {"opera": "move", "posix": 965.62890625, "posiy": 103.55078125, "stime": 4.987626075744629}, {"opera": "move", "posix": 965.88671875, "posiy": 103.2890625, "stime": 4.995812892913818}, {"opera": "move", "posix": 965.37890625, "posiy": 102.54296875, "stime": 5.076995849609375}, {"opera": "move", "posix": 965.1328125, "posiy": 102.54296875, "stime": 5.093358278274536}, {"opera": "move", "posix": 964.1015625, "posiy": 102.54296875, "stime": 5.134038209915161}, {"opera": "move", "posix": 962.6953125, "posiy": 102.54296875, "stime": 5.158647060394287}, {"opera": "move", "posix": 962.43359375, "posiy": 102.80078125, "stime": 5.166542053222656}, {"opera": "move", "posix": 961.859375, "posiy": 103.0859375, "stime": 5.1750030517578125}, {"opera": "move", "posix": 959.0390625, "posiy": 104.7421875, "stime": 5.22355318069458}, {"opera": "move", "posix": 958.77734375, "posiy": 105.0, "stime": 5.231871128082275}, {"opera": "move", "posix": 958.25390625, "posiy": 105.2578125, "stime": 5.24812912940979}, {"opera": "move", "posix": 957.734375, "posiy": 105.2578125, "stime": 5.2641520500183105}, {"opera": "move", "posix": 957.49609375, "posiy": 105.2578125, "stime": 5.304893970489502}] 2 | -------------------------------------------------------------------------------- /py/controller.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import json 4 | import random 5 | import threading 6 | import pynput 7 | 8 | 9 | class MouseRecord(object): 10 | """ 11 | 记录键盘鼠标事件值json文件, 12 | 包括鼠标移动,滚动,左右键 13 | 键盘按下,放开 14 | """ 15 | def __init__(self, file_name): 16 | self.start_time = 0 17 | self.mouse_list = [] 18 | self.running = True 19 | self.save_file = "py/case/" + file_name + ".json" 20 | 21 | def get_time(self): 22 | return time.time() - self.start_time 23 | 24 | def on_click(self, x, y, button, pressed): 25 | """ 26 | click事件 27 | """ 28 | if not self.running: 29 | return False 30 | if not pressed: 31 | return True 32 | self.mouse_list.append({ 33 | "opera": "click", 34 | "posix": x, 35 | "posiy": y, 36 | "button": str(button), 37 | "stime": self.get_time() 38 | }) 39 | 40 | def on_move(self, x, y): 41 | """ 42 | 鼠标移动事件,加个随机减少存储 43 | """ 44 | if random.randint(0, 2) == 1: 45 | self.mouse_list.append({ 46 | "opera": "move", 47 | "posix": x, 48 | "posiy": y, 49 | "stime": self.get_time() 50 | }) 51 | 52 | def on_scroll(self, x, y, dx, dy): 53 | """ 54 | 鼠标滚动事件 55 | """ 56 | self.mouse_list.append({ 57 | "opera": "scroll", 58 | "posix": x, 59 | "posiy": y, 60 | "scrollx": dx, 61 | "scrolly": dy, 62 | "stime": self.get_time() 63 | }) 64 | 65 | def on_key_press(self, key): 66 | """ 67 | 键盘按下事件,正常建是直接展示字符,特殊键会返回Key.xxx 68 | 按下esc的时候退出监听 69 | """ 70 | if key == pynput.keyboard.Key.esc: 71 | self.running = False 72 | mouse = pynput.mouse.Controller() 73 | mouse.click(pynput.mouse.Button.left) 74 | return self.running 75 | if str(key) != "<0>": 76 | self.mouse_list.append({ 77 | "opera": "press", 78 | "key": str(key).strip("'"), 79 | "stime": self.get_time() 80 | }) 81 | 82 | def on_key_release(self, key): 83 | """ 84 | 键盘释放事件 85 | """ 86 | if str(key) != "<0>": 87 | self.mouse_list.append({ 88 | "opera": "release", 89 | "key": str(key).strip("'"), 90 | "stime": self.get_time() 91 | }) 92 | 93 | def mouse_listen(self): 94 | """ 95 | 开启鼠标监听 96 | """ 97 | with pynput.mouse.Listener(on_move=self.on_move, on_click=self.on_click, on_scroll=self.on_scroll) as listener: 98 | listener.join() 99 | 100 | def key_listen(self): 101 | """ 102 | 开启键盘监听 103 | """ 104 | with pynput.keyboard.Listener(on_press=self.on_key_press, on_release=self.on_key_release) as listener: 105 | listener.join() 106 | 107 | def run(self): 108 | """ 109 | 运行监听,结束后保存为json文件 110 | """ 111 | self.start_time = time.time() 112 | t1 = threading.Thread(target=self.mouse_listen) 113 | t2 = threading.Thread(target=self.key_listen) 114 | t1.start() 115 | t2.start() 116 | t1.join() 117 | t2.join() 118 | print(json.dumps(self.mouse_list), file=open(self.save_file, "w")) 119 | 120 | 121 | class MouseRecover(object): 122 | """ 123 | 还原键盘鼠标事件 124 | """ 125 | def __init__(self, file_name): 126 | self.start_time = 0 127 | self.mouse = pynput.mouse.Controller() 128 | self.keyboard = pynput.keyboard.Controller() 129 | self.buttons = { 130 | "Button.left": pynput.mouse.Button.left, 131 | "Button.right": pynput.mouse.Button.right 132 | } 133 | self.read_file = "py/case/" + file_name + ".json" 134 | 135 | def deal_click(self, record): 136 | """ 137 | 处理鼠标点击事件 138 | """ 139 | self.mouse.position = (record.get("posix"), record.get("posiy")) 140 | time.sleep(0.1) 141 | self.mouse.click(self.buttons.get(record.get("button"))) 142 | 143 | def deal_move(self, record): 144 | """ 145 | 处理鼠标移动事件 146 | """ 147 | self.mouse.position = (record.get("posix"), record.get("posiy")) 148 | 149 | def deal_scroll(self, record): 150 | """ 151 | 处理鼠标滚动事件 152 | """ 153 | self.mouse.position = (record.get("posix"), record.get("posiy")) 154 | self.mouse.scroll(record.get("scrollx"), record.get("scrolly")) 155 | 156 | def deal_key_press(self, record): 157 | """ 158 | 处理键盘按下事件 159 | """ 160 | if record.get("key").startswith("Key"): 161 | self.keyboard.press(eval(record.get("key"), {}, { 162 | "Key": pynput.keyboard.Key 163 | })) 164 | else: 165 | self.keyboard.press(record.get("key")) 166 | 167 | def deal_key_release(self, record): 168 | """ 169 | 处理键盘释放事件 170 | """ 171 | if record.get("key").startswith("Key"): 172 | self.keyboard.release(eval(record.get("key"), {}, { 173 | "Key": pynput.keyboard.Key 174 | })) 175 | else: 176 | self.keyboard.release(record.get("key")) 177 | 178 | def run(self): 179 | """ 180 | 读取json文件,执行事件 181 | """ 182 | data = json.load(open(self.read_file, "r")) 183 | for item in data: 184 | if item.get("opera") == "click": 185 | self.deal_click(item) 186 | if item.get("opera") == "move": 187 | self.deal_move(item) 188 | if item.get("opera") == "scroll": 189 | self.deal_scroll(item) 190 | if item.get("opera") == "press": 191 | self.deal_key_press(item) 192 | if item.get("opera") == "release": 193 | self.deal_key_release(item) 194 | time.sleep(item.get("stime") - self.start_time) 195 | self.start_time = item.get("stime") 196 | 197 | 198 | if __name__ == "__main__": 199 | if sys.argv[1] == "record": 200 | #记录事件 201 | t = MouseRecord(str(sys.argv[2])) 202 | t.run() 203 | elif sys.argv[1] == "recover": 204 | # 运行事件 205 | tt = MouseRecover(str(sys.argv[2])) 206 | tt.run() 207 | else: 208 | print("use argv record / recover case_name") 209 | -------------------------------------------------------------------------------- /py/case/受控事务.json: -------------------------------------------------------------------------------- 1 | [{"opera": "move", "posix": 859.453125, "posiy": 304.015625, "stime": 0.3635399341583252}, {"opera": "move", "posix": 741.66796875, "posiy": 282.78125, "stime": 0.40438389778137207}, {"opera": "move", "posix": 642.37890625, "posiy": 258.7421875, "stime": 0.42853403091430664}, {"opera": "move", "posix": 606.44140625, "posiy": 249.7578125, "stime": 0.4371209144592285}, {"opera": "move", "posix": 569.41796875, "posiy": 239.34375, "stime": 0.4449191093444824}, {"opera": "move", "posix": 213.859375, "posiy": 156.76171875, "stime": 0.5767831802368164}, {"opera": "move", "posix": 201.40234375, "posiy": 151.6328125, "stime": 0.5843770503997803}, {"opera": "move", "posix": 165.80859375, "posiy": 135.36328125, "stime": 0.6343870162963867}, {"opera": "move", "posix": 159.953125, "posiy": 132.390625, "stime": 0.6821091175079346}, {"opera": "move", "posix": 154.4765625, "posiy": 131.9140625, "stime": 0.8695731163024902}, {"opera": "move", "posix": 136.96875, "posiy": 124.73828125, "stime": 0.8939492702484131}, {"opera": "move", "posix": 107.19921875, "posiy": 107.28515625, "stime": 0.9183249473571777}, {"opera": "move", "posix": 102.37109375, "posiy": 103.52734375, "stime": 0.9265022277832031}, {"opera": "move", "posix": 71.421875, "posiy": 83.359375, "stime": 0.9610438346862793}, {"opera": "move", "posix": 54.390625, "posiy": 71.66015625, "stime": 1.0120649337768555}, {"opera": "move", "posix": 52.91015625, "posiy": 70.55078125, "stime": 1.0160439014434814}, {"opera": "move", "posix": 50.3203125, "posiy": 67.9609375, "stime": 1.0426092147827148}, {"opera": "move", "posix": 49.71875, "posiy": 66.44921875, "stime": 1.058915138244629}, {"opera": "move", "posix": 49.40234375, "posiy": 65.5078125, "stime": 1.065669059753418}, {"opera": "move", "posix": 49.11328125, "posiy": 64.93359375, "stime": 1.0740442276000977}, {"opera": "move", "posix": 49.11328125, "posiy": 64.359375, "stime": 1.081725835800171}, {"opera": "move", "posix": 47.06640625, "posiy": 60.546875, "stime": 1.1302759647369385}, {"opera": "move", "posix": 45.37109375, "posiy": 57.421875, "stime": 1.1794018745422363}, {"opera": "move", "posix": 44.8203125, "posiy": 55.75, "stime": 1.2120251655578613}, {"opera": "move", "posix": 43.08984375, "posiy": 52.30859375, "stime": 1.2612919807434082}, {"opera": "move", "posix": 42.5703125, "posiy": 51.52734375, "stime": 1.2859790325164795}, {"opera": "move", "posix": 42.30859375, "posiy": 51.265625, "stime": 1.2941310405731201}, {"opera": "move", "posix": 41.78515625, "posiy": 50.7421875, "stime": 1.318753957748413}, {"opera": "move", "posix": 40.75390625, "posiy": 49.95703125, "stime": 1.3672749996185303}, {"opera": "move", "posix": 40.51171875, "posiy": 49.95703125, "stime": 1.3918461799621582}, {"opera": "move", "posix": 40.265625, "posiy": 49.7109375, "stime": 1.4084420204162598}, {"opera": "move", "posix": 40.01953125, "posiy": 49.7109375, "stime": 1.4247710704803467}, {"opera": "click", "posix": 39.7578125, "posiy": 49.0, "button": "Button.left", "stime": 1.9896900653839111}, {"opera": "move", "posix": 40.68359375, "posiy": 49.0, "stime": 2.5097241401672363}, {"opera": "move", "posix": 50.46875, "posiy": 52.64453125, "stime": 2.534119129180908}, {"opera": "move", "posix": 59.15234375, "posiy": 55.125, "stime": 2.542290210723877}, {"opera": "move", "posix": 206.94140625, "posiy": 128.57421875, "stime": 2.6293981075286865}, {"opera": "move", "posix": 301.4609375, "posiy": 183.03515625, "stime": 2.7294020652770996}, {"opera": "move", "posix": 311.84765625, "posiy": 190.70703125, "stime": 2.7623841762542725}, {"opera": "move", "posix": 315.3671875, "posiy": 193.484375, "stime": 2.787252902984619}, {"opera": "move", "posix": 316.84375, "posiy": 194.58984375, "stime": 2.795323133468628}, {"opera": "move", "posix": 318.3203125, "posiy": 195.6953125, "stime": 2.8034679889678955}, {"opera": "move", "posix": 323.75, "posiy": 201.125, "stime": 2.8197250366210938}, {"opera": "move", "posix": 335.46875, "posiy": 214.41796875, "stime": 2.8441340923309326}, {"opera": "move", "posix": 349.78515625, "posiy": 231.578125, "stime": 2.868657112121582}, {"opera": "move", "posix": 378.0546875, "posiy": 285.54296875, "stime": 2.933867931365967}, {"opera": "move", "posix": 380.484375, "posiy": 294.15625, "stime": 2.950093984603882}, {"opera": "move", "posix": 381.92578125, "posiy": 298.48046875, "stime": 2.9585421085357666}, {"opera": "move", "posix": 383.1953125, "posiy": 301.4453125, "stime": 2.9670262336730957}, {"opera": "move", "posix": 385.0390625, "posiy": 305.1328125, "stime": 2.9831290245056152}, {"opera": "move", "posix": 389.72265625, "posiy": 307.28515625, "stime": 3.006901979446411}, {"opera": "move", "posix": 410.41015625, "posiy": 297.54296875, "stime": 3.056274890899658}, {"opera": "move", "posix": 428.44921875, "posiy": 284.62109375, "stime": 3.088658094406128}, {"opera": "move", "posix": 436.6484375, "posiy": 277.26171875, "stime": 3.113708019256592}, {"opera": "move", "posix": 443.359375, "posiy": 269.36328125, "stime": 3.154026985168457}, {"opera": "move", "posix": 444.296875, "posiy": 265.796875, "stime": 3.18713116645813}, {"opera": "move", "posix": 444.296875, "posiy": 261.21484375, "stime": 3.211130142211914}, {"opera": "move", "posix": 444.296875, "posiy": 260.2734375, "stime": 3.21937894821167}, {"opera": "move", "posix": 462.71875, "posiy": 240.8984375, "stime": 3.350180149078369}, {"opera": "move", "posix": 464.2265625, "posiy": 240.58203125, "stime": 3.3664419651031494}, {"opera": "click", "posix": 464.7421875, "posiy": 240.3203125, "button": "Button.left", "stime": 3.5565831661224365}, {"opera": "move", "posix": 468.41015625, "posiy": 240.3203125, "stime": 3.668297052383423}, {"opera": "move", "posix": 480.43359375, "posiy": 240.3203125, "stime": 3.6839869022369385}, {"opera": "move", "posix": 499.02734375, "posiy": 240.3203125, "stime": 3.69197416305542}, {"opera": "move", "posix": 522.16015625, "posiy": 240.3203125, "stime": 3.7094080448150635}, {"opera": "move", "posix": 552.4140625, "posiy": 240.3203125, "stime": 3.716722249984741}, {"opera": "move", "posix": 613.79296875, "posiy": 240.3203125, "stime": 3.7408602237701416}, {"opera": "move", "posix": 642.109375, "posiy": 240.3203125, "stime": 3.7574830055236816}, {"opera": "move", "posix": 678.74609375, "posiy": 240.3203125, "stime": 3.782135009765625}, {"opera": "move", "posix": 688.46875, "posiy": 240.3203125, "stime": 3.791386127471924}, {"opera": "move", "posix": 697.15234375, "posiy": 240.3203125, "stime": 3.7986180782318115}, {"opera": "move", "posix": 706.0234375, "posiy": 240.3203125, "stime": 3.814743995666504}, {"opera": "move", "posix": 714.9921875, "posiy": 240.3203125, "stime": 3.8399369716644287}, {"opera": "move", "posix": 719.5625, "posiy": 240.3203125, "stime": 3.863948106765747}, {"opera": "move", "posix": 720.5, "posiy": 240.3203125, "stime": 3.8729820251464844}, {"opera": "move", "posix": 722.96484375, "posiy": 240.05859375, "stime": 3.939316987991333}] 2 | -------------------------------------------------------------------------------- /css/gridstack.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * gridstack 2.1.0 required CSS for default 12 and 1 column Mode size. Use gridstack-extra.css for column [2-11], else see https://github.com/gridstack/gridstack.js#custom-columns-css 3 | * https://gridstackjs.com/ 4 | * (c) 2014-2020 Alain Dumesny, Dylan Weiss, Pavel Reznikov 5 | * gridstack.js may be freely distributed under the MIT license. 6 | */:root .grid-stack-item>.ui-resizable-handle{filter:none}.grid-stack{position:relative}.grid-stack.grid-stack-rtl{direction:ltr}.grid-stack.grid-stack-rtl>.grid-stack-item{direction:rtl}.grid-stack .grid-stack-placeholder>.placeholder-content{border:1px dashed #d3d3d3;margin:0;position:absolute;width:auto;z-index:0!important;text-align:center}.grid-stack>.grid-stack-item{min-width:8.3333333333%;position:absolute;padding:0}.grid-stack>.grid-stack-item>.grid-stack-item-content{margin:0;position:absolute;width:auto;overflow-x:hidden;overflow-y:auto}.grid-stack>.grid-stack-item>.ui-resizable-handle{position:absolute;font-size:.1px;display:block;-ms-touch-action:none;touch-action:none}.grid-stack>.grid-stack-item.ui-resizable-autohide>.ui-resizable-handle,.grid-stack>.grid-stack-item.ui-resizable-disabled>.ui-resizable-handle{display:none}.grid-stack>.grid-stack-item.ui-draggable-dragging,.grid-stack>.grid-stack-item.ui-resizable-resizing{z-index:100}.grid-stack>.grid-stack-item.ui-draggable-dragging>.grid-stack-item-content,.grid-stack>.grid-stack-item.ui-resizable-resizing>.grid-stack-item-content{box-shadow:1px 4px 6px rgba(0,0,0,.2);opacity:.8}.grid-stack>.grid-stack-item>.ui-resizable-se,.grid-stack>.grid-stack-item>.ui-resizable-sw{background-image:url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTYuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iQ2FwYV8xIiB4PSIwcHgiIHk9IjBweCIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCIgdmlld0JveD0iMCAwIDUxMS42MjYgNTExLjYyNyIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTExLjYyNiA1MTEuNjI3OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+CjxnPgoJPHBhdGggZD0iTTMyOC45MDYsNDAxLjk5NGgtMzYuNTUzVjEwOS42MzZoMzYuNTUzYzQuOTQ4LDAsOS4yMzYtMS44MDksMTIuODQ3LTUuNDI2YzMuNjEzLTMuNjE1LDUuNDIxLTcuODk4LDUuNDIxLTEyLjg0NSAgIGMwLTQuOTQ5LTEuODAxLTkuMjMxLTUuNDI4LTEyLjg1MWwtNzMuMDg3LTczLjA5QzI2NS4wNDQsMS44MDksMjYwLjc2LDAsMjU1LjgxMywwYy00Ljk0OCwwLTkuMjI5LDEuODA5LTEyLjg0Nyw1LjQyNCAgIGwtNzMuMDg4LDczLjA5Yy0zLjYxOCwzLjYxOS01LjQyNCw3LjkwMi01LjQyNCwxMi44NTFjMCw0Ljk0NiwxLjgwNyw5LjIyOSw1LjQyNCwxMi44NDVjMy42MTksMy42MTcsNy45MDEsNS40MjYsMTIuODUsNS40MjYgICBoMzYuNTQ1djI5Mi4zNThoLTM2LjU0MmMtNC45NTIsMC05LjIzNSwxLjgwOC0xMi44NSw1LjQyMWMtMy42MTcsMy42MjEtNS40MjQsNy45MDUtNS40MjQsMTIuODU0ICAgYzAsNC45NDUsMS44MDcsOS4yMjcsNS40MjQsMTIuODQ3bDczLjA4OSw3My4wODhjMy42MTcsMy42MTcsNy44OTgsNS40MjQsMTIuODQ3LDUuNDI0YzQuOTUsMCw5LjIzNC0xLjgwNywxMi44NDktNS40MjQgICBsNzMuMDg3LTczLjA4OGMzLjYxMy0zLjYyLDUuNDIxLTcuOTAxLDUuNDIxLTEyLjg0N2MwLTQuOTQ4LTEuODA4LTkuMjMyLTUuNDIxLTEyLjg1NCAgIEMzMzguMTQyLDQwMy44MDIsMzMzLjg1Nyw0MDEuOTk0LDMyOC45MDYsNDAxLjk5NHoiIGZpbGw9IiM2NjY2NjYiLz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8Zz4KPC9nPgo8L3N2Zz4K);background-repeat:no-repeat;background-position:center;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-ms-transform:rotate(45deg);-o-transform:rotate(45deg);transform:rotate(45deg)}.grid-stack>.grid-stack-item>.ui-resizable-se{-webkit-transform:rotate(-45deg);-moz-transform:rotate(-45deg);-ms-transform:rotate(-45deg);-o-transform:rotate(-45deg);transform:rotate(-45deg)}.grid-stack>.grid-stack-item>.ui-resizable-nw{cursor:nw-resize;width:20px;height:20px;top:0}.grid-stack>.grid-stack-item>.ui-resizable-n{cursor:n-resize;height:10px;top:0;left:25px;right:25px}.grid-stack>.grid-stack-item>.ui-resizable-ne{cursor:ne-resize;width:20px;height:20px;top:0}.grid-stack>.grid-stack-item>.ui-resizable-e{cursor:e-resize;width:10px;top:15px;bottom:15px}.grid-stack>.grid-stack-item>.ui-resizable-se{cursor:se-resize;width:20px;height:20px}.grid-stack>.grid-stack-item>.ui-resizable-s{cursor:s-resize;height:10px;left:25px;bottom:0;right:25px}.grid-stack>.grid-stack-item>.ui-resizable-sw{cursor:sw-resize;width:20px;height:20px;bottom:0}.grid-stack>.grid-stack-item>.ui-resizable-w{cursor:w-resize;width:10px;top:15px;bottom:15px}.grid-stack>.grid-stack-item.ui-draggable-dragging>.ui-resizable-handle{display:none!important}.grid-stack>.grid-stack-item[data-gs-width='1']{width:8.3333333333%}.grid-stack>.grid-stack-item[data-gs-x='1']{left:8.3333333333%}.grid-stack>.grid-stack-item[data-gs-min-width='1']{min-width:8.3333333333%}.grid-stack>.grid-stack-item[data-gs-max-width='1']{max-width:8.3333333333%}.grid-stack>.grid-stack-item[data-gs-width='2']{width:16.6666666667%}.grid-stack>.grid-stack-item[data-gs-x='2']{left:16.6666666667%}.grid-stack>.grid-stack-item[data-gs-min-width='2']{min-width:16.6666666667%}.grid-stack>.grid-stack-item[data-gs-max-width='2']{max-width:16.6666666667%}.grid-stack>.grid-stack-item[data-gs-width='3']{width:25%}.grid-stack>.grid-stack-item[data-gs-x='3']{left:25%}.grid-stack>.grid-stack-item[data-gs-min-width='3']{min-width:25%}.grid-stack>.grid-stack-item[data-gs-max-width='3']{max-width:25%}.grid-stack>.grid-stack-item[data-gs-width='4']{width:33.3333333333%}.grid-stack>.grid-stack-item[data-gs-x='4']{left:33.3333333333%}.grid-stack>.grid-stack-item[data-gs-min-width='4']{min-width:33.3333333333%}.grid-stack>.grid-stack-item[data-gs-max-width='4']{max-width:33.3333333333%}.grid-stack>.grid-stack-item[data-gs-width='5']{width:41.6666666667%}.grid-stack>.grid-stack-item[data-gs-x='5']{left:41.6666666667%}.grid-stack>.grid-stack-item[data-gs-min-width='5']{min-width:41.6666666667%}.grid-stack>.grid-stack-item[data-gs-max-width='5']{max-width:41.6666666667%}.grid-stack>.grid-stack-item[data-gs-width='6']{width:50%}.grid-stack>.grid-stack-item[data-gs-x='6']{left:50%}.grid-stack>.grid-stack-item[data-gs-min-width='6']{min-width:50%}.grid-stack>.grid-stack-item[data-gs-max-width='6']{max-width:50%}.grid-stack>.grid-stack-item[data-gs-width='7']{width:58.3333333333%}.grid-stack>.grid-stack-item[data-gs-x='7']{left:58.3333333333%}.grid-stack>.grid-stack-item[data-gs-min-width='7']{min-width:58.3333333333%}.grid-stack>.grid-stack-item[data-gs-max-width='7']{max-width:58.3333333333%}.grid-stack>.grid-stack-item[data-gs-width='8']{width:66.6666666667%}.grid-stack>.grid-stack-item[data-gs-x='8']{left:66.6666666667%}.grid-stack>.grid-stack-item[data-gs-min-width='8']{min-width:66.6666666667%}.grid-stack>.grid-stack-item[data-gs-max-width='8']{max-width:66.6666666667%}.grid-stack>.grid-stack-item[data-gs-width='9']{width:75%}.grid-stack>.grid-stack-item[data-gs-x='9']{left:75%}.grid-stack>.grid-stack-item[data-gs-min-width='9']{min-width:75%}.grid-stack>.grid-stack-item[data-gs-max-width='9']{max-width:75%}.grid-stack>.grid-stack-item[data-gs-width='10']{width:83.3333333333%}.grid-stack>.grid-stack-item[data-gs-x='10']{left:83.3333333333%}.grid-stack>.grid-stack-item[data-gs-min-width='10']{min-width:83.3333333333%}.grid-stack>.grid-stack-item[data-gs-max-width='10']{max-width:83.3333333333%}.grid-stack>.grid-stack-item[data-gs-width='11']{width:91.6666666667%}.grid-stack>.grid-stack-item[data-gs-x='11']{left:91.6666666667%}.grid-stack>.grid-stack-item[data-gs-min-width='11']{min-width:91.6666666667%}.grid-stack>.grid-stack-item[data-gs-max-width='11']{max-width:91.6666666667%}.grid-stack>.grid-stack-item[data-gs-width='12']{width:100%}.grid-stack>.grid-stack-item[data-gs-x='12']{left:100%}.grid-stack>.grid-stack-item[data-gs-min-width='12']{min-width:100%}.grid-stack>.grid-stack-item[data-gs-max-width='12']{max-width:100%}.grid-stack.grid-stack-1>.grid-stack-item{min-width:100%}.grid-stack.grid-stack-1>.grid-stack-item[data-gs-width='1']{width:100%}.grid-stack.grid-stack-1>.grid-stack-item[data-gs-x='1']{left:100%}.grid-stack.grid-stack-1>.grid-stack-item[data-gs-min-width='1']{min-width:100%}.grid-stack.grid-stack-1>.grid-stack-item[data-gs-max-width='1']{max-width:100%}.grid-stack.grid-stack-animate,.grid-stack.grid-stack-animate .grid-stack-item{-webkit-transition:left .3s,top .3s,height .3s,width .3s;-moz-transition:left .3s,top .3s,height .3s,width .3s;-ms-transition:left .3s,top .3s,height .3s,width .3s;-o-transition:left .3s,top .3s,height .3s,width .3s;transition:left .3s,top .3s,height .3s,width .3s}.grid-stack.grid-stack-animate .grid-stack-item.grid-stack-placeholder,.grid-stack.grid-stack-animate .grid-stack-item.ui-draggable-dragging,.grid-stack.grid-stack-animate .grid-stack-item.ui-resizable-resizing{-webkit-transition:left 0s,top 0s,height 0s,width 0s;-moz-transition:left 0s,top 0s,height 0s,width 0s;-ms-transition:left 0s,top 0s,height 0s,width 0s;-o-transition:left 0s,top 0s,height 0s,width 0s;transition:left 0s,top 0s,height 0s,width 0s} -------------------------------------------------------------------------------- /js/external/clipboard.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * clipboard.js v2.0.6 3 | * https://clipboardjs.com/ 4 | * 5 | * Licensed MIT © Zeno Rocha 6 | */ 7 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.ClipboardJS=e():t.ClipboardJS=e()}(this,function(){return o={},r.m=n=[function(t,e){t.exports=function(t){var e;if("SELECT"===t.nodeName)t.focus(),e=t.value;else if("INPUT"===t.nodeName||"TEXTAREA"===t.nodeName){var n=t.hasAttribute("readonly");n||t.setAttribute("readonly",""),t.select(),t.setSelectionRange(0,t.value.length),n||t.removeAttribute("readonly"),e=t.value}else{t.hasAttribute("contenteditable")&&t.focus();var o=window.getSelection(),r=document.createRange();r.selectNodeContents(t),o.removeAllRanges(),o.addRange(r),e=o.toString()}return e}},function(t,e){function n(){}n.prototype={on:function(t,e,n){var o=this.e||(this.e={});return(o[t]||(o[t]=[])).push({fn:e,ctx:n}),this},once:function(t,e,n){var o=this;function r(){o.off(t,r),e.apply(n,arguments)}return r._=e,this.on(t,r,n)},emit:function(t){for(var e=[].slice.call(arguments,1),n=((this.e||(this.e={}))[t]||[]).slice(),o=0,r=n.length;o *')); 195 | if (this.settings.draggable !== false) { 196 | this.draggable(); 197 | } 198 | return this; 199 | } 200 | 201 | ordinalize($elements) { 202 | var $element, i, j, ref, results1; 203 | results1 = []; 204 | for (i = j = 0, ref = $elements.length; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) { 205 | $element = $($elements[i]); 206 | results1.push($element.data('position', i)); 207 | } 208 | return results1; 209 | } 210 | 211 | reordinalize($element, position) { 212 | return $element.data('position', position); 213 | } 214 | 215 | $(selector) { 216 | return this.$el.find(selector); 217 | } 218 | 219 | compare(d, s) { 220 | if (d.y > s.y + s.h) { 221 | return +1; 222 | } 223 | if (s.y > d.y + d.h) { 224 | return -1; 225 | } 226 | if ((d.x + (d.w / 2)) > (s.x + (s.w / 2))) { 227 | return +1; 228 | } 229 | if ((s.x + (s.w / 2)) > (d.x + (d.w / 2))) { 230 | return -1; 231 | } 232 | return 0; 233 | } 234 | 235 | draggable(method) { 236 | if (this._draggable == null) { 237 | this._draggable = new Draggable(this.$el, this.settings.draggable.selector, { 238 | began: this.draggingBegan, 239 | ended: this.draggingEnded, 240 | moved: this.draggingMoved 241 | }); 242 | } 243 | if (method != null) { 244 | return this._draggable[method](); 245 | } 246 | } 247 | 248 | $sorted($elements) { 249 | return ($elements || this.$('> *')).sort(function(a, b) { 250 | var $a, $b, aPosition, aPositionInt, bPosition, bPositionInt; 251 | $a = $(a); 252 | $b = $(b); 253 | aPosition = $a.data('position'); 254 | bPosition = $b.data('position'); 255 | aPositionInt = parseInt(aPosition); 256 | bPositionInt = parseInt(bPosition); 257 | if ((aPosition != null) && (bPosition == null)) { 258 | return -1; 259 | } 260 | if ((bPosition != null) && (aPosition == null)) { 261 | return +1; 262 | } 263 | if (!aPosition && !bPosition && $a.index() < $b.index()) { 264 | return -1; 265 | } 266 | if (!bPosition && !aPosition && $b.index() < $a.index()) { 267 | return +1; 268 | } 269 | if (aPositionInt < bPositionInt) { 270 | return -1; 271 | } 272 | if (bPositionInt < aPositionInt) { 273 | return +1; 274 | } 275 | return 0; 276 | }); 277 | } 278 | 279 | draggingBegan(event) { 280 | var $elements, ref, ref1; 281 | $elements = this.$sorted(); 282 | this.ordinalize($elements); 283 | setTimeout(this.layout, 0); 284 | return (ref = this.settings) != null ? (ref1 = ref.callbacks) != null ? typeof ref1.reordering === "function" ? ref1.reordering($elements) : void 0 : void 0 : void 0; 285 | } 286 | 287 | draggingEnded(event) { 288 | var $elements, ref, ref1; 289 | $elements = this.$sorted(); 290 | this.ordinalize($elements); 291 | setTimeout(this.layout, 0); 292 | return (ref = this.settings) != null ? (ref1 = ref.callbacks) != null ? typeof ref1.reordered === "function" ? ref1.reordered($elements, this._draggable.dragged) : void 0 : void 0 : void 0; 293 | } 294 | 295 | draggingMoved(event) { 296 | var $dragging, $elements, element, i, index, j, k, len, original, positions, ref, ref1, ref2; 297 | $dragging = $(event.target).closest(this.$(this.settings.draggable.selector)); 298 | $elements = this.$sorted(this.$(this.settings.draggable.selector)); 299 | positions = this.structure($elements).positions; 300 | original = index = $dragging.data('position'); 301 | ref = positions.filter(function(position) { 302 | return position.$element.is($dragging); 303 | }); 304 | for (j = 0, len = ref.length; j < len; j++) { 305 | element = ref[j]; 306 | element.x = $dragging.position().left; 307 | element.y = $dragging.position().top; 308 | element.w = $dragging.data('width') || $dragging.outerWidth(); 309 | element.h = $dragging.data('height') || $dragging.outerHeight(); 310 | } 311 | positions.sort(this.compare); 312 | $elements = positions.map(function(position) { 313 | return position.$element; 314 | }); 315 | $elements = (((ref1 = this.settings.callbacks) != null ? ref1.optimize : void 0) || this.optimize)($elements); 316 | for (i = k = 0, ref2 = $elements.length; (0 <= ref2 ? k < ref2 : k > ref2); i = 0 <= ref2 ? ++k : --k) { 317 | this.reordinalize($($elements[i]), i); 318 | } 319 | return this.layout(); 320 | } 321 | 322 | size($element) { 323 | return (($element.data('width') || $element.outerWidth()) + this.settings.gutter) / (this.settings.base + this.settings.gutter); 324 | } 325 | 326 | position($element, columns) { 327 | var column, height, i, j, k, max, ref, ref1, ref2, size; 328 | size = this.size($element); 329 | height = 2e308; 330 | column = 0; 331 | for (i = j = 0, ref = columns.length - size; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) { 332 | max = Math.max(...columns.slice(i, (i + size))); 333 | if (max < height) { 334 | height = max; 335 | column = i; 336 | } 337 | } 338 | for (i = k = ref1 = column, ref2 = column + size; (ref1 <= ref2 ? k < ref2 : k > ref2); i = ref1 <= ref2 ? ++k : --k) { 339 | columns[i] = height + ($element.data('height') || $element.outerHeight()) + this.settings.gutter; 340 | } 341 | return { 342 | x: column * (this.settings.base + this.settings.gutter), 343 | y: height 344 | }; 345 | } 346 | 347 | structure($elements = this.$sorted()) { 348 | var $element, columns, i, index, j, position, positions, ref; 349 | positions = []; 350 | columns = (function() { 351 | var j, ref, results1; 352 | results1 = []; 353 | for (i = j = 0, ref = this.settings.columns; (0 <= ref ? j <= ref : j >= ref); i = 0 <= ref ? ++j : --j) { 354 | results1.push(0); 355 | } 356 | return results1; 357 | }).call(this); 358 | for (index = j = 0, ref = $elements.length; (0 <= ref ? j < ref : j > ref); index = 0 <= ref ? ++j : --j) { 359 | $element = $($elements[index]); 360 | position = this.position($element, columns); 361 | positions.push({ 362 | x: position.x, 363 | y: position.y, 364 | w: $element.data('width') || $element.outerWidth(), 365 | h: $element.data('height') || $element.outerHeight(), 366 | $element: $element 367 | }); 368 | } 369 | return { 370 | height: Math.max(...columns), 371 | positions: positions 372 | }; 373 | } 374 | 375 | layout() { 376 | var $element, $elements, index, j, position, ref, ref1, structure; 377 | $elements = (((ref = this.settings.callbacks) != null ? ref.optimize : void 0) || this.optimize)(this.$sorted()); 378 | structure = this.structure($elements); 379 | for (index = j = 0, ref1 = $elements.length; (0 <= ref1 ? j < ref1 : j > ref1); index = 0 <= ref1 ? ++j : --j) { 380 | $element = $($elements[index]); 381 | position = structure.positions[index]; 382 | if ($element.is('.dragging')) { 383 | continue; 384 | } 385 | $element.css({ 386 | position: 'absolute', 387 | left: position.x, 388 | top: position.y 389 | }); 390 | } 391 | return this.$el.css({ 392 | height: structure.height 393 | }); 394 | } 395 | 396 | optimize(originals) { 397 | var columns, index, j, ref, results; 398 | results = []; 399 | columns = 0; 400 | while (originals.length > 0) { 401 | if (columns === this.settings.columns) { 402 | columns = 0; 403 | } 404 | index = 0; 405 | for (index = j = 0, ref = originals.length; (0 <= ref ? j < ref : j > ref); index = 0 <= ref ? ++j : --j) { 406 | if (!(columns + this.size($(originals[index])) > this.settings.columns)) { 407 | break; 408 | } 409 | } 410 | if (index === originals.length) { 411 | index = 0; 412 | columns = 0; 413 | } 414 | columns += this.size($(originals[index])); 415 | // Move from originals into results 416 | results.push(originals.splice(index, 1)[0]); 417 | } 418 | return results; 419 | } 420 | 421 | }; 422 | 423 | Gridly.settings = { 424 | base: 60, 425 | gutter: 20, 426 | columns: 12, 427 | draggable: { 428 | zIndex: 800, 429 | selector: '> *' 430 | } 431 | }; 432 | 433 | return Gridly; 434 | 435 | }).call(this); 436 | 437 | $.fn.extend({ 438 | gridly: function(option = {}, ...parameters) { 439 | return this.each(function() { 440 | var $this, action, options; 441 | $this = $(this); 442 | options = $.extend({}, $.fn.gridly.defaults, typeof option === "object" && option); 443 | action = typeof option === "string" ? option : option.action; 444 | if (action == null) { 445 | action = "layout"; 446 | } 447 | return Gridly.gridly($this, options)[action](parameters); 448 | }); 449 | } 450 | }); 451 | 452 | }).call(this); 453 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web_robot 2 | 自动化网页操作机器人 3 | 4 | ![图片](http://blog.ganjiacheng.cn/img/mypost/2021/1-1.jpg) 5 | 6 | ## 详细说明 7 | 8 | 请见博客 9 | [Web Robot使用教程(终极版)](http://ganjiacheng.cn/article/2021/article_3_WEB_ROBOT%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3(%E7%BB%88%E6%9E%81%E7%89%88)/) 10 | [看板教程](http://ganjiacheng.cn/article/article_31_chrome%E6%8F%92%E4%BB%B6-WEB-ROBOT%E4%B9%8B%E6%88%91%E7%9A%84%E7%9C%8B%E6%9D%BF/) 11 | [使用教程V1.0版本](http://ganjiacheng.cn/article/article_18_chrome%E6%8F%92%E4%BB%B6-%E7%BD%91%E9%A1%B5%E8%87%AA%E5%8A%A8%E5%8C%96/) 12 | [持续更新教程](http://ganjiacheng.cn/article/article_21_chrome%E6%8F%92%E4%BB%B6-WEB-ROBOT/) 13 | 14 | 15 | ## 已有功能 16 | 1. 管理多个事务,每个事务有多个事件,每个事件对应一种操作 17 | 2. 新增事件中方便的页面元素筛选器,querySelect自由筛选器 18 | 3. 可以测试运行一个事件,运行一整个事务。 19 | 4. 支持事务的导入导出 20 | 5. 支持源码事务,写js源码并注入运行 21 | 6. 支持流程事务的受控运行,本地鼠标和键盘还原事件。 22 | 7. 支持受控事务,实现键鼠录制和还原 23 | 8. 支持元素筛选和执行时的自动定位 24 | 9. 支持设值事件作为运行前自定义参数${value} 25 | 10. 支持页面直接添加事件 26 | 11. 支持定时运行 27 | 12. 支持源码事务的开启直接注入 28 | 13. 支持流程取值事件,取到的值对当次流程有效 29 | 14. 支持流程事件的直接录制 30 | 15. 页面添加事件中优秀的可视化圈选 31 | 16. 支持值选择器 32 | 17. 支持dom自旋检查 33 | 18. 自定义看板,看板简易模式 34 | 19. 支持配置并发页面级爬虫 35 | 20. 支持爬虫与客户端数据交互 36 | 21. 支持消息通知事件 37 | 22. 支持流程事务在后台运行 38 | 23. 支持页面元素数据监控配置 39 | 24. 支持单节点监控与多节点监控 40 | 25. 支持快捷键 41 | 26. 支持流程事务的跳转 42 | 43 | ## 核心部分--事务和运行机制说明 44 | 45 | 新建事务分为三种事务,流程事务,源码事务,受控事务; 46 | (事务功能场景互相有重叠又互相有补充,详情见下面使用场景) 47 | 运行分为运行,定时运行,受控运行,轮播,开启注入; 48 | 49 | - 流程事务:通过dom定义事件。 50 | - 运行 (运行一次,运行在浏览器后台,使用浏览器事件) 51 | - 受控运行 (运行一次,运行在本地客户端,控制鼠标键盘还原对应事件) 52 | - 定时运行 (定时运行,运行在浏览器后台,有两种定时模式每日和每隔,使用浏览器事件) 53 | - 轮播 (循环运行事件,运行在插件页,插件页关闭即停止,使用浏览器事件) 54 | 55 | - 源码事务:通过源码定义事件,有正则地址匹配机制。 56 | - 运行 (运行一次,直接向目前页注入代码) 57 | - 定时运行 (定时运行,运行在浏览器后台,向当前页注入固定代码) 58 | - 开启注入 (打开页面时注入,直接匹配地址,进行注入) 59 | 60 | - 受控事务:通过鼠标键盘录制定义事件,可以受控运行, 61 | - 受控运行 (运行一次,运行于本地客户端,还原录制的鼠标键盘事件) 62 | 63 | 流程事务事件的定义包括 dom节点,事件,延时,设值: 64 | 65 | - 节点筛选器包含 66 | - 自定义节点筛选器 67 | - html标签筛选器 68 | 69 | - 事件包含 70 | - click 点击 71 | - value 设值 72 | - refresh 刷新 73 | - mouseover 鼠标移入 74 | - pagejump 当页跳转 75 | - newpage 新页打开 76 | - getvalue 取值 77 | - getcustomvalue 自定义获取数据 78 | - closepage 关闭页面 79 | - onlyshow 唯一展示(看板使用) 80 | - sendmessage 发送通知(使用有问题请看下方常见问题) 81 | 82 | 注:受控相关的都必须使用开启本地客户端。 83 | 84 | ## 使用方法 85 | 请认准这个为chrome插件,运行于chrome浏览器,或基于chromium的浏览器 86 | 1. 浏览器设置(三个点)--> 更多工具 --> 扩展程序 ↓ 87 | 2. 打开右上角开发者模式 --> 加载已解压的扩展程序 --> 选择clone下来的该项目根目录 ↓ 88 | 3. 弄完可关掉开发者模式 --> 右键项目图标 --> 检查可读取和更改网站数据 --> 在所有网站上 89 | 90 | ## 版本更新 91 | 92 | > git pull 93 | 94 | 在继续上面的1,2步骤 95 | 96 | ## 受控运行,需开启本地客户端web服务 97 | 98 | 0. 重要:**目前本地客户端只在mac系统上进行过测试**。 99 | 1. 首先准备一个python3虚拟环境,venv/ 放于项目根目录下,如有自己的python3,请修改py/web.py中的PYTHON_ENV 100 | - PYTHON_ENV = "./venv/bin/python" 101 | 2. pip下载 py/requirements.txt 里的包 102 | - pip install -r py/requirements.txt 103 | 3. 项目根目录下启动web服务 **python py/web.py** 104 | 4. 如果没反应,以mac举例,左上角的设置 -> 系统偏好设置 -> 安全性与隐私 -> 辅助功能 将开启web服务的应用(如iTerm)加入到里面 105 | 106 | ## 使用场景 107 | 108 | - 流程事务可以定义复杂重复的页面操作进行自动化,直接运行适用比如重复填写负责的表单,设值也可以运行时自定义参数。 109 | - 流程事务的定时运行可以适用每日签到,每日在网页处理某件同样的事。 110 | - 流程事务受控运行适用于前端做了特殊处理无法触发事件的情况,使用键盘鼠标模拟事件,必然可以触发。 111 | - 源码事务的的定时运行适用于如定时提醒喝水(alert)等 112 | - 源码事务的开始注入适用于如百度去广告的场景等 113 | - 受控事务的录制和受控运行适用于对一个复杂操作(无法用流程实现)的定义和复现。 114 | 115 | ## 常见问题 116 | 117 | ### 发消息事件无响应 118 | 1、在Chrome浏览器中访问地址:chrome://flags 119 | 2、搜索栏中搜索:notifications,找到 Enable system notifications 选项,将其选项值改为 Disabled 120 | 3、重启浏览器,问题解决。 121 | 122 | ## 演示用例,(直接复制,导入事务即可享用) 123 | 124 | - 基本操作(打开百度,搜索天气,点击确定) 125 | ```json 126 | {"case_name":"基本操作","case_process":[{"n":"0","opera":"newpage","tag":"body","value":"https://www.baidu.com/s?ie=UTF-8&wd=test","wait":"1"},{"n":"0","opera":"value","tag":"INPUT#kw","value":"天气","wait":"2"},{"n":"0","opera":"click","tag":"INPUT#su","value":"","wait":"1"}],"case_sourcecode":"","case_type":"process","control_url":"","sourcecode_url":".*"} 127 | ``` 128 | 129 | - 取值事件(打开个人博客页,获取内容赋值给title,打开百度,搜索刚刚获取到的title,点击搜索) 130 | ```json 131 | {"case_name":"取值事件用例","case_process":[{"n":"0","opera":"newpage","tag":"body","value":"http://blog.ganjiacheng.cn/","wait":"1"},{"bgopen":false,"check":true,"expr":"","n":"0","opera":"getvalue","parser":"text_parser","sysmsg":false,"tag":"HTML.macos.desktop.landscape > BODY > NAV.navbar.navbar-default.navbar-custom.navbar-fixed-top > DIV.container-fluid > DIV.navbar-header.page-scroll > A.navbar-brand","value":"title","wait":"1"},{"bgopen":false,"check":false,"expr":"","n":"0","opera":"pagejump","parser":"text_parser","sysmsg":false,"tag":"body","value":"https://www.baidu.com/s?ie=UTF-8&wd=test","wait":"2"},{"bgopen":false,"check":true,"expr":"","n":"0","opera":"value","parser":"text_parser","sysmsg":false,"tag":"INPUT#kw","value":"{title}","wait":"1"},{"bgopen":false,"check":true,"expr":"","n":"0","opera":"click","parser":"text_parser","sysmsg":false,"tag":"INPUT#su","value":"","wait":"1"}],"case_sourcecode":"","case_type":"process","control_url":"","sourcecode_url":".*"} 132 | ``` 133 | 134 | - 百度去广告(源码事务) 135 | ```json 136 | {"case_name":"百度去广告","case_process":[],"case_sourcecode":"Array.from(\n document.querySelectorAll('#content_left>div'))\n .forEach(el => \n />广告 {\n try{\n Array.from(\n document.querySelectorAll('#content_left>div'))\n .forEach(el => \n />广告 DIV.result-op.c-container.xpath-log > DIV.op_weather4_twoicon_container_div > DIV.op_weather4_twoicon > A.op_weather4_twoicon_today.OP_LOG_LINK","value":"key","wait":"1"},{"bgopen":false,"check":true,"expr":"","n":"0","opera":"sendmessage","parser":"text_parser","sysmsg":true,"tag":"DIV#wrapper_wrapper","value":"天气:{key}","wait":"0"}],"case_sourcecode":"","case_type":"process","control_url":"","fail_rerun":false,"last_runtime":1611820796375,"runtime":"","sourcecode_url":".*"} 162 | ``` 163 | 164 | - 线性爬虫 + 列表数据解析(初始化 - 打开博客页面;取数据 - 获取列表第一条数据,且配置列表解析;下一步 - 点击下一页) 165 | ```json 166 | {"add_dashboard":true,"case_name":"线性爬虫","case_process":[],"case_sourcecode":"","case_type":"serial_crawler","control_url":"","serial_crawler":{"api":"http://127.0.0.1:12580/crawler/","data":null,"fetch":[{"bgopen":false,"check":true,"expr":"new Date()","n":"0","opera":"getcustomvalue","parser":"text_parser","sysmsg":true,"tag":"body","value":"key","wait":"0.5"},{"bgopen":false,"check":true,"expr":"","n":"0","opera":"getvalue","parser":"list_parser","sysmsg":true,"tag":".post-title","value":"titles","wait":"0"}],"freq":10,"init":[{"bgopen":false,"check":false,"expr":"","n":"0","opera":"newpage","parser":"text_parser","sysmsg":true,"tag":"body","value":"https://coding-pages-bucket-3440936-7810273-13586-512516-1300444322.cos-website.ap-shanghai.myqcloud.com/","wait":"0"}],"next":[{"bgopen":false,"check":true,"expr":"","n":"0","opera":"click","parser":"text_parser","sysmsg":true,"tag":"li.next>a","value":"","wait":"0"}],"send":false,"times":5},"sourcecode_url":".*"} 167 | ``` 168 | 169 | - 单节点监控,监控单个元素变化(打开搜时间的百度页,配置监控展示时间的单个节点,变化时会有页面内消息通知)需打开某页面 170 | ```json 171 | {"case_name":"单个监控-时间","case_process":[],"case_sourcecode":"","case_type":"monitor","control_url":"","monitor":{"run":false,"selector":".result-op.c-container:nth-child(1)","url":"https://www.baidu.com/s?wd=%E6%97%B6%E9%97%B4"},"sourcecode_url":".*"} 172 | ``` 173 | 174 | - 多节点监控,监控多个节点的增量变化(打开微博热搜列表页,配置监控前20条增量变化,增加新数据时会有页面内消息通知)需打开某页面 175 | ```json 176 | {"case_name":"批量监控-热搜","case_process":[],"case_sourcecode":"","case_type":"monitor","control_url":"","monitor":{"run":false,"selector":"tr:nth-child(-n+22) td:nth-child(2) a","url":"https://s.weibo.com/top/summary?cate=realtimehot"},"sourcecode_url":".*"} 177 | ``` 178 | 179 | - 快捷键+选中传参跳转+流程事务(快捷键为lp,选中项默认为{SELECT}) 180 | ```json 181 | {"case_name":"快捷键+选中传参跳转+流程事务","case_process":[{"bgopen":false,"check":false,"expr":"","n":"1","opera":"newpage","parser":"text_parser","sysmsg":true,"tag":"a","value":"https://www.baidu.com/s?ie=UTF-8&wd={SELECT}","wait":"0"}],"case_sourcecode":"","case_type":"process","control_url":"","last_runtime":1655450299124,"short_key":"l,p","sourcecode_url":".*"} 182 | ``` 183 | 184 | - 快捷键+复制传参跳转+源码事务(快捷键为qw,复制默认为{COPY}) 185 | ```json 186 | {"case_name":"快捷键+复制传参跳转+源码事务","case_process":[],"case_sourcecode":"window.open(\"https://www.baidu.com/s?ie=UTF-8&wd={COPY}\")","case_type":"sourcecode","control_url":"","last_runtime":1657184059039,"short_key":"q,w","sourcecode_url":".*"} 187 | ``` 188 | 189 | - 页面有iframe的流程事件(获取iframe的内容,并发消息) 190 | ```json 191 | {"case_name":"页面有iframe的流程事件","case_process":[{"bgopen":false,"check":false,"expr":"","id":1,"iframe":"TopFrame","jumpto":"","opera":"newpage","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"https://www.runoob.com/try/try.php?filename=tryhtml_intro","wait":"0"},{"bgopen":false,"check":true,"expr":"","id":2,"iframe":"iframe&0","jumpto":"","n":"0","opera":"getvalue","parser":"text_parser","sysmsg":true,"tag":"h1","value":"iframe内文本","wait":"0"},{"bgopen":false,"check":false,"expr":"","id":3,"iframe":"TopFrame","jumpto":"","n":"undefined","opera":"sendmessage","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"iframe内文本:{iframe内文本}","wait":"0"},{"bgopen":false,"check":false,"expr":"","id":4,"iframe":"TopFrame","jumpto":"","n":"undefined","opera":"closepage","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"","wait":"1"}],"case_sourcecode":"","case_type":"process","control_url":"","sourcecode_url":".*"} 192 | ``` 193 | 194 | - 流程事务跳转事件(0-12点返回消息早上好,12-24点返回消息下午好) 195 | ```json 196 | {"case_name":"跳转事件演示","case_process":[{"bgopen":false,"check":false,"expr":"new Date().getHours()","id":1,"iframe":null,"jumpto":"","opera":"getcustomvalue","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"hour","wait":"0"},{"bgopen":false,"check":false,"expr":"{hour} >= 12","id":2,"iframe":null,"jumpto":"4","n":"undefined","opera":"processjump","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"","wait":"0"},{"bgopen":false,"check":false,"expr":"","id":3,"iframe":null,"jumpto":"","opera":"sendmessage","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"上午好","wait":"0"},{"bgopen":false,"check":false,"expr":"1==1","id":5,"iframe":null,"jumpto":"-1","opera":"processjump","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"","wait":"0"},{"bgopen":false,"check":false,"expr":"","id":4,"iframe":null,"jumpto":"","opera":"sendmessage","parser":"text_parser","sysmsg":true,"tag":"空标签","value":"下午好","wait":"0"}],"case_sourcecode":"","case_type":"process","control_url":"","sourcecode_url":".*"} 197 | ``` 198 | 199 | 200 | - 演示1 201 | 202 | ![演示1](http://blog.ganjiacheng.cn/img/mypost/robot_demo1.gif) 203 | 204 | - 演示2 205 | 206 | ![演示2](http://blog.ganjiacheng.cn/img/mypost/robot_demo2.gif) 207 | 208 | - 演示3 209 | 210 | ![演示3](http://blog.ganjiacheng.cn/img/mypost/robot_demo3.gif) 211 | 212 | ## 版本迭代 213 | 214 | v0.1 (2019.08.14) 215 | 1. 完成初始第一版,管理流程事务 216 | 217 | v1.0 (2020.05.13)(重构更新) 218 | 1. 管理多个事务,每个事务有多个过程,每个过程对应一种操作 219 | 2. 新增操作中方便的页面元素筛选器,css/id筛选器 220 | 3. 测试运行一个过程,运行一个事务,运行转为background后台 221 | 4. 支持事务的导入导出 222 | 223 | v1.1 (2020.05.28) (数据与上版本不兼容) 224 | 1. 支持源码事务 225 | 226 | v1.2 (2020.06.01) 227 | 1. 新增事务受控运行模式,运行于background中 228 | 2. 新增本地web服务,用于鼠标键盘模拟流程的受控执行 229 | 230 | v1.2.1 (2020.06.03) 231 | 1. 删除事务新增校验 232 | 2. 实现流程中事件的复制,移动,编辑 233 | 234 | v1.3 (2020.06.05) 235 | 1. 新增受控事务 236 | 2. 本地客户端实现受控事务的键鼠事件录制,存储和还原 237 | 238 | v1.3.1 (2020.06.06) 239 | 1. 优化元素筛选器进行自动定位 240 | 2. 优化流程事件运行和受控运行的自动定位 241 | 242 | v1.4.0 (2020.06.07) 243 | 1. 改class/id筛选器为自由筛选器 244 | 2. 使用promise重构流程执行 245 | 3. 新增执行前自定义参数 246 | 4. 新增本地服务检查 247 | 248 | v1.5.0 (2020.06.08) 249 | 1. 新增网页直接添加流程事件的方式 250 | 251 | v1.6.0 (2020.06.09) 252 | 1. 新增定时运行, 仅支持流程事务 253 | 254 | v1.6.1 (2020.06.10) 255 | 1. 修改源码事务,可进行路由匹配检查 256 | 2. 新增源码事务的定时运行 257 | 258 | v1.6.2 (2020.06.13) 259 | 1. 优化运行元素的定位 260 | 2. 新增源码事务直接注入的开启与关闭 261 | 262 | v1.6.3 (2020.06.22) 263 | 1. 新增鼠标移入事件操作 264 | 265 | v1.7.0 (2020.06.29) 266 | 1. 修复部分bug 267 | 2. 流程事务新增取值事件,支持运行,受控运行与轮播 268 | 269 | v1.7.1 (2020.07.05) 270 | 1. 新增流程事件页面直接录制 271 | 2. 关闭页面添加事件入口 272 | 273 | V1.8.0 (2020.07.20) 274 | 1. 新增页面添加事件的可视化圈选 275 | 2. 重构页面事件定义的iframe嵌入页 276 | 277 | V1.8.1 (2020.08.09) 278 | 1. 流程事务支持新页面跳转操作 279 | 2. 主页默认根据创建时间展示列表,支持移位 280 | 281 | V1.8.2 (2020.08.15) 282 | 1. 新用户初始化数据修复 283 | 2. 新增重命名操作 284 | 285 | V1.8.3(2020.09.18) 286 | 1. 支持值选择器 287 | 288 | V1.9.0(2020.10.26) 289 | 1. 重构运行流程事务,增加dom检查自旋支持 290 | 2. 新增页面消息提醒 291 | 292 | V1.9.1(2020.10.27) 293 | 1. 定时运行增加可配置的失败重试 294 | 295 | V2.0.0 (2020.12.01) 296 | 1. 新增唯一展示事件 297 | 2. 新增简易看板模式 298 | 3. 增加看板,看板配置 299 | 300 | V2.1.0 (2021.01.14) 301 | 1. 新增并发页面爬虫(使用新页iframe实现) 302 | 2. 支持爬虫与客户端数据交互 303 | 304 | V2.2.0 (2021.01.20) 305 | 1. 页面爬虫支持后台运行,支持定时运行 306 | 2. 页面爬虫数据支持增加到看板 307 | 308 | V2.3.0 (2021.01.29) 309 | 1. 流程事务支持后台运行(新开页面事件在后台) 310 | 2. 新增消息通知事件(增加系统通知与浏览器通知) 311 | 3. 优化看板元素不展示中间过程 312 | 313 | V2.4.0 (2021.03.03) 314 | 1. 新增优化后的线性爬虫 315 | 2. 增加取值事件的数据解析 316 | 317 | V2.5.0(2021.06.07) 318 | 1. 新增监控事务(单节点监控与多节点监控) 319 | 320 | V2.6.0 (2022.06.30) 321 | 1. 新增快捷键触发事务,支持默认的选中传值:{SELECT},复制传值:{COPY} 322 | 2. 修改中间运行参数为{}格式(注:运行前参数格式为${},本次影响原取值事件的设值,消息发送中间参数) 323 | 3. 源码事务也支持自定义设值 324 | 325 | V2.7.0 (2022.07.24) 326 | 1. 流程事件支持指定iframe 327 | 328 | V2.8.0 (2022.08.15) 329 | 1. 流程事务支持跳转事件(只能往后跳转,跳转不存在的节点会结束流程) 330 | 2. 增加流程事件测至此操作 331 | 3. 增加爬虫事务数据下载 332 | 4. 其他各种优化 333 | 334 | 335 | ## 特别说明 336 | 本插件使用manifest V2版本,插件导入浏览器中会有错误报警,暂时可忽略。 337 | 338 | ## 感谢轮子 339 | 1. [materializecss](http://www.materializecss.cn/about.html) 340 | 3. [官方轮子](https://developer.chrome.com/extensions) 341 | 4. [插件教程](https://www.cnblogs.com/liuxianan/p/chrome-plugin-develop.html) 342 | 343 | 344 | ## 感谢Contributors,欢迎加入 345 | 346 | - [webgjc](https://github.com/webgjc) 347 | - [ILovePing](https://github.com/ILovePing) 348 | 349 | ## License 350 | 351 | web_robot is [MIT licensed](./LICENSE). 352 | 353 | ## 赞赏 354 | 355 | ![赞赏](https://raw.githubusercontent.com/webgjc/web_robot/master/images/reward.png) -------------------------------------------------------------------------------- /html/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | web_robot 10 | 11 | 177 | 178 | 179 | 180 |
181 | 182 | 183 |
184 |
185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 |
事务名操作
195 |
196 | 添加事务 197 | 导入事务 198 |
199 |
200 |
201 |
202 | 208 |
209 |
210 |
211 | 页面添加看板 212 |
213 |
214 | 看板开关 215 |
216 | 217 | 238 | 239 | 250 | 251 | 262 |
263 | 264 | 265 | 282 | 283 | 284 | 357 | 358 | 359 | 393 | 394 | 395 | 450 | 451 | 452 | 460 | 461 | 462 | 474 | 475 | 476 | 484 | 485 | 486 | 494 | 495 | 496 | 518 | 519 | 544 | 545 | 559 |
560 | 561 | 562 | 563 | 564 | 565 | 566 | -------------------------------------------------------------------------------- /js/newtab.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 功能: 3 | * 1、浏览器默认主页覆盖,用作看板功能 4 | * 2、并发爬虫使用主页iframe实现 5 | */ 6 | 7 | 8 | const tag_types = [ 9 | "自由选择器", 10 | "a", 11 | "body", 12 | "button", 13 | "div", 14 | "i", 15 | "img", 16 | "input", 17 | "li", 18 | "p", 19 | "span", 20 | "td", 21 | "textarea", 22 | "tr", 23 | "ul", 24 | "h1", 25 | "h2", 26 | "h3", 27 | "h4", 28 | "h5", 29 | ]; 30 | 31 | // 获取数据存储 32 | function get_my_robot(callback) { 33 | chrome.storage.local.get(["my_robot"], function (res) { 34 | if (callback) callback(res.my_robot); 35 | }); 36 | } 37 | 38 | // 设置数据存储 39 | function set_my_robot(new_robot, cb) { 40 | chrome.storage.local.set( 41 | { 42 | my_robot: new_robot, 43 | }, 44 | function () { 45 | cb && cb(); 46 | } 47 | ); 48 | } 49 | 50 | // 拼接执行的js 51 | function jscode(process) { 52 | let exec_code = "(function(){ \n"; 53 | if ( 54 | process["opera"] === "click" || 55 | process["opera"] === "value" || 56 | process["opera"] === "mouseover" 57 | ) { 58 | if (tag_types.indexOf(process.tag) === -1) { 59 | exec_code += `var robot_node;\n`; 60 | exec_code += ` 61 | let ptag = '${process.tag}'; 62 | if (ptag.indexOf("{") !== -1 && ptag.indexOf("}") !== -1) { 63 | let doms = document.querySelectorAll(ptag.substring(0, ptag.indexOf("{"))); 64 | let value = ptag.substring(ptag.indexOf("{") + 1, ptag.indexOf("}")); 65 | robot_node = Array.prototype.slice.call(doms) 66 | .filter(d => d.textContent.trim() === value && d.children.length === 0)[${process.n}]; 67 | }else{ 68 | robot_node = document.querySelectorAll(ptag)[${process.n}]; 69 | }\n`; 70 | } else { 71 | exec_code += `robot_node = document.getElementsByTagName('${process.tag}')[${process.n}];\n`; 72 | } 73 | exec_code += `function myrobot_getAbsPoin(dom) { 74 | let x = dom.offsetLeft; 75 | let y = dom.offsetTop; 76 | while (dom.offsetParent) { 77 | dom = dom.offsetParent; 78 | x += dom.offsetLeft; 79 | y += dom.offsetTop; 80 | } 81 | return { 82 | 'x': x, 83 | 'y': y 84 | }; 85 | };\n`; 86 | exec_code += `let domposi = myrobot_getAbsPoint(robot_node);\n`; 87 | exec_code += `if (domposi.y < window.scrollY || domposi.y > (window.scrollY + window.innerHeight * 0.8) || 88 | domposi.x < window.scrollX || domposi.x > (window.scrollX + window.innerWidth * 0.8)) { 89 | window.scrollTo(domposi.x - window.innerWidth / 2, domposi.y - window.innerHeight / 2);}\n`; 90 | } 91 | if (process["opera"] === "click") { 92 | exec_code += "robot_node.click();"; 93 | } else if (process["opera"] === "value") { 94 | /** 95 | * 为react兼容 96 | */ 97 | exec_code += "let lastValue = robot_node.value;"; 98 | exec_code += `robot_node.value='${process.value}';`; 99 | exec_code += "let event = new Event('input', { bubbles: true });"; 100 | exec_code += "event.simulated = true;"; 101 | exec_code += "let tracker = robot_node._valueTracker;"; 102 | exec_code += "if (tracker) { tracker.setValue(lastValue); }\n"; 103 | exec_code += "robot_node.dispatchEvent(event);"; 104 | } else if (process["opera"] === "refresh") { 105 | exec_code += "window.location.reload();"; 106 | } else if (process["opera"] === "pagejump") { 107 | exec_code += `window.location.href='${process.value}';`; 108 | } else if (process["opera"] === "mouseover") { 109 | exec_code += `let mouseoverevent = new MouseEvent('mouseover', {bubbles: true, cancelable: true});`; 110 | exec_code += `robot_node.dispatchEvent(mouseoverevent);`; 111 | } 112 | exec_code += "\n})();"; 113 | return exec_code; 114 | } 115 | 116 | // 等待 117 | function sleep(s) { 118 | return new Promise(function (resolve, reject) { 119 | setTimeout(resolve, s * 1000); 120 | }); 121 | } 122 | 123 | // chrome://newtab/?case=rwe 124 | // function resetwh(w, h, name) { 125 | // let id = name.split("-")[1]; 126 | // w = w + "px"; 127 | // h = h + "px"; 128 | // document.getElementById(`frame-${id}`).style.width = w; 129 | // document.getElementById(`frame-${id}`).style.height = h; 130 | // document.getElementById(`grid-${id}`).style.width = w; 131 | // document.getElementById(`grid-${id}`).style.height = h; 132 | // } 133 | 134 | function exec_run_item(process_item, tab_id, name, grid, node, args, cb) { 135 | if (process_item.opera === "onlyshow") { 136 | chrome.tabs.sendMessage(tab_id, { 137 | name: name, 138 | type: "onlyshow", 139 | tag: process_item.tag, 140 | n: process_item.n, 141 | grid: grid, 142 | width: document.getElementById(name).clientWidth + "px", 143 | height: document.getElementById(name).clientHeight + "px" 144 | }, (msg) => { 145 | document.getElementById(name).style.opacity=1; 146 | }) 147 | } else if (process_item.opera === "getvalue") { 148 | chrome.tabs.sendMessage( 149 | tab_id, 150 | { 151 | type: "get_value_frame", 152 | name: name, 153 | tag: process_item.tag, 154 | n: process_item.n, 155 | }, 156 | function (msg) { 157 | args[process_item.value] = msg.data 158 | cb && cb(name, args, node); 159 | } 160 | ); 161 | } else if (process_item.opera === "getcustomvalue") { 162 | chrome.tabs.sendMessage( 163 | tab_id, 164 | { 165 | type: "get_custom_value_frame", 166 | name: name, 167 | value: process_item.expr 168 | }, 169 | function (msg) { 170 | args[process_item.value] = msg.data 171 | cb && cb(name, args, node); 172 | } 173 | ); 174 | } else { 175 | chrome.tabs.sendMessage(tab_id, { 176 | name: name, 177 | type: "execute_frame", 178 | code: jscode(process_item) 179 | }) 180 | } 181 | } 182 | 183 | 184 | // dom检查自旋运行 185 | function dom_check_run(process, tab_id, name, grid, node, cb) { 186 | // console.log("dom check run") 187 | let run_status = 0; // 运行状态 0 - 正在检查,1 - 等待运行,2 - 正在运行 188 | let now_index = 0; // 当前运行process 189 | let args = {}; // 可取参数列表(包括取值导入) 190 | let count = 0; 191 | if (process.length === 0) { 192 | cb && cb(name, args, node); 193 | return; 194 | } 195 | let dom_itvl = setInterval(function () { 196 | if (run_status == 0 && !process[now_index].check) { 197 | run_status = 1; 198 | } 199 | if (run_status == 0) { 200 | count += 1; 201 | chrome.tabs.sendMessage( 202 | tab_id, 203 | { 204 | type: "get_dom_frame", 205 | name: name, 206 | tag: process[now_index].tag, 207 | n: process[now_index].n, 208 | }, 209 | function (msg) { 210 | // console.log(msg) 211 | if (msg.type == "get_dom_frame" && msg.dom) { 212 | run_status = 1; 213 | count = 0; 214 | } 215 | } 216 | ); 217 | } else if (run_status == 1) { 218 | if (process.length - 1 === now_index) { 219 | clearInterval(dom_itvl); 220 | exec_run_item(process[now_index], tab_id, name, grid, node, args, cb); 221 | } else { 222 | exec_run_item(process[now_index], tab_id, name, grid, node, args); 223 | } 224 | now_index += 1; 225 | run_status = 0; 226 | } 227 | if (count == 50) { 228 | clearInterval(dom_itvl); 229 | console.log( 230 | `dom not found: ${process[now_index].tag} , ${process[now_index].n}` 231 | ); 232 | } 233 | }, 200); 234 | } 235 | 236 | // 运行流程事务 237 | // async function exec_run(process, tab_id, name, grid) { 238 | // for (let i = 0; i < process.length; i++) { 239 | // await sleep(process[i].wait); 240 | // await exec_run_item(process[i], tab_id, name, grid); 241 | // } 242 | // } 243 | 244 | // function fetch_html(url, cb) { 245 | // fetch(url) 246 | // .then(resp => resp.text()) 247 | // .then(data => cb && cb(data)); 248 | // } 249 | 250 | // 获取url地址参数 251 | function get_query_variable(variable) { 252 | let query = window.location.search.substring(1); 253 | let vars = query.split("&"); 254 | for (var i = 0; i < vars.length; i++) { 255 | let pair = vars[i].split("="); 256 | if (pair[0] == variable) { return decodeURI(pair[1]); } 257 | } 258 | return (false); 259 | } 260 | 261 | // 获取爬虫地址 262 | function get_crawler_url(crawler, urls, index, cb) { 263 | if (crawler.apicb) { 264 | fetch(crawler.urlapi) 265 | .then(resp => resp.text()) 266 | .then(url => cb && cb(url)); 267 | } else { 268 | cb && cb(urls[index]); 269 | } 270 | } 271 | 272 | // 处理批量的url配置 273 | function deal_batch_url(crawler) { 274 | let res_url = []; 275 | for (let i = 0; i < crawler.urls.length; i++) { 276 | let match_part = crawler.urls[i].match("\{(.*?)\}"); 277 | if (match_part != null) { 278 | let start_end = match_part[1].split("-").map(j => parseInt(j)); 279 | for (let k = start_end[0]; k <= start_end[1]; k++) { 280 | res_url.push(crawler.urls[i].replace(match_part[0], k)); 281 | } 282 | } else { 283 | res_url.push(crawler.urls[i]); 284 | } 285 | } 286 | return res_url; 287 | } 288 | 289 | // 并发爬虫运行 290 | function crawler_run(the_case, grid, crawler, tab, cb) { 291 | 292 | // 处理批量url 293 | let urls = deal_batch_url(crawler); 294 | 295 | // 并发数 296 | let size = crawler.apicb ? 297 | crawler.cc : Math.min(crawler.cc, urls.length); 298 | let arr = []; 299 | let index = 0; 300 | let queue = []; 301 | let result = []; 302 | let queue_status = {}; 303 | let queue_url = {}; 304 | let names = []; 305 | 306 | // 发送数据初始化 307 | crawler_send_data(the_case, crawler, "clear", result); 308 | 309 | // 运行容器iframe初始化 310 | for (let i = 0; i < size; i++) { 311 | get_crawler_url(crawler, urls, i, function (url) { 312 | arr.push({ 313 | w: 25, 314 | h: 25, 315 | content: ``, 316 | id: `crawler-${i}`, 317 | url: url 318 | }); 319 | index++; 320 | queue.push(`crawler-${i}`); 321 | names.push(`crawler-${i}`); 322 | queue_url[`crawler-${i}`] = url; 323 | queue_status[`crawler-${i}`] = 0; 324 | if (i == size - 1) { 325 | grid.load(arr); 326 | } 327 | }) 328 | } 329 | let nodes = grid.el.children; 330 | 331 | // 定时器检查队列中是否有空余容器准备运行 332 | let timer = setInterval(function () { 333 | while (queue.length > 0) { 334 | let name = queue.shift(); 335 | queue_status[name] = 1; 336 | 337 | // 运行 338 | dom_check_run(crawler.fetch, tab.id, name, null, nodes[parseInt(name.split("-")[1])], (name, data, node) => { 339 | // 处理得到的数据,和为容器准备下一个url 340 | get_crawler_url(crawler, urls, index, function (url) { 341 | queue_status[name] = 0; 342 | let tmp = $.extend({ 343 | "primary_key": queue_url[name] 344 | }, data); 345 | if ((!crawler.apicb && index < urls.length) || (crawler.apicb && url != "")) { 346 | result.push(tmp); 347 | grid.update(node, { 348 | content: ``, 349 | url: url 350 | }) 351 | queue_url[name] = url 352 | queue.push(name); 353 | index++; 354 | crawler_send_data(the_case, crawler, "save", result); 355 | } else { 356 | result.push(tmp); 357 | for (let i = 0; i < names.length; i++) { 358 | if (queue_status[names[i]] == 1) { 359 | return; 360 | } 361 | } 362 | crawler_send_data(the_case, crawler, "saveAll", result); 363 | crawler_send_data(the_case, crawler, "summary", result, () => { 364 | clearInterval(timer); 365 | cb && cb(result); 366 | }); 367 | } 368 | }) 369 | }); 370 | } 371 | }, 200) 372 | return; 373 | } 374 | 375 | 376 | // 发送数据到客户端/自定义云端 377 | function crawler_send_data(case_name, crawler, opera, data, callback) { 378 | console.log(case_name, crawler, opera, data) 379 | if (crawler.send) { 380 | let pd = null; 381 | if (opera === "saveAll") { 382 | pd = data; 383 | opera = "save"; 384 | } else { 385 | if (data.length >= crawler.freq) { 386 | pd = data.splice(0, crawler.freq); 387 | } 388 | } 389 | if ((opera == "save" && pd != null) || opera != "save") { 390 | let tmp = { 391 | case_name: case_name, 392 | opera: opera, 393 | data: pd 394 | } 395 | fetch(crawler.api, { 396 | method: "POST", 397 | body: JSON.stringify(tmp), 398 | }).then(() => { 399 | callback && callback(); 400 | }); 401 | } 402 | } else { 403 | callback && callback(); 404 | } 405 | } 406 | 407 | 408 | $(document).ready(function () { 409 | 410 | // 初始化布局插件 411 | let grid = GridStack.init({ 412 | alwaysShowResizeHandle: /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test( 413 | navigator.userAgent 414 | ), 415 | resizable: { 416 | handles: 'e, se, s, sw, w' 417 | }, 418 | column: 100, 419 | cellHeight: 'auto', 420 | // float: true, 421 | enableMove: true, 422 | enableResize: true, 423 | margin: 2 424 | }); 425 | 426 | // 设置操作板显隐 427 | $("body").mousemove(e => { 428 | let sh = e.clientX / window.innerWidth; 429 | if (e.clientY < 10 && sh > 0.4 && sh < 0.6) { 430 | $("#opera").show(); 431 | } 432 | }); 433 | $("#opera").mouseleave(e => { 434 | if ($("#handgrid").text() === "排版") { 435 | $("#opera").hide(); 436 | } 437 | }); 438 | 439 | // 主要入口 440 | chrome.tabs.getCurrent(tab => { 441 | get_my_robot(my_robot => { 442 | let process = []; 443 | let names = []; 444 | let mygrid = my_robot.SETTING_DATA.DASHBOARD_GRID || []; 445 | let mygridmap = {}; 446 | let the_case = get_query_variable("case"); 447 | let show_data = get_query_variable("show_data"); 448 | 449 | // 查看数据 450 | if(show_data == "1") { 451 | let crawler = my_robot[the_case].serial_crawler ? 452 | my_robot[the_case].serial_crawler : my_robot[the_case].paral_crawler; 453 | document.body.innerHTML = `
${JSON.stringify(crawler.data, null, 4)}
`; 454 | return; 455 | } 456 | 457 | // 流程事务 458 | if(the_case && my_robot[the_case].case_type === "process") { 459 | let bg = chrome.extension.getBackgroundPage(); 460 | let process = my_robot[the_case].case_process; 461 | if(process[process.length-1].opera !== "closepage") { 462 | process.push({ 463 | tag: "body", 464 | n: "0", 465 | opera: "closepage", 466 | check: false 467 | }) 468 | } 469 | bg.dom_check_run(my_robot[the_case]["case_process"], tab.id, my_robot, the_case, false); 470 | setTimeout(() => { 471 | window.close(); 472 | }, 500); 473 | return; 474 | } 475 | 476 | // 爬虫事务 477 | if (the_case && my_robot[the_case].case_type === "paral_crawler") { 478 | let crawler = my_robot[the_case].paral_crawler; 479 | if (!crawler.apicb && crawler.urls.length == 0) { 480 | crawler.data = []; 481 | set_my_robot(my_robot, () => { 482 | window.close(); 483 | }) 484 | } 485 | crawler_run(the_case, grid, crawler, tab, (result) => { 486 | crawler.data = result; 487 | set_my_robot(my_robot, () => { 488 | chrome.notifications.create(null, { 489 | type: "basic", 490 | iconUrl: "/images/robot.png", 491 | title: "爬虫运行完毕通知", 492 | message: `${the_case} 运行完毕` 493 | }, () => { 494 | window.close(); 495 | }) 496 | }) 497 | }); 498 | return; 499 | } 500 | 501 | 502 | // 我的看板 503 | grid.enableMove(false); 504 | grid.enableResize(false); 505 | // 加载过去已保存在看板的元素 506 | grid.load(mygrid.filter(g => my_robot[g.id.slice(6)].case_type === "process")); 507 | grid.commit(); 508 | $("iframe").css("opacity", "0.01"); 509 | 510 | for (let i = 0; i < mygrid.length; i++) { 511 | mygridmap[mygrid[i].id] = mygrid[i]; 512 | } 513 | 514 | // 加载新加到看板中的元素 515 | for (let i = 0; i < my_robot.SETTING_DATA.KEYS.length; i++) { 516 | let key = my_robot.SETTING_DATA.KEYS[i]; 517 | let tmpid = `frame-${key}`; 518 | 519 | // 流程事务添加到看板 520 | if (my_robot[key].add_dashboard && my_robot[key].case_type === "process") { 521 | if (mygridmap[tmpid]) { 522 | process.push(my_robot[key].case_process.slice(1)) 523 | names.push(tmpid); 524 | } else { 525 | let grid_contain = ``; 526 | let newgrid = { 527 | w: 20, 528 | h: 20, 529 | content: grid_contain, 530 | id: tmpid, 531 | url: my_robot[key].case_process[0].value 532 | }; 533 | grid.addWidget(newgrid); 534 | mygridmap[tmpid] = newgrid; 535 | process.push(my_robot[key].case_process.slice(1)) 536 | names.push(tmpid); 537 | } 538 | 539 | // 并发爬虫事务添加到看板 540 | } else if (my_robot[key].add_dashboard && 541 | (my_robot[key].case_type === "paral_crawler" || my_robot[key].case_type === "serial_crawler")) { 542 | let crawler = my_robot[key].paral_crawler || my_robot[key].serial_crawler; 543 | let titles = crawler.fetch 544 | .filter(i => i.opera === "getvalue" || i.opera === "getcustomvalue") 545 | .map(i => i.value); 546 | let grid_contain = ` 547 |
548 | 549 | 550 | ${titles.map(t => ``).join("\n")} 551 | 552 | 553 | 554 | ${crawler.data.map(d => ` 555 | 556 | ${titles.map(t => ``).join("\n")} 557 | 558 | `).join("\n")} 559 | 560 |
${t === "primary_key" ? "主键" : t}
${d[t]}
561 |
`; 562 | console.log(mygridmap[tmpid]) 563 | let newgrid = { 564 | w: mygridmap[tmpid] == null ? 20 : mygridmap[tmpid].w, 565 | h: mygridmap[tmpid] == null ? 20 : mygridmap[tmpid].h, 566 | x: mygridmap[tmpid] == null ? null : mygridmap[tmpid].x, 567 | y: mygridmap[tmpid] == null ? null : mygridmap[tmpid].y, 568 | content: grid_contain, 569 | id: tmpid 570 | }; 571 | grid.addWidget(newgrid); 572 | mygridmap[tmpid] = newgrid; 573 | process.push(null); 574 | names.push(tmpid); 575 | } 576 | } 577 | 578 | 579 | // 运行看板中的流程事务 580 | for (let i = 0; i < names.length; i++) { 581 | if (process[i] != null) { 582 | dom_check_run(process[i], tab.id, names[i], mygridmap[names[i]]); 583 | } 584 | } 585 | 586 | // grid.on("dragstop resizestop", (e, el) => { 587 | // my_robot.SETTING_DATA.DASHBOARD_GRID = grid.save(); 588 | // set_my_robot(my_robot); 589 | // }); 590 | 591 | // 看板编辑 592 | $("#handgrid").click(e => { 593 | if ($("#handgrid").text() == "排版") { 594 | $("#handgrid").html("保存"); 595 | let editgrid = []; 596 | let tmpgridmap = JSON.parse(JSON.stringify(mygridmap)) 597 | for (let i = 0; i < names.length; i++) { 598 | tmpgridmap[names[i]].content = ``; 599 | tmpgridmap[names[i]].content += `
${names[i].slice(6)}
` 600 | tmpgridmap[names[i]].id = `panel-${i}`; 601 | editgrid.push(tmpgridmap[names[i]]); 602 | } 603 | grid.load(editgrid, true); 604 | grid.enableMove(true); 605 | grid.enableResize(true); 606 | } else { 607 | $("#handgrid").html("排版"); 608 | let editgrid = grid.save(); 609 | console.log(editgrid) 610 | let tmpkeys = []; 611 | for (let i = 0; i < editgrid.length; i++) { 612 | let idx = parseInt(editgrid[i].id.slice(6)); 613 | tmpkeys.push(names[idx]); 614 | mygridmap[names[idx]].x = editgrid[i].x; 615 | mygridmap[names[idx]].y = editgrid[i].y; 616 | mygridmap[names[idx]].w = editgrid[i].w; 617 | mygridmap[names[idx]].h = editgrid[i].h; 618 | } 619 | let tmpgrid = []; 620 | for (let i = 0; i < tmpkeys.length; i++) { 621 | tmpgrid.push(mygridmap[tmpkeys[i]]); 622 | } 623 | my_robot.SETTING_DATA.DASHBOARD_GRID = tmpgrid; 624 | set_my_robot(my_robot, () => { 625 | window.location.reload(); 626 | }); 627 | } 628 | }); 629 | 630 | // 删除一个看板元素 631 | $(".grid-stack").on("click", ".close-panel", e => { 632 | let thisgrid = grid.save(); 633 | for (let i = 0; i < thisgrid.length; i++) { 634 | if (thisgrid[i].id === e.target.id) { 635 | thisgrid.splice(i, 1); 636 | break; 637 | } 638 | } 639 | grid.load(thisgrid, true); 640 | my_robot[names[parseInt(e.target.id.slice(6))].slice(6)].add_dashboard = false; 641 | // names.splice(parseInt(e.target.id.slice(6)), 1); 642 | }); 643 | 644 | // 重置所有看板元素 645 | $("#reset").click(e => { 646 | my_robot.SETTING_DATA.DASHBOARD_GRID = []; 647 | set_my_robot(my_robot, () => { 648 | window.location.reload(); 649 | }) 650 | }) 651 | }) 652 | }) 653 | }) 654 | 655 | // fetch html 也可以实现突破 x-frame-origin 限制,但会缺少js事件,目前使用backgroud修改response头实现 656 | // for (let i = 0; i < mygrid.length; i++) { 657 | // let frame = document.createElement("iframe"); 658 | // frame.onload = function () { 659 | // fetch_html("https://www.zhihu.com/hot", data => { 660 | // let ed = frame.contentWindow.document; 661 | // ed.open(); 662 | // ed.write(data); 663 | // ed.close(); 664 | // ed.contentEditable = true; 665 | // ed.designMode = 'on'; 666 | // mygrid[i].content = frame.outerHTML; 667 | // if (i == mygrid.length - 1) { 668 | // grid.load(mygrid); 669 | // document.getElementById("reframe").style.display = "none"; 670 | // } 671 | // }) 672 | // } 673 | // document.getElementById("reframe").appendChild(frame); 674 | // mygrid[i].content = `` 675 | // console.log(mygrid) 676 | // } -------------------------------------------------------------------------------- /css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto} 5 | -------------------------------------------------------------------------------- /css/gridstack-extra.css: -------------------------------------------------------------------------------- 1 | .grid-stack > .grid-stack-item { 2 | min-width: 1%; 3 | } 4 | .grid-stack > .grid-stack-item[gs-w="1"] { 5 | width: 1%; 6 | } 7 | .grid-stack > .grid-stack-item[gs-x="1"] { 8 | left: 1%; 9 | } 10 | .grid-stack > .grid-stack-item[gs-min-w="1"] { 11 | min-width: 1%; 12 | } 13 | .grid-stack > .grid-stack-item[gs-max-w="1"] { 14 | max-width: 1%; 15 | } 16 | .grid-stack > .grid-stack-item[gs-w="2"] { 17 | width: 2%; 18 | } 19 | .grid-stack > .grid-stack-item[gs-x="2"] { 20 | left: 2%; 21 | } 22 | .grid-stack > .grid-stack-item[gs-min-w="2"] { 23 | min-width: 2%; 24 | } 25 | .grid-stack > .grid-stack-item[gs-max-w="2"] { 26 | max-width: 2%; 27 | } 28 | .grid-stack > .grid-stack-item[gs-w="3"] { 29 | width: 3%; 30 | } 31 | .grid-stack > .grid-stack-item[gs-x="3"] { 32 | left: 3%; 33 | } 34 | .grid-stack > .grid-stack-item[gs-min-w="3"] { 35 | min-width: 3%; 36 | } 37 | .grid-stack > .grid-stack-item[gs-max-w="3"] { 38 | max-width: 3%; 39 | } 40 | .grid-stack > .grid-stack-item[gs-w="4"] { 41 | width: 4%; 42 | } 43 | .grid-stack > .grid-stack-item[gs-x="4"] { 44 | left: 4%; 45 | } 46 | .grid-stack > .grid-stack-item[gs-min-w="4"] { 47 | min-width: 4%; 48 | } 49 | .grid-stack > .grid-stack-item[gs-max-w="4"] { 50 | max-width: 4%; 51 | } 52 | .grid-stack > .grid-stack-item[gs-w="5"] { 53 | width: 5%; 54 | } 55 | .grid-stack > .grid-stack-item[gs-x="5"] { 56 | left: 5%; 57 | } 58 | .grid-stack > .grid-stack-item[gs-min-w="5"] { 59 | min-width: 5%; 60 | } 61 | .grid-stack > .grid-stack-item[gs-max-w="5"] { 62 | max-width: 5%; 63 | } 64 | .grid-stack > .grid-stack-item[gs-w="6"] { 65 | width: 6%; 66 | } 67 | .grid-stack > .grid-stack-item[gs-x="6"] { 68 | left: 6%; 69 | } 70 | .grid-stack > .grid-stack-item[gs-min-w="6"] { 71 | min-width: 6%; 72 | } 73 | .grid-stack > .grid-stack-item[gs-max-w="6"] { 74 | max-width: 6%; 75 | } 76 | .grid-stack > .grid-stack-item[gs-w="7"] { 77 | width: 7%; 78 | } 79 | .grid-stack > .grid-stack-item[gs-x="7"] { 80 | left: 7%; 81 | } 82 | .grid-stack > .grid-stack-item[gs-min-w="7"] { 83 | min-width: 7%; 84 | } 85 | .grid-stack > .grid-stack-item[gs-max-w="7"] { 86 | max-width: 7%; 87 | } 88 | .grid-stack > .grid-stack-item[gs-w="8"] { 89 | width: 8%; 90 | } 91 | .grid-stack > .grid-stack-item[gs-x="8"] { 92 | left: 8%; 93 | } 94 | .grid-stack > .grid-stack-item[gs-min-w="8"] { 95 | min-width: 8%; 96 | } 97 | .grid-stack > .grid-stack-item[gs-max-w="8"] { 98 | max-width: 8%; 99 | } 100 | .grid-stack > .grid-stack-item[gs-w="9"] { 101 | width: 9%; 102 | } 103 | .grid-stack > .grid-stack-item[gs-x="9"] { 104 | left: 9%; 105 | } 106 | .grid-stack > .grid-stack-item[gs-min-w="9"] { 107 | min-width: 9%; 108 | } 109 | .grid-stack > .grid-stack-item[gs-max-w="9"] { 110 | max-width: 9%; 111 | } 112 | .grid-stack > .grid-stack-item[gs-w="10"] { 113 | width: 10%; 114 | } 115 | .grid-stack > .grid-stack-item[gs-x="10"] { 116 | left: 10%; 117 | } 118 | .grid-stack > .grid-stack-item[gs-min-w="10"] { 119 | min-width: 10%; 120 | } 121 | .grid-stack > .grid-stack-item[gs-max-w="10"] { 122 | max-width: 10%; 123 | } 124 | .grid-stack > .grid-stack-item[gs-w="11"] { 125 | width: 11%; 126 | } 127 | .grid-stack > .grid-stack-item[gs-x="11"] { 128 | left: 11%; 129 | } 130 | .grid-stack > .grid-stack-item[gs-min-w="11"] { 131 | min-width: 11%; 132 | } 133 | .grid-stack > .grid-stack-item[gs-max-w="11"] { 134 | max-width: 11%; 135 | } 136 | .grid-stack > .grid-stack-item[gs-w="12"] { 137 | width: 12%; 138 | } 139 | .grid-stack > .grid-stack-item[gs-x="12"] { 140 | left: 12%; 141 | } 142 | .grid-stack > .grid-stack-item[gs-min-w="12"] { 143 | min-width: 12%; 144 | } 145 | .grid-stack > .grid-stack-item[gs-max-w="12"] { 146 | max-width: 12%; 147 | } 148 | .grid-stack > .grid-stack-item[gs-w="13"] { 149 | width: 13%; 150 | } 151 | .grid-stack > .grid-stack-item[gs-x="13"] { 152 | left: 13%; 153 | } 154 | .grid-stack > .grid-stack-item[gs-min-w="13"] { 155 | min-width: 13%; 156 | } 157 | .grid-stack > .grid-stack-item[gs-max-w="13"] { 158 | max-width: 13%; 159 | } 160 | .grid-stack > .grid-stack-item[gs-w="14"] { 161 | width: 14%; 162 | } 163 | .grid-stack > .grid-stack-item[gs-x="14"] { 164 | left: 14%; 165 | } 166 | .grid-stack > .grid-stack-item[gs-min-w="14"] { 167 | min-width: 14%; 168 | } 169 | .grid-stack > .grid-stack-item[gs-max-w="14"] { 170 | max-width: 14%; 171 | } 172 | .grid-stack > .grid-stack-item[gs-w="15"] { 173 | width: 15%; 174 | } 175 | .grid-stack > .grid-stack-item[gs-x="15"] { 176 | left: 15%; 177 | } 178 | .grid-stack > .grid-stack-item[gs-min-w="15"] { 179 | min-width: 15%; 180 | } 181 | .grid-stack > .grid-stack-item[gs-max-w="15"] { 182 | max-width: 15%; 183 | } 184 | .grid-stack > .grid-stack-item[gs-w="16"] { 185 | width: 16%; 186 | } 187 | .grid-stack > .grid-stack-item[gs-x="16"] { 188 | left: 16%; 189 | } 190 | .grid-stack > .grid-stack-item[gs-min-w="16"] { 191 | min-width: 16%; 192 | } 193 | .grid-stack > .grid-stack-item[gs-max-w="16"] { 194 | max-width: 16%; 195 | } 196 | .grid-stack > .grid-stack-item[gs-w="17"] { 197 | width: 17%; 198 | } 199 | .grid-stack > .grid-stack-item[gs-x="17"] { 200 | left: 17%; 201 | } 202 | .grid-stack > .grid-stack-item[gs-min-w="17"] { 203 | min-width: 17%; 204 | } 205 | .grid-stack > .grid-stack-item[gs-max-w="17"] { 206 | max-width: 17%; 207 | } 208 | .grid-stack > .grid-stack-item[gs-w="18"] { 209 | width: 18%; 210 | } 211 | .grid-stack > .grid-stack-item[gs-x="18"] { 212 | left: 18%; 213 | } 214 | .grid-stack > .grid-stack-item[gs-min-w="18"] { 215 | min-width: 18%; 216 | } 217 | .grid-stack > .grid-stack-item[gs-max-w="18"] { 218 | max-width: 18%; 219 | } 220 | .grid-stack > .grid-stack-item[gs-w="19"] { 221 | width: 19%; 222 | } 223 | .grid-stack > .grid-stack-item[gs-x="19"] { 224 | left: 19%; 225 | } 226 | .grid-stack > .grid-stack-item[gs-min-w="19"] { 227 | min-width: 19%; 228 | } 229 | .grid-stack > .grid-stack-item[gs-max-w="19"] { 230 | max-width: 19%; 231 | } 232 | .grid-stack > .grid-stack-item[gs-w="20"] { 233 | width: 20%; 234 | } 235 | .grid-stack > .grid-stack-item[gs-x="20"] { 236 | left: 20%; 237 | } 238 | .grid-stack > .grid-stack-item[gs-min-w="20"] { 239 | min-width: 20%; 240 | } 241 | .grid-stack > .grid-stack-item[gs-max-w="20"] { 242 | max-width: 20%; 243 | } 244 | .grid-stack > .grid-stack-item[gs-w="21"] { 245 | width: 21%; 246 | } 247 | .grid-stack > .grid-stack-item[gs-x="21"] { 248 | left: 21%; 249 | } 250 | .grid-stack > .grid-stack-item[gs-min-w="21"] { 251 | min-width: 21%; 252 | } 253 | .grid-stack > .grid-stack-item[gs-max-w="21"] { 254 | max-width: 21%; 255 | } 256 | .grid-stack > .grid-stack-item[gs-w="22"] { 257 | width: 22%; 258 | } 259 | .grid-stack > .grid-stack-item[gs-x="22"] { 260 | left: 22%; 261 | } 262 | .grid-stack > .grid-stack-item[gs-min-w="22"] { 263 | min-width: 22%; 264 | } 265 | .grid-stack > .grid-stack-item[gs-max-w="22"] { 266 | max-width: 22%; 267 | } 268 | .grid-stack > .grid-stack-item[gs-w="23"] { 269 | width: 23%; 270 | } 271 | .grid-stack > .grid-stack-item[gs-x="23"] { 272 | left: 23%; 273 | } 274 | .grid-stack > .grid-stack-item[gs-min-w="23"] { 275 | min-width: 23%; 276 | } 277 | .grid-stack > .grid-stack-item[gs-max-w="23"] { 278 | max-width: 23%; 279 | } 280 | .grid-stack > .grid-stack-item[gs-w="24"] { 281 | width: 24%; 282 | } 283 | .grid-stack > .grid-stack-item[gs-x="24"] { 284 | left: 24%; 285 | } 286 | .grid-stack > .grid-stack-item[gs-min-w="24"] { 287 | min-width: 24%; 288 | } 289 | .grid-stack > .grid-stack-item[gs-max-w="24"] { 290 | max-width: 24%; 291 | } 292 | .grid-stack > .grid-stack-item[gs-w="25"] { 293 | width: 25%; 294 | } 295 | .grid-stack > .grid-stack-item[gs-x="25"] { 296 | left: 25%; 297 | } 298 | .grid-stack > .grid-stack-item[gs-min-w="25"] { 299 | min-width: 25%; 300 | } 301 | .grid-stack > .grid-stack-item[gs-max-w="25"] { 302 | max-width: 25%; 303 | } 304 | .grid-stack > .grid-stack-item[gs-w="26"] { 305 | width: 26%; 306 | } 307 | .grid-stack > .grid-stack-item[gs-x="26"] { 308 | left: 26%; 309 | } 310 | .grid-stack > .grid-stack-item[gs-min-w="26"] { 311 | min-width: 26%; 312 | } 313 | .grid-stack > .grid-stack-item[gs-max-w="26"] { 314 | max-width: 26%; 315 | } 316 | .grid-stack > .grid-stack-item[gs-w="27"] { 317 | width: 27%; 318 | } 319 | .grid-stack > .grid-stack-item[gs-x="27"] { 320 | left: 27%; 321 | } 322 | .grid-stack > .grid-stack-item[gs-min-w="27"] { 323 | min-width: 27%; 324 | } 325 | .grid-stack > .grid-stack-item[gs-max-w="27"] { 326 | max-width: 27%; 327 | } 328 | .grid-stack > .grid-stack-item[gs-w="28"] { 329 | width: 28%; 330 | } 331 | .grid-stack > .grid-stack-item[gs-x="28"] { 332 | left: 28%; 333 | } 334 | .grid-stack > .grid-stack-item[gs-min-w="28"] { 335 | min-width: 28%; 336 | } 337 | .grid-stack > .grid-stack-item[gs-max-w="28"] { 338 | max-width: 28%; 339 | } 340 | .grid-stack > .grid-stack-item[gs-w="29"] { 341 | width: 29%; 342 | } 343 | .grid-stack > .grid-stack-item[gs-x="29"] { 344 | left: 29%; 345 | } 346 | .grid-stack > .grid-stack-item[gs-min-w="29"] { 347 | min-width: 29%; 348 | } 349 | .grid-stack > .grid-stack-item[gs-max-w="29"] { 350 | max-width: 29%; 351 | } 352 | .grid-stack > .grid-stack-item[gs-w="30"] { 353 | width: 30%; 354 | } 355 | .grid-stack > .grid-stack-item[gs-x="30"] { 356 | left: 30%; 357 | } 358 | .grid-stack > .grid-stack-item[gs-min-w="30"] { 359 | min-width: 30%; 360 | } 361 | .grid-stack > .grid-stack-item[gs-max-w="30"] { 362 | max-width: 30%; 363 | } 364 | .grid-stack > .grid-stack-item[gs-w="31"] { 365 | width: 31%; 366 | } 367 | .grid-stack > .grid-stack-item[gs-x="31"] { 368 | left: 31%; 369 | } 370 | .grid-stack > .grid-stack-item[gs-min-w="31"] { 371 | min-width: 31%; 372 | } 373 | .grid-stack > .grid-stack-item[gs-max-w="31"] { 374 | max-width: 31%; 375 | } 376 | .grid-stack > .grid-stack-item[gs-w="32"] { 377 | width: 32%; 378 | } 379 | .grid-stack > .grid-stack-item[gs-x="32"] { 380 | left: 32%; 381 | } 382 | .grid-stack > .grid-stack-item[gs-min-w="32"] { 383 | min-width: 32%; 384 | } 385 | .grid-stack > .grid-stack-item[gs-max-w="32"] { 386 | max-width: 32%; 387 | } 388 | .grid-stack > .grid-stack-item[gs-w="33"] { 389 | width: 33%; 390 | } 391 | .grid-stack > .grid-stack-item[gs-x="33"] { 392 | left: 33%; 393 | } 394 | .grid-stack > .grid-stack-item[gs-min-w="33"] { 395 | min-width: 33%; 396 | } 397 | .grid-stack > .grid-stack-item[gs-max-w="33"] { 398 | max-width: 33%; 399 | } 400 | .grid-stack > .grid-stack-item[gs-w="34"] { 401 | width: 34%; 402 | } 403 | .grid-stack > .grid-stack-item[gs-x="34"] { 404 | left: 34%; 405 | } 406 | .grid-stack > .grid-stack-item[gs-min-w="34"] { 407 | min-width: 34%; 408 | } 409 | .grid-stack > .grid-stack-item[gs-max-w="34"] { 410 | max-width: 34%; 411 | } 412 | .grid-stack > .grid-stack-item[gs-w="35"] { 413 | width: 35%; 414 | } 415 | .grid-stack > .grid-stack-item[gs-x="35"] { 416 | left: 35%; 417 | } 418 | .grid-stack > .grid-stack-item[gs-min-w="35"] { 419 | min-width: 35%; 420 | } 421 | .grid-stack > .grid-stack-item[gs-max-w="35"] { 422 | max-width: 35%; 423 | } 424 | .grid-stack > .grid-stack-item[gs-w="36"] { 425 | width: 36%; 426 | } 427 | .grid-stack > .grid-stack-item[gs-x="36"] { 428 | left: 36%; 429 | } 430 | .grid-stack > .grid-stack-item[gs-min-w="36"] { 431 | min-width: 36%; 432 | } 433 | .grid-stack > .grid-stack-item[gs-max-w="36"] { 434 | max-width: 36%; 435 | } 436 | .grid-stack > .grid-stack-item[gs-w="37"] { 437 | width: 37%; 438 | } 439 | .grid-stack > .grid-stack-item[gs-x="37"] { 440 | left: 37%; 441 | } 442 | .grid-stack > .grid-stack-item[gs-min-w="37"] { 443 | min-width: 37%; 444 | } 445 | .grid-stack > .grid-stack-item[gs-max-w="37"] { 446 | max-width: 37%; 447 | } 448 | .grid-stack > .grid-stack-item[gs-w="38"] { 449 | width: 38%; 450 | } 451 | .grid-stack > .grid-stack-item[gs-x="38"] { 452 | left: 38%; 453 | } 454 | .grid-stack > .grid-stack-item[gs-min-w="38"] { 455 | min-width: 38%; 456 | } 457 | .grid-stack > .grid-stack-item[gs-max-w="38"] { 458 | max-width: 38%; 459 | } 460 | .grid-stack > .grid-stack-item[gs-w="39"] { 461 | width: 39%; 462 | } 463 | .grid-stack > .grid-stack-item[gs-x="39"] { 464 | left: 39%; 465 | } 466 | .grid-stack > .grid-stack-item[gs-min-w="39"] { 467 | min-width: 39%; 468 | } 469 | .grid-stack > .grid-stack-item[gs-max-w="39"] { 470 | max-width: 39%; 471 | } 472 | .grid-stack > .grid-stack-item[gs-w="40"] { 473 | width: 40%; 474 | } 475 | .grid-stack > .grid-stack-item[gs-x="40"] { 476 | left: 40%; 477 | } 478 | .grid-stack > .grid-stack-item[gs-min-w="40"] { 479 | min-width: 40%; 480 | } 481 | .grid-stack > .grid-stack-item[gs-max-w="40"] { 482 | max-width: 40%; 483 | } 484 | .grid-stack > .grid-stack-item[gs-w="41"] { 485 | width: 41%; 486 | } 487 | .grid-stack > .grid-stack-item[gs-x="41"] { 488 | left: 41%; 489 | } 490 | .grid-stack > .grid-stack-item[gs-min-w="41"] { 491 | min-width: 41%; 492 | } 493 | .grid-stack > .grid-stack-item[gs-max-w="41"] { 494 | max-width: 41%; 495 | } 496 | .grid-stack > .grid-stack-item[gs-w="42"] { 497 | width: 42%; 498 | } 499 | .grid-stack > .grid-stack-item[gs-x="42"] { 500 | left: 42%; 501 | } 502 | .grid-stack > .grid-stack-item[gs-min-w="42"] { 503 | min-width: 42%; 504 | } 505 | .grid-stack > .grid-stack-item[gs-max-w="42"] { 506 | max-width: 42%; 507 | } 508 | .grid-stack > .grid-stack-item[gs-w="43"] { 509 | width: 43%; 510 | } 511 | .grid-stack > .grid-stack-item[gs-x="43"] { 512 | left: 43%; 513 | } 514 | .grid-stack > .grid-stack-item[gs-min-w="43"] { 515 | min-width: 43%; 516 | } 517 | .grid-stack > .grid-stack-item[gs-max-w="43"] { 518 | max-width: 43%; 519 | } 520 | .grid-stack > .grid-stack-item[gs-w="44"] { 521 | width: 44%; 522 | } 523 | .grid-stack > .grid-stack-item[gs-x="44"] { 524 | left: 44%; 525 | } 526 | .grid-stack > .grid-stack-item[gs-min-w="44"] { 527 | min-width: 44%; 528 | } 529 | .grid-stack > .grid-stack-item[gs-max-w="44"] { 530 | max-width: 44%; 531 | } 532 | .grid-stack > .grid-stack-item[gs-w="45"] { 533 | width: 45%; 534 | } 535 | .grid-stack > .grid-stack-item[gs-x="45"] { 536 | left: 45%; 537 | } 538 | .grid-stack > .grid-stack-item[gs-min-w="45"] { 539 | min-width: 45%; 540 | } 541 | .grid-stack > .grid-stack-item[gs-max-w="45"] { 542 | max-width: 45%; 543 | } 544 | .grid-stack > .grid-stack-item[gs-w="46"] { 545 | width: 46%; 546 | } 547 | .grid-stack > .grid-stack-item[gs-x="46"] { 548 | left: 46%; 549 | } 550 | .grid-stack > .grid-stack-item[gs-min-w="46"] { 551 | min-width: 46%; 552 | } 553 | .grid-stack > .grid-stack-item[gs-max-w="46"] { 554 | max-width: 46%; 555 | } 556 | .grid-stack > .grid-stack-item[gs-w="47"] { 557 | width: 47%; 558 | } 559 | .grid-stack > .grid-stack-item[gs-x="47"] { 560 | left: 47%; 561 | } 562 | .grid-stack > .grid-stack-item[gs-min-w="47"] { 563 | min-width: 47%; 564 | } 565 | .grid-stack > .grid-stack-item[gs-max-w="47"] { 566 | max-width: 47%; 567 | } 568 | .grid-stack > .grid-stack-item[gs-w="48"] { 569 | width: 48%; 570 | } 571 | .grid-stack > .grid-stack-item[gs-x="48"] { 572 | left: 48%; 573 | } 574 | .grid-stack > .grid-stack-item[gs-min-w="48"] { 575 | min-width: 48%; 576 | } 577 | .grid-stack > .grid-stack-item[gs-max-w="48"] { 578 | max-width: 48%; 579 | } 580 | .grid-stack > .grid-stack-item[gs-w="49"] { 581 | width: 49%; 582 | } 583 | .grid-stack > .grid-stack-item[gs-x="49"] { 584 | left: 49%; 585 | } 586 | .grid-stack > .grid-stack-item[gs-min-w="49"] { 587 | min-width: 49%; 588 | } 589 | .grid-stack > .grid-stack-item[gs-max-w="49"] { 590 | max-width: 49%; 591 | } 592 | .grid-stack > .grid-stack-item[gs-w="50"] { 593 | width: 50%; 594 | } 595 | .grid-stack > .grid-stack-item[gs-x="50"] { 596 | left: 50%; 597 | } 598 | .grid-stack > .grid-stack-item[gs-min-w="50"] { 599 | min-width: 50%; 600 | } 601 | .grid-stack > .grid-stack-item[gs-max-w="50"] { 602 | max-width: 50%; 603 | } 604 | .grid-stack > .grid-stack-item[gs-w="51"] { 605 | width: 51%; 606 | } 607 | .grid-stack > .grid-stack-item[gs-x="51"] { 608 | left: 51%; 609 | } 610 | .grid-stack > .grid-stack-item[gs-min-w="51"] { 611 | min-width: 51%; 612 | } 613 | .grid-stack > .grid-stack-item[gs-max-w="51"] { 614 | max-width: 51%; 615 | } 616 | .grid-stack > .grid-stack-item[gs-w="52"] { 617 | width: 52%; 618 | } 619 | .grid-stack > .grid-stack-item[gs-x="52"] { 620 | left: 52%; 621 | } 622 | .grid-stack > .grid-stack-item[gs-min-w="52"] { 623 | min-width: 52%; 624 | } 625 | .grid-stack > .grid-stack-item[gs-max-w="52"] { 626 | max-width: 52%; 627 | } 628 | .grid-stack > .grid-stack-item[gs-w="53"] { 629 | width: 53%; 630 | } 631 | .grid-stack > .grid-stack-item[gs-x="53"] { 632 | left: 53%; 633 | } 634 | .grid-stack > .grid-stack-item[gs-min-w="53"] { 635 | min-width: 53%; 636 | } 637 | .grid-stack > .grid-stack-item[gs-max-w="53"] { 638 | max-width: 53%; 639 | } 640 | .grid-stack > .grid-stack-item[gs-w="54"] { 641 | width: 54%; 642 | } 643 | .grid-stack > .grid-stack-item[gs-x="54"] { 644 | left: 54%; 645 | } 646 | .grid-stack > .grid-stack-item[gs-min-w="54"] { 647 | min-width: 54%; 648 | } 649 | .grid-stack > .grid-stack-item[gs-max-w="54"] { 650 | max-width: 54%; 651 | } 652 | .grid-stack > .grid-stack-item[gs-w="55"] { 653 | width: 55%; 654 | } 655 | .grid-stack > .grid-stack-item[gs-x="55"] { 656 | left: 55%; 657 | } 658 | .grid-stack > .grid-stack-item[gs-min-w="55"] { 659 | min-width: 55%; 660 | } 661 | .grid-stack > .grid-stack-item[gs-max-w="55"] { 662 | max-width: 55%; 663 | } 664 | .grid-stack > .grid-stack-item[gs-w="56"] { 665 | width: 56%; 666 | } 667 | .grid-stack > .grid-stack-item[gs-x="56"] { 668 | left: 56%; 669 | } 670 | .grid-stack > .grid-stack-item[gs-min-w="56"] { 671 | min-width: 56%; 672 | } 673 | .grid-stack > .grid-stack-item[gs-max-w="56"] { 674 | max-width: 56%; 675 | } 676 | .grid-stack > .grid-stack-item[gs-w="57"] { 677 | width: 57%; 678 | } 679 | .grid-stack > .grid-stack-item[gs-x="57"] { 680 | left: 57%; 681 | } 682 | .grid-stack > .grid-stack-item[gs-min-w="57"] { 683 | min-width: 57%; 684 | } 685 | .grid-stack > .grid-stack-item[gs-max-w="57"] { 686 | max-width: 57%; 687 | } 688 | .grid-stack > .grid-stack-item[gs-w="58"] { 689 | width: 58%; 690 | } 691 | .grid-stack > .grid-stack-item[gs-x="58"] { 692 | left: 58%; 693 | } 694 | .grid-stack > .grid-stack-item[gs-min-w="58"] { 695 | min-width: 58%; 696 | } 697 | .grid-stack > .grid-stack-item[gs-max-w="58"] { 698 | max-width: 58%; 699 | } 700 | .grid-stack > .grid-stack-item[gs-w="59"] { 701 | width: 59%; 702 | } 703 | .grid-stack > .grid-stack-item[gs-x="59"] { 704 | left: 59%; 705 | } 706 | .grid-stack > .grid-stack-item[gs-min-w="59"] { 707 | min-width: 59%; 708 | } 709 | .grid-stack > .grid-stack-item[gs-max-w="59"] { 710 | max-width: 59%; 711 | } 712 | .grid-stack > .grid-stack-item[gs-w="60"] { 713 | width: 60%; 714 | } 715 | .grid-stack > .grid-stack-item[gs-x="60"] { 716 | left: 60%; 717 | } 718 | .grid-stack > .grid-stack-item[gs-min-w="60"] { 719 | min-width: 60%; 720 | } 721 | .grid-stack > .grid-stack-item[gs-max-w="60"] { 722 | max-width: 60%; 723 | } 724 | .grid-stack > .grid-stack-item[gs-w="61"] { 725 | width: 61%; 726 | } 727 | .grid-stack > .grid-stack-item[gs-x="61"] { 728 | left: 61%; 729 | } 730 | .grid-stack > .grid-stack-item[gs-min-w="61"] { 731 | min-width: 61%; 732 | } 733 | .grid-stack > .grid-stack-item[gs-max-w="61"] { 734 | max-width: 61%; 735 | } 736 | .grid-stack > .grid-stack-item[gs-w="62"] { 737 | width: 62%; 738 | } 739 | .grid-stack > .grid-stack-item[gs-x="62"] { 740 | left: 62%; 741 | } 742 | .grid-stack > .grid-stack-item[gs-min-w="62"] { 743 | min-width: 62%; 744 | } 745 | .grid-stack > .grid-stack-item[gs-max-w="62"] { 746 | max-width: 62%; 747 | } 748 | .grid-stack > .grid-stack-item[gs-w="63"] { 749 | width: 63%; 750 | } 751 | .grid-stack > .grid-stack-item[gs-x="63"] { 752 | left: 63%; 753 | } 754 | .grid-stack > .grid-stack-item[gs-min-w="63"] { 755 | min-width: 63%; 756 | } 757 | .grid-stack > .grid-stack-item[gs-max-w="63"] { 758 | max-width: 63%; 759 | } 760 | .grid-stack > .grid-stack-item[gs-w="64"] { 761 | width: 64%; 762 | } 763 | .grid-stack > .grid-stack-item[gs-x="64"] { 764 | left: 64%; 765 | } 766 | .grid-stack > .grid-stack-item[gs-min-w="64"] { 767 | min-width: 64%; 768 | } 769 | .grid-stack > .grid-stack-item[gs-max-w="64"] { 770 | max-width: 64%; 771 | } 772 | .grid-stack > .grid-stack-item[gs-w="65"] { 773 | width: 65%; 774 | } 775 | .grid-stack > .grid-stack-item[gs-x="65"] { 776 | left: 65%; 777 | } 778 | .grid-stack > .grid-stack-item[gs-min-w="65"] { 779 | min-width: 65%; 780 | } 781 | .grid-stack > .grid-stack-item[gs-max-w="65"] { 782 | max-width: 65%; 783 | } 784 | .grid-stack > .grid-stack-item[gs-w="66"] { 785 | width: 66%; 786 | } 787 | .grid-stack > .grid-stack-item[gs-x="66"] { 788 | left: 66%; 789 | } 790 | .grid-stack > .grid-stack-item[gs-min-w="66"] { 791 | min-width: 66%; 792 | } 793 | .grid-stack > .grid-stack-item[gs-max-w="66"] { 794 | max-width: 66%; 795 | } 796 | .grid-stack > .grid-stack-item[gs-w="67"] { 797 | width: 67%; 798 | } 799 | .grid-stack > .grid-stack-item[gs-x="67"] { 800 | left: 67%; 801 | } 802 | .grid-stack > .grid-stack-item[gs-min-w="67"] { 803 | min-width: 67%; 804 | } 805 | .grid-stack > .grid-stack-item[gs-max-w="67"] { 806 | max-width: 67%; 807 | } 808 | .grid-stack > .grid-stack-item[gs-w="68"] { 809 | width: 68%; 810 | } 811 | .grid-stack > .grid-stack-item[gs-x="68"] { 812 | left: 68%; 813 | } 814 | .grid-stack > .grid-stack-item[gs-min-w="68"] { 815 | min-width: 68%; 816 | } 817 | .grid-stack > .grid-stack-item[gs-max-w="68"] { 818 | max-width: 68%; 819 | } 820 | .grid-stack > .grid-stack-item[gs-w="69"] { 821 | width: 69%; 822 | } 823 | .grid-stack > .grid-stack-item[gs-x="69"] { 824 | left: 69%; 825 | } 826 | .grid-stack > .grid-stack-item[gs-min-w="69"] { 827 | min-width: 69%; 828 | } 829 | .grid-stack > .grid-stack-item[gs-max-w="69"] { 830 | max-width: 69%; 831 | } 832 | .grid-stack > .grid-stack-item[gs-w="70"] { 833 | width: 70%; 834 | } 835 | .grid-stack > .grid-stack-item[gs-x="70"] { 836 | left: 70%; 837 | } 838 | .grid-stack > .grid-stack-item[gs-min-w="70"] { 839 | min-width: 70%; 840 | } 841 | .grid-stack > .grid-stack-item[gs-max-w="70"] { 842 | max-width: 70%; 843 | } 844 | .grid-stack > .grid-stack-item[gs-w="71"] { 845 | width: 71%; 846 | } 847 | .grid-stack > .grid-stack-item[gs-x="71"] { 848 | left: 71%; 849 | } 850 | .grid-stack > .grid-stack-item[gs-min-w="71"] { 851 | min-width: 71%; 852 | } 853 | .grid-stack > .grid-stack-item[gs-max-w="71"] { 854 | max-width: 71%; 855 | } 856 | .grid-stack > .grid-stack-item[gs-w="72"] { 857 | width: 72%; 858 | } 859 | .grid-stack > .grid-stack-item[gs-x="72"] { 860 | left: 72%; 861 | } 862 | .grid-stack > .grid-stack-item[gs-min-w="72"] { 863 | min-width: 72%; 864 | } 865 | .grid-stack > .grid-stack-item[gs-max-w="72"] { 866 | max-width: 72%; 867 | } 868 | .grid-stack > .grid-stack-item[gs-w="73"] { 869 | width: 73%; 870 | } 871 | .grid-stack > .grid-stack-item[gs-x="73"] { 872 | left: 73%; 873 | } 874 | .grid-stack > .grid-stack-item[gs-min-w="73"] { 875 | min-width: 73%; 876 | } 877 | .grid-stack > .grid-stack-item[gs-max-w="73"] { 878 | max-width: 73%; 879 | } 880 | .grid-stack > .grid-stack-item[gs-w="74"] { 881 | width: 74%; 882 | } 883 | .grid-stack > .grid-stack-item[gs-x="74"] { 884 | left: 74%; 885 | } 886 | .grid-stack > .grid-stack-item[gs-min-w="74"] { 887 | min-width: 74%; 888 | } 889 | .grid-stack > .grid-stack-item[gs-max-w="74"] { 890 | max-width: 74%; 891 | } 892 | .grid-stack > .grid-stack-item[gs-w="75"] { 893 | width: 75%; 894 | } 895 | .grid-stack > .grid-stack-item[gs-x="75"] { 896 | left: 75%; 897 | } 898 | .grid-stack > .grid-stack-item[gs-min-w="75"] { 899 | min-width: 75%; 900 | } 901 | .grid-stack > .grid-stack-item[gs-max-w="75"] { 902 | max-width: 75%; 903 | } 904 | .grid-stack > .grid-stack-item[gs-w="76"] { 905 | width: 76%; 906 | } 907 | .grid-stack > .grid-stack-item[gs-x="76"] { 908 | left: 76%; 909 | } 910 | .grid-stack > .grid-stack-item[gs-min-w="76"] { 911 | min-width: 76%; 912 | } 913 | .grid-stack > .grid-stack-item[gs-max-w="76"] { 914 | max-width: 76%; 915 | } 916 | .grid-stack > .grid-stack-item[gs-w="77"] { 917 | width: 77%; 918 | } 919 | .grid-stack > .grid-stack-item[gs-x="77"] { 920 | left: 77%; 921 | } 922 | .grid-stack > .grid-stack-item[gs-min-w="77"] { 923 | min-width: 77%; 924 | } 925 | .grid-stack > .grid-stack-item[gs-max-w="77"] { 926 | max-width: 77%; 927 | } 928 | .grid-stack > .grid-stack-item[gs-w="78"] { 929 | width: 78%; 930 | } 931 | .grid-stack > .grid-stack-item[gs-x="78"] { 932 | left: 78%; 933 | } 934 | .grid-stack > .grid-stack-item[gs-min-w="78"] { 935 | min-width: 78%; 936 | } 937 | .grid-stack > .grid-stack-item[gs-max-w="78"] { 938 | max-width: 78%; 939 | } 940 | .grid-stack > .grid-stack-item[gs-w="79"] { 941 | width: 79%; 942 | } 943 | .grid-stack > .grid-stack-item[gs-x="79"] { 944 | left: 79%; 945 | } 946 | .grid-stack > .grid-stack-item[gs-min-w="79"] { 947 | min-width: 79%; 948 | } 949 | .grid-stack > .grid-stack-item[gs-max-w="79"] { 950 | max-width: 79%; 951 | } 952 | .grid-stack > .grid-stack-item[gs-w="80"] { 953 | width: 80%; 954 | } 955 | .grid-stack > .grid-stack-item[gs-x="80"] { 956 | left: 80%; 957 | } 958 | .grid-stack > .grid-stack-item[gs-min-w="80"] { 959 | min-width: 80%; 960 | } 961 | .grid-stack > .grid-stack-item[gs-max-w="80"] { 962 | max-width: 80%; 963 | } 964 | .grid-stack > .grid-stack-item[gs-w="81"] { 965 | width: 81%; 966 | } 967 | .grid-stack > .grid-stack-item[gs-x="81"] { 968 | left: 81%; 969 | } 970 | .grid-stack > .grid-stack-item[gs-min-w="81"] { 971 | min-width: 81%; 972 | } 973 | .grid-stack > .grid-stack-item[gs-max-w="81"] { 974 | max-width: 81%; 975 | } 976 | .grid-stack > .grid-stack-item[gs-w="82"] { 977 | width: 82%; 978 | } 979 | .grid-stack > .grid-stack-item[gs-x="82"] { 980 | left: 82%; 981 | } 982 | .grid-stack > .grid-stack-item[gs-min-w="82"] { 983 | min-width: 82%; 984 | } 985 | .grid-stack > .grid-stack-item[gs-max-w="82"] { 986 | max-width: 82%; 987 | } 988 | .grid-stack > .grid-stack-item[gs-w="83"] { 989 | width: 83%; 990 | } 991 | .grid-stack > .grid-stack-item[gs-x="83"] { 992 | left: 83%; 993 | } 994 | .grid-stack > .grid-stack-item[gs-min-w="83"] { 995 | min-width: 83%; 996 | } 997 | .grid-stack > .grid-stack-item[gs-max-w="83"] { 998 | max-width: 83%; 999 | } 1000 | .grid-stack > .grid-stack-item[gs-w="84"] { 1001 | width: 84%; 1002 | } 1003 | .grid-stack > .grid-stack-item[gs-x="84"] { 1004 | left: 84%; 1005 | } 1006 | .grid-stack > .grid-stack-item[gs-min-w="84"] { 1007 | min-width: 84%; 1008 | } 1009 | .grid-stack > .grid-stack-item[gs-max-w="84"] { 1010 | max-width: 84%; 1011 | } 1012 | .grid-stack > .grid-stack-item[gs-w="85"] { 1013 | width: 85%; 1014 | } 1015 | .grid-stack > .grid-stack-item[gs-x="85"] { 1016 | left: 85%; 1017 | } 1018 | .grid-stack > .grid-stack-item[gs-min-w="85"] { 1019 | min-width: 85%; 1020 | } 1021 | .grid-stack > .grid-stack-item[gs-max-w="85"] { 1022 | max-width: 85%; 1023 | } 1024 | .grid-stack > .grid-stack-item[gs-w="86"] { 1025 | width: 86%; 1026 | } 1027 | .grid-stack > .grid-stack-item[gs-x="86"] { 1028 | left: 86%; 1029 | } 1030 | .grid-stack > .grid-stack-item[gs-min-w="86"] { 1031 | min-width: 86%; 1032 | } 1033 | .grid-stack > .grid-stack-item[gs-max-w="86"] { 1034 | max-width: 86%; 1035 | } 1036 | .grid-stack > .grid-stack-item[gs-w="87"] { 1037 | width: 87%; 1038 | } 1039 | .grid-stack > .grid-stack-item[gs-x="87"] { 1040 | left: 87%; 1041 | } 1042 | .grid-stack > .grid-stack-item[gs-min-w="87"] { 1043 | min-width: 87%; 1044 | } 1045 | .grid-stack > .grid-stack-item[gs-max-w="87"] { 1046 | max-width: 87%; 1047 | } 1048 | .grid-stack > .grid-stack-item[gs-w="88"] { 1049 | width: 88%; 1050 | } 1051 | .grid-stack > .grid-stack-item[gs-x="88"] { 1052 | left: 88%; 1053 | } 1054 | .grid-stack > .grid-stack-item[gs-min-w="88"] { 1055 | min-width: 88%; 1056 | } 1057 | .grid-stack > .grid-stack-item[gs-max-w="88"] { 1058 | max-width: 88%; 1059 | } 1060 | .grid-stack > .grid-stack-item[gs-w="89"] { 1061 | width: 89%; 1062 | } 1063 | .grid-stack > .grid-stack-item[gs-x="89"] { 1064 | left: 89%; 1065 | } 1066 | .grid-stack > .grid-stack-item[gs-min-w="89"] { 1067 | min-width: 89%; 1068 | } 1069 | .grid-stack > .grid-stack-item[gs-max-w="89"] { 1070 | max-width: 89%; 1071 | } 1072 | .grid-stack > .grid-stack-item[gs-w="90"] { 1073 | width: 90%; 1074 | } 1075 | .grid-stack > .grid-stack-item[gs-x="90"] { 1076 | left: 90%; 1077 | } 1078 | .grid-stack > .grid-stack-item[gs-min-w="90"] { 1079 | min-width: 90%; 1080 | } 1081 | .grid-stack > .grid-stack-item[gs-max-w="90"] { 1082 | max-width: 90%; 1083 | } 1084 | .grid-stack > .grid-stack-item[gs-w="91"] { 1085 | width: 91%; 1086 | } 1087 | .grid-stack > .grid-stack-item[gs-x="91"] { 1088 | left: 91%; 1089 | } 1090 | .grid-stack > .grid-stack-item[gs-min-w="91"] { 1091 | min-width: 91%; 1092 | } 1093 | .grid-stack > .grid-stack-item[gs-max-w="91"] { 1094 | max-width: 91%; 1095 | } 1096 | .grid-stack > .grid-stack-item[gs-w="92"] { 1097 | width: 92%; 1098 | } 1099 | .grid-stack > .grid-stack-item[gs-x="92"] { 1100 | left: 92%; 1101 | } 1102 | .grid-stack > .grid-stack-item[gs-min-w="92"] { 1103 | min-width: 92%; 1104 | } 1105 | .grid-stack > .grid-stack-item[gs-max-w="92"] { 1106 | max-width: 92%; 1107 | } 1108 | .grid-stack > .grid-stack-item[gs-w="93"] { 1109 | width: 93%; 1110 | } 1111 | .grid-stack > .grid-stack-item[gs-x="93"] { 1112 | left: 93%; 1113 | } 1114 | .grid-stack > .grid-stack-item[gs-min-w="93"] { 1115 | min-width: 93%; 1116 | } 1117 | .grid-stack > .grid-stack-item[gs-max-w="93"] { 1118 | max-width: 93%; 1119 | } 1120 | .grid-stack > .grid-stack-item[gs-w="94"] { 1121 | width: 94%; 1122 | } 1123 | .grid-stack > .grid-stack-item[gs-x="94"] { 1124 | left: 94%; 1125 | } 1126 | .grid-stack > .grid-stack-item[gs-min-w="94"] { 1127 | min-width: 94%; 1128 | } 1129 | .grid-stack > .grid-stack-item[gs-max-w="94"] { 1130 | max-width: 94%; 1131 | } 1132 | .grid-stack > .grid-stack-item[gs-w="95"] { 1133 | width: 95%; 1134 | } 1135 | .grid-stack > .grid-stack-item[gs-x="95"] { 1136 | left: 95%; 1137 | } 1138 | .grid-stack > .grid-stack-item[gs-min-w="95"] { 1139 | min-width: 95%; 1140 | } 1141 | .grid-stack > .grid-stack-item[gs-max-w="95"] { 1142 | max-width: 95%; 1143 | } 1144 | .grid-stack > .grid-stack-item[gs-w="96"] { 1145 | width: 96%; 1146 | } 1147 | .grid-stack > .grid-stack-item[gs-x="96"] { 1148 | left: 96%; 1149 | } 1150 | .grid-stack > .grid-stack-item[gs-min-w="96"] { 1151 | min-width: 96%; 1152 | } 1153 | .grid-stack > .grid-stack-item[gs-max-w="96"] { 1154 | max-width: 96%; 1155 | } 1156 | .grid-stack > .grid-stack-item[gs-w="97"] { 1157 | width: 97%; 1158 | } 1159 | .grid-stack > .grid-stack-item[gs-x="97"] { 1160 | left: 97%; 1161 | } 1162 | .grid-stack > .grid-stack-item[gs-min-w="97"] { 1163 | min-width: 97%; 1164 | } 1165 | .grid-stack > .grid-stack-item[gs-max-w="97"] { 1166 | max-width: 97%; 1167 | } 1168 | .grid-stack > .grid-stack-item[gs-w="98"] { 1169 | width: 98%; 1170 | } 1171 | .grid-stack > .grid-stack-item[gs-x="98"] { 1172 | left: 98%; 1173 | } 1174 | .grid-stack > .grid-stack-item[gs-min-w="98"] { 1175 | min-width: 98%; 1176 | } 1177 | .grid-stack > .grid-stack-item[gs-max-w="98"] { 1178 | max-width: 98%; 1179 | } 1180 | .grid-stack > .grid-stack-item[gs-w="99"] { 1181 | width: 99%; 1182 | } 1183 | .grid-stack > .grid-stack-item[gs-x="99"] { 1184 | left: 99%; 1185 | } 1186 | .grid-stack > .grid-stack-item[gs-min-w="99"] { 1187 | min-width: 99%; 1188 | } 1189 | .grid-stack > .grid-stack-item[gs-max-w="99"] { 1190 | max-width: 99%; 1191 | } 1192 | .grid-stack > .grid-stack-item[gs-w="100"] { 1193 | width: 100%; 1194 | } 1195 | .grid-stack > .grid-stack-item[gs-x="100"] { 1196 | left: 100%; 1197 | } 1198 | .grid-stack > .grid-stack-item[gs-min-w="100"] { 1199 | min-width: 100%; 1200 | } 1201 | .grid-stack > .grid-stack-item[gs-max-w="100"] { 1202 | max-width: 100%; 1203 | } --------------------------------------------------------------------------------