├── .gitignore ├── LICENSE ├── README.md ├── assets ├── VincaDataCable.png ├── VincaReaderAsUSBKeyboard.jpg ├── VincaReaderCaliperWeb.jpg ├── VincaReaderIndicatorWeb.jpg ├── VincaReaderScreenshot.png └── circuit_diagram.png ├── circuit.cddx ├── include └── ESP_IOT.h ├── platformio.ini ├── scripts ├── measurement_to_clipboard.py └── requirements.txt ├── src ├── ESP_IOT.cpp └── main.cpp ├── web ├── LCD.woff ├── csv.png ├── favicon.png ├── index.html ├── jexcel.css ├── jexcel.js ├── jexcel.themes.css ├── jsuites.css ├── jsuites.js └── reconnecting_websocket.js └── web_page_to_c.py /.gitignore: -------------------------------------------------------------------------------- 1 | .pio 2 | .vscode 3 | .DS_Store 4 | .obsidian 5 | web_assets.h -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 liba2k 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # VINCA Reader 2 | 3 | 4 | 5 | This project replaces the VINCA `DTCR-03` "RS232" Digital Caliper Data Transfer Cable with a WiFi enabled ESP8266/ESP32. 6 | 7 | 8 | 9 | The VINCA `DCLA-0605` Caliper only supports data transfer to a computer over a proprietary cable. 10 | 11 | ![](assets/VincaDataCable.png) 12 | 13 | 14 | 15 | You _could_ buy a USB adapter, but that's no fun. I decided to reverse engineer the "RS232" and implement my own solution. 16 | 17 | 18 | 19 | 20 | ## Decoding the Serial Protocol 21 | 22 | 23 | 24 | The connector is a USB micro, so I cut down a cable and started looking at the lines with an oscilloscope. It was easy to identify that the D+ and D- lines have clock and data. Since it has a clock, that means it's a synchronous protocol, even though Amazon says it's RS232. I wrote a Python script to extract the 24 bits being sent at 150ms intervals and record them to a CSV. Then I spent a lot of time trying to understand the data by trying to fit it to all the floating point standards I could find, but nothing worked. Eventually, I got it to work as a fixed point[^1] with the extra 4 bit for flags. One flag represents units (inch / mm), and the other represents the sign (negative when `1`). 25 | 26 | 27 | 28 | [^1]: After I finished the project, I came across an old blog post that explains the bit format. It's a lot simpler, just number of distance units, number of 0.01mm or 0.0005" depending on the units used. I updated the code accordingly. The blog can be found here: https://www.yuriystoys.com/2013/07/chinese-caliper-data-format.html. 29 | 30 | 31 | 32 | ## Hardware 33 | 34 | 35 | 36 | Once I understood the protocol, I was ready to work on the hardware for my solution. I wanted to use the ESP platform so I can send the data over wifi, but the serial data is at 1.2v and the ESP runs at 3.3v. I started looking at level shifters, but most of the online designs are based on the 2N7000 MOSFET, but 1.2v wasn't enough to trigger the gate. I was looking at MOSFET with lower VGS trigger voltage, but I didn't want to use a special part. Eventually, I've realized I can just use a 2N2222 bi-polar transistor. I'll lose the MOSFET performance, but it'll be more than enough for digital `1` and `0` levels. 37 | 38 | ![](assets/circuit_diagram.png) 39 | 40 | 41 | 42 | ## Software 43 | 44 | 45 | 46 | The UI is an Excel style interface that allows collecting and naming measurements. 47 | 48 | The main display is constantly updated via WebSocket.  49 | 50 | Measurements can be inserted with a button or using the spacebar. 51 | 52 | 53 | 54 | The Web UI is compressed and stored on the ESP flash. I used a library I maintain for other IoT projects which provides features like: 55 | 56 | 57 | 58 | 1. Web server with `/wifi` page to set the wifi credentials. 59 | 60 | 2. Access point mode, when booting and the last stored wifi network is not in range. 61 | 62 | 3. mDNS publish of the HTTP service so you don't need to find the IP, you can just use [http://vinca_reader.local/](http://vinca_reader.local/). 63 | 64 | 4. OTA programming. 65 | 66 | 67 | 68 | ### Building the sketch 69 | 70 | 71 | 72 | 1. Run `web_page_to_c.py` to generate the file `include/web_assets.h`, which contains the UI files. 73 | 74 | 2. Install https://github.com/Links2004/arduinoWebSockets. 75 | 76 | 3. Build and upload the sketch to the microcontroller. 77 | 78 | 79 | 80 | #### Arduino IDE users 81 | 82 | 83 | 84 | For step 2 you can simply unzip the latest release from https://github.com/Links2004/arduinoWebSockets/releases in the Arduino libraries folder. 85 | 86 | For step 3: 87 | 88 | - Create a new sketch, save it as "VINCA_reader" and close it. 89 | 90 | - Copy all files under `src` and `include` to the VINCA_reader sketch folder. 91 | 92 | - Rename the file `main.cpp` to `VINCA_reader.ino` in the sketch folder. 93 | 94 | - Open the sketch in the Arduino IDE and build/upload as usual. 95 | 96 | 97 | 98 | ## Support for Other Hardware 99 | 100 | 101 | 102 | There are three separate adapters for VINCA products: 103 | 104 | 1. `DTCR-03` for Digital calipers 105 | 106 | 2. `DTCR-02` for Clockwise Tools calipers  107 | 108 | 3. `DTCR-01` for Clockwise Tools Digital Indicators 109 | 110 | 111 | 112 | All of these use a micro USB as the connector, but there were some other differences when I compared against the `DIGR-0105` 1" Digital Indicators:  113 | 114 | 115 | 116 | 1. The +5v line on the indicator is connected to 1.5v, but on the caliper it's not connected. 117 | 118 | 2. The time period between the serial bursts is shorter on the indicator. 119 | 120 | 121 | 122 | It was very easy to add support for both, and I'm assuming other models may work or will need slight modifications. 123 | 124 | 125 | 126 | 127 | 128 | |![](/assets/VincaReaderCaliperWeb.jpg)| 129 | |:-------------------------:| 130 | |Adapter in use over WiFi powered from an external battery| 131 | 132 | |![](/assets/VincaReaderIndicatorWeb.jpg)| 133 | |:-------------------------:| 134 | |Adapter in use over WiFi powered directly by the tablet connected to a digital indicator| 135 | 136 | 137 | 138 | 139 | The original cable from Clockwise Tools identifies as a usb keyboard and types the measurement when you press the button. With the ESP32-S2 we can have this functionality as well. (The ESP8266 doesn't have a native USB support). 140 | 141 | 142 | |![](/assets/VincaReaderAsUSBKeyboard.jpg)| 143 | | :----------------------------------------: | 144 | | Google sheets on android with the adapter connected as a usb keyboard| 145 | 146 | 147 | -------------------------------------------------------------------------------- /assets/VincaDataCable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/VincaDataCable.png -------------------------------------------------------------------------------- /assets/VincaReaderAsUSBKeyboard.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/VincaReaderAsUSBKeyboard.jpg -------------------------------------------------------------------------------- /assets/VincaReaderCaliperWeb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/VincaReaderCaliperWeb.jpg -------------------------------------------------------------------------------- /assets/VincaReaderIndicatorWeb.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/VincaReaderIndicatorWeb.jpg -------------------------------------------------------------------------------- /assets/VincaReaderScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/VincaReaderScreenshot.png -------------------------------------------------------------------------------- /assets/circuit_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/assets/circuit_diagram.png -------------------------------------------------------------------------------- /circuit.cddx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/circuit.cddx -------------------------------------------------------------------------------- /include/ESP_IOT.h: -------------------------------------------------------------------------------- 1 | /* 2 | ESP_IOT.h - Library for simplifying OTA and WiFi Station settings. 3 | Created by Itai Liba, 28 Feb 2016. 4 | Released into the public domain. 5 | */ 6 | #ifndef ESP_IOT_h 7 | #define ESP_IOT_h 8 | 9 | #include "Arduino.h" 10 | #ifdef ARDUINO_ARCH_ESP32 11 | #include 12 | #include 13 | #include 14 | #else 15 | #include 16 | #include 17 | #define WebServer ESP8266WebServer 18 | #include 19 | #endif 20 | 21 | #include 22 | 23 | const byte DNS_PORT = 53; 24 | 25 | class ESP_IOT 26 | { 27 | public: 28 | static ESP_IOT& Create(); 29 | 30 | ESP_IOT(); 31 | 32 | bool writeFile(String filename, String data); 33 | bool writeFile(String filename, int data); 34 | bool readFile(String filename, String& data); 35 | bool readFile(String filename, int& data); 36 | 37 | bool initIOT(String OTAPassword, String deviceName); 38 | bool initIOT(String APssid, String APpsk, String OTAPassword, String deviceName); 39 | 40 | bool handle(); 41 | 42 | static WebServer server; 43 | 44 | private: 45 | bool startWebServer(); 46 | void startOTA(String OTAPassword, String deviceName); 47 | void startSoftAP(); 48 | 49 | String m_ssid; 50 | String m_psk; 51 | String m_APssid; 52 | String m_APpsk; 53 | bool m_wasDisconnected; 54 | unsigned long m_lastMillis; 55 | DNSServer dnsServer; 56 | bool m_noSoftAP; 57 | }; 58 | 59 | extern ESP_IOT IOT; 60 | 61 | #endif -------------------------------------------------------------------------------- /platformio.ini: -------------------------------------------------------------------------------- 1 | ;PlatformIO Project Configuration File 2 | ; 3 | ; Build options: build flags, source filter 4 | ; Upload options: custom upload port, speed and extra flags 5 | ; Library options: dependencies, extra library storages 6 | ; Advanced options: extra scripting 7 | ; 8 | ; Please visit documentation for the other options and examples 9 | ; https://docs.platformio.org/page/projectconf.html 10 | 11 | [platformio] 12 | ; default_envs = nodemcuv2 13 | default_envs = esp32 14 | 15 | [env] 16 | ; Comment out for programing using USB 17 | ; upload_port = vinca_reader.local 18 | ; upload_protocol = espota 19 | ; upload_flags = "-a ThisPa55word!" 20 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 21 | 22 | extra_scripts = pre:web_page_to_c.py 23 | lib_deps = WebSockets 24 | monitor_speed = 115200 25 | 26 | 27 | [env:esp32] 28 | platform = espressif32 29 | board = lolin_s2_mini 30 | framework = arduino 31 | 32 | 33 | [env:nodemcuv2] 34 | platform = espressif8266 35 | board = nodemcuv2 36 | framework = arduino 37 | upload_speed = 460800 38 | 39 | -------------------------------------------------------------------------------- /scripts/measurement_to_clipboard.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import websockets 3 | import pyperclip 4 | 5 | async def main(): 6 | async with websockets.connect("ws://vinca_reader.local:81") as websocket: 7 | print("OK, Ready for input") 8 | while True: 9 | data = await websocket.recv() 10 | data = data.strip() 11 | data = data[1:] if '*' in data else data 12 | pyperclip.copy(data) 13 | 14 | 15 | if __name__ == "__main__": 16 | asyncio.run(main()) 17 | -------------------------------------------------------------------------------- /scripts/requirements.txt: -------------------------------------------------------------------------------- 1 | websockets==10.3 2 | pyperclip==1.8.2 -------------------------------------------------------------------------------- /src/ESP_IOT.cpp: -------------------------------------------------------------------------------- 1 | #include "ESP_IOT.h" 2 | #include 3 | #include 4 | #include 5 | 6 | #define FORMAT_LITTLEFS_IF_FAILED true 7 | 8 | ESP_IOT IOT; 9 | 10 | WebServer ESP_IOT::server(80); 11 | 12 | ESP_IOT::ESP_IOT(): m_ssid(""), m_psk(""), m_APssid("ESP_IOT"), m_APpsk("password"), m_wasDisconnected(false), m_lastMillis(0), dnsServer(), m_noSoftAP(false) 13 | { 14 | } 15 | 16 | bool ESP_IOT::writeFile(String filename, String data) 17 | { 18 | File configFile = LittleFS.open(filename, "w"); 19 | if (!configFile) 20 | { 21 | Serial.println("Failed to open " + filename + " for writing"); 22 | return false; 23 | } 24 | configFile.println(data); 25 | configFile.close(); 26 | return true; 27 | } 28 | bool ESP_IOT::writeFile(String filename, int data) 29 | { 30 | return writeFile(filename, String(data)); 31 | 32 | } 33 | bool ESP_IOT::readFile(String filename, String& data) 34 | { 35 | File configFile = LittleFS.open(filename, "r"); 36 | if (!configFile) 37 | { 38 | Serial.println("Failed to open " + filename + " for reading"); 39 | return false; 40 | } 41 | data = configFile.readString(); 42 | data.trim(); 43 | configFile.close(); 44 | return true; 45 | } 46 | bool ESP_IOT::readFile(String filename, int& data) 47 | { 48 | File configFile = LittleFS.open(filename, "r"); 49 | if (!configFile) 50 | { 51 | Serial.println("Failed to open " + filename + " for reading"); 52 | return false; 53 | } 54 | data = configFile.readString().toInt(); 55 | configFile.close(); 56 | return true; 57 | } 58 | 59 | bool ESP_IOT::startWebServer() 60 | { 61 | server.on("/wifi", [](){ 62 | String s = ""; 63 | s += "
Network name:

Password:


"; 64 | s += ""; 65 | server.send(200, "text/html", s); 66 | }); 67 | 68 | server.on("/setwifi", [](){ 69 | if (server.arg("ssid") == "" || server.arg("psk") == "") 70 | { 71 | server.send(200, "text/plain", "Missing ssid or psk"); 72 | } 73 | else 74 | { 75 | if (IOT.writeFile("/ssid",server.arg("ssid")) && IOT.writeFile("/psk", server.arg("psk"))) 76 | server.send(200, "text/html", "

Configuration saved

"); 77 | else 78 | server.send(200, "text/plain", "Configuration save failed"); 79 | } 80 | }); 81 | server.on("/wifi_info", [](){ 82 | String ssid; 83 | 84 | if (IOT.readFile("/ssid",ssid)) 85 | server.send(200, "text/plain", "ssid="+ ssid); 86 | else 87 | server.send(200, "text/plain", "Couldn't load config"); 88 | 89 | }); 90 | server.on("/reboot", [](){ 91 | server.send(200, "text/plain", "bye"); 92 | ESP.restart(); 93 | 94 | }); 95 | 96 | server.onNotFound([](){server.send(404, "text/plain", "Page not found");}); 97 | server.begin(); 98 | return true; 99 | } 100 | 101 | bool ESP_IOT::initIOT(String APssid, String APpsk, String OTAPassword, String deviceName) 102 | { 103 | m_APssid = APssid; 104 | m_APpsk = APpsk; 105 | return initIOT(OTAPassword, deviceName); 106 | } 107 | void ESP_IOT::startSoftAP() 108 | { 109 | WiFi.mode(WIFI_AP); 110 | WiFi.softAP(m_APssid.c_str(), m_APpsk.c_str()); 111 | dnsServer.setTTL(300); 112 | dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); 113 | dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); 114 | Serial.println("No WiFi params, softAP"); 115 | } 116 | bool ESP_IOT::initIOT(String OTAPassword, String deviceName) 117 | { 118 | #ifdef ARDUINO_ARCH_ESP32 119 | if (!LittleFS.begin(FORMAT_LITTLEFS_IF_FAILED)) { 120 | #else 121 | if (!LittleFS.begin()) { 122 | #endif 123 | Serial.println("LittleFS mount failed"); 124 | return false; 125 | } 126 | 127 | if ((!IOT.readFile("/ssid",m_ssid)) or (!IOT.readFile("/psk",m_psk))) 128 | { 129 | startSoftAP(); 130 | } 131 | else 132 | { 133 | WiFi.mode(WIFI_STA); 134 | WiFi.begin(m_ssid.c_str(), m_psk.c_str()); 135 | if (WiFi.waitForConnectResult() == WL_CONNECTED) 136 | { 137 | m_noSoftAP = true; 138 | Serial.print("connected "); 139 | Serial.println(WiFi.localIP()); 140 | } 141 | else 142 | { 143 | startSoftAP(); 144 | } 145 | 146 | } 147 | delay(100); 148 | if (!startWebServer()) 149 | return false; 150 | startOTA(OTAPassword, deviceName); 151 | MDNS.addService("http", "tcp", 80); 152 | return true; 153 | } 154 | 155 | bool ESP_IOT::handle() 156 | { 157 | if (WiFi.getMode() == WIFI_STA) 158 | ArduinoOTA.handle(); 159 | server.handleClient(); 160 | dnsServer.processNextRequest(); 161 | 162 | unsigned long l_millis = millis(); 163 | if(l_millis < m_lastMillis + 10000) 164 | return true; 165 | m_lastMillis = l_millis; 166 | if (WiFi.getMode() == WIFI_STA && WiFi.status() == WL_DISCONNECTED) 167 | { 168 | static int count=0; 169 | // If we couldn't connect to AP and after waiting 30 secs for the AP the boot 170 | // we will enable soft AP. 171 | if (m_noSoftAP == false && m_wasDisconnected && count++ > 3) 172 | { 173 | count = 0; 174 | m_wasDisconnected = false; 175 | Serial.println("Can't connect, softAP"); 176 | WiFi.mode(WIFI_AP); 177 | WiFi.softAP(m_APssid.c_str(), m_APpsk.c_str()); 178 | dnsServer.setTTL(300); 179 | dnsServer.setErrorReplyCode(DNSReplyCode::ServerFailure); 180 | dnsServer.start(DNS_PORT, "*", WiFi.softAPIP()); 181 | } 182 | else 183 | { 184 | m_wasDisconnected = true; 185 | // WiFi.mode(WIFI_AP_STA); 186 | Serial.println("Reconnecting "); 187 | // WiFi.begin(m_ssid.c_str(), m_psk.c_str()); 188 | } 189 | } 190 | else if (m_wasDisconnected) 191 | { 192 | m_wasDisconnected = false; 193 | m_noSoftAP = true; 194 | WiFi.mode(WIFI_STA); 195 | Serial.print("Reconnected IP address: "); 196 | Serial.println(WiFi.localIP()); 197 | } 198 | return true; 199 | } 200 | 201 | 202 | void ESP_IOT::startOTA(String OTAPassword, String deviceName) 203 | { 204 | 205 | ArduinoOTA.setHostname(deviceName.c_str()); 206 | 207 | //No authentication by default 208 | ArduinoOTA.setPassword(OTAPassword.c_str()); 209 | 210 | ArduinoOTA.onStart([]() { 211 | Serial.println("Start"); 212 | }); 213 | ArduinoOTA.onEnd([]() { 214 | Serial.println("End"); 215 | }); 216 | ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { 217 | Serial.printf("Progress: %u%%\r\n", (progress / (total / 100))); 218 | }); 219 | ArduinoOTA.onError([](ota_error_t error) { 220 | Serial.printf("Error[%u]: ", error); 221 | if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed"); 222 | else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed"); 223 | else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed"); 224 | else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed"); 225 | else if (error == OTA_END_ERROR) Serial.println("End Failed"); 226 | }); 227 | ArduinoOTA.begin(); 228 | } -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "web_assets.h" 3 | #include 4 | #include "ESP_IOT.h" 5 | 6 | #ifdef ARDUINO_ARCH_ESP32 7 | #include "USB.h" 8 | #include "USBHIDKeyboard.h" 9 | USBHIDKeyboard Keyboard; 10 | #define kbd_begin() Keyboard.begin() 11 | #define kbd_print(x) Keyboard.print(x) 12 | 13 | #define BUTTON 35 14 | #define SCLCK 7 15 | #define MISO 9 16 | #else 17 | #define kbd_begin() 18 | #define kbd_print(x) 19 | #define BUTTON 5 20 | #define SCLCK 14 21 | #define MISO 12 22 | #endif 23 | 24 | #define LED 15 25 | 26 | #define MILLIS_BETWEEN_PACKETS 100 27 | int last_millis; 28 | u32_t packet; 29 | bool data_ready; 30 | u32_t data_ready_packet; 31 | u32_t last_data_ready_packet; 32 | char last_value[16]; 33 | 34 | IRAM_ATTR void clock_isr() { 35 | int new_data = digitalRead(MISO) ? 0 : 1; 36 | unsigned long new_millis = millis(); 37 | if (new_millis - last_millis > MILLIS_BETWEEN_PACKETS) 38 | { 39 | data_ready_packet = packet; 40 | packet = new_data; 41 | data_ready = true; 42 | } 43 | else 44 | { 45 | packet = packet << 1 | new_data; 46 | } 47 | 48 | last_millis = new_millis; 49 | } 50 | 51 | WebSocketsServer webSocket = WebSocketsServer(81); 52 | 53 | void webSocketEvent(uint8_t num, WStype_t type, uint8_t * payload, size_t length) { 54 | 55 | if (type == WStype_TEXT) 56 | Serial.printf("%s", payload); 57 | } 58 | 59 | void handle_index() { 60 | IOT.server.sendHeader("Content-Encoding", "gzip"); 61 | IOT.server.send_P(200, "text/html", index_html, sizeof(index_html)); 62 | } 63 | void handle_favicon() { 64 | IOT.server.sendHeader("Content-Encoding", "gzip"); 65 | IOT.server.send_P(200, "image/x-icon", favicon_png, sizeof(favicon_png)); 66 | } 67 | void handle_reconnecting_websocket() { 68 | IOT.server.sendHeader("Content-Encoding", "gzip"); 69 | IOT.server.send_P(200, "text/javascript ", reconnecting_websocket_js, sizeof(reconnecting_websocket_js)); 70 | } 71 | void handle_csvpng() { 72 | IOT.server.sendHeader("Content-Encoding", "gzip"); 73 | IOT.server.send_P(200, "image/x-icon", csv_png, sizeof(csv_png)); 74 | } 75 | void handle_lcd() { 76 | IOT.server.sendHeader("Content-Encoding", "gzip"); 77 | IOT.server.send_P(200, "font/woff", LCD_woff, sizeof(LCD_woff)); 78 | } 79 | void handle_jexcel_css() { 80 | IOT.server.sendHeader("Content-Encoding", "gzip"); 81 | IOT.server.send_P(200, "text/css", jexcel_css, sizeof(jexcel_css)); 82 | } 83 | void handle_jexcel_js() { 84 | IOT.server.sendHeader("Content-Encoding", "gzip"); 85 | IOT.server.send_P(200, "text/javascript", jexcel_js, sizeof(jexcel_js)); 86 | } 87 | void handle_jsuites_js() { 88 | IOT.server.sendHeader("Content-Encoding", "gzip"); 89 | IOT.server.send_P(200, "text/javascript", jsuites_js, sizeof(jsuites_js)); 90 | } 91 | void handle_jexcel_themes_css() { 92 | IOT.server.sendHeader("Content-Encoding", "gzip"); 93 | IOT.server.send_P(200, "text/css", jexcel_themes_css, sizeof(jexcel_themes_css)); 94 | } 95 | void handle_jsuites_css() { 96 | IOT.server.sendHeader("Content-Encoding", "gzip"); 97 | IOT.server.send_P(200, "text/css", jsuites_css, sizeof(jsuites_css)); 98 | } 99 | void setup() { 100 | packet = 0; 101 | last_millis = millis(); 102 | data_ready = false; 103 | Serial.begin(115200); 104 | kbd_begin(); 105 | pinMode(BUTTON, INPUT); 106 | pinMode(SCLCK, INPUT); 107 | pinMode(MISO, INPUT); 108 | pinMode(LED, OUTPUT); 109 | 110 | attachInterrupt(digitalPinToInterrupt(SCLCK), clock_isr, FALLING); 111 | 112 | String deviceName = "VINCA_reader"; // Soft AP and device name for OTA. 113 | IOT.initIOT(deviceName, "password", "ThisPa55word!", deviceName); 114 | 115 | IOT.server.on("/", handle_index); 116 | IOT.server.on("/favicon.png", handle_favicon); 117 | IOT.server.on("/csv.png", handle_csvpng); 118 | IOT.server.on("/LCD.woff", handle_lcd); 119 | IOT.server.on("/reconnecting_websocket.js", handle_reconnecting_websocket); 120 | IOT.server.on("/jexcel.css", handle_jexcel_css); 121 | IOT.server.on("/jexcel.js", handle_jexcel_js); 122 | IOT.server.on("/jexcel.themes.css", handle_jexcel_themes_css); 123 | IOT.server.on("/jsuites.css", handle_jsuites_css); 124 | IOT.server.on("/jsuites.js", handle_jsuites_js); 125 | 126 | webSocket.begin(); 127 | webSocket.onEvent(webSocketEvent); 128 | } 129 | 130 | u32_t reverse(u32_t p) 131 | { 132 | u32_t r=0; 133 | for(int i=0;i<24;i++) 134 | { 135 | r |= ((p>>i) & 1)<<(23-i); 136 | } 137 | return r; 138 | } 139 | 140 | void decode_vinca_bitstream(u32_t p) 141 | { 142 | bool inch = p & 0x1; 143 | float sign = p & 0x8 ? -1.0 : 1.0; 144 | p &= 0xfffff0; 145 | p = reverse(p); 146 | 147 | float value = inch ? p * 0.0005 : p * 0.01; 148 | value *= sign; 149 | sprintf(last_value, "%.4f%s\n", value, inch ? "\"" : "mm"); 150 | } 151 | 152 | void loop() 153 | { 154 | IOT.handle(); 155 | webSocket.loop(); 156 | 157 | if (data_ready && last_data_ready_packet != data_ready_packet) 158 | { 159 | last_data_ready_packet = data_ready_packet; 160 | data_ready = false; 161 | decode_vinca_bitstream(last_data_ready_packet); 162 | 163 | Serial.printf(last_value); 164 | webSocket.broadcastTXT(last_value); 165 | } 166 | static unsigned long last_button_milis = 0; 167 | // Check button and debounce. 168 | if (digitalRead(BUTTON) == 0 && millis() > last_button_milis + 500) 169 | { 170 | kbd_print(last_value); 171 | last_button_milis = millis(); 172 | String msg = "*"; 173 | msg += last_value; 174 | webSocket.broadcastTXT(msg); 175 | } 176 | 177 | } 178 | -------------------------------------------------------------------------------- /web/LCD.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/web/LCD.woff -------------------------------------------------------------------------------- /web/csv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/web/csv.png -------------------------------------------------------------------------------- /web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liba2k/VINCA_reader/0a978a302755b141f3d28edaa9669466817d4198/web/favicon.png -------------------------------------------------------------------------------- /web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 |
0.00mm
25 |
26 | 31 |
32 | 33 | 34 | 65 | 66 | -------------------------------------------------------------------------------- /web/jexcel.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jexcel-border-color:#000; 3 | } 4 | 5 | .jexcel_container { 6 | display:inline-block; 7 | padding-right:2px; 8 | box-sizing: border-box; 9 | overscroll-behavior: contain; 10 | outline: none; 11 | } 12 | 13 | .jexcel_container.fullscreen { 14 | position:fixed; 15 | top:0px; 16 | left:0px; 17 | width:100%; 18 | height:100%; 19 | z-index:21; 20 | } 21 | 22 | .jexcel_container.fullscreen .jexcel_content { 23 | overflow:auto; 24 | width:100%; 25 | height:100%; 26 | background-color:#ffffff; 27 | } 28 | 29 | .jexcel_container.with-toolbar .jexcel > thead > tr > td { 30 | top: 0; 31 | } 32 | 33 | .jexcel_container.fullscreen.with-toolbar { 34 | height: calc(100% - 46px); 35 | } 36 | 37 | .jexcel_content { 38 | display:inline-block; 39 | box-sizing: border-box; 40 | padding-right:3px; 41 | padding-bottom:3px; 42 | position:relative; 43 | scrollbar-width: thin; 44 | scrollbar-color: #666 transparent; 45 | } 46 | 47 | @supports (-moz-appearance:none) { 48 | .jexcel_content { padding-right:10px; } 49 | } 50 | 51 | .jexcel_content::-webkit-scrollbar { 52 | width: 8px; 53 | height: 8px; 54 | } 55 | 56 | .jexcel_content::-webkit-scrollbar-track { 57 | background: #eee; 58 | } 59 | 60 | .jexcel_content::-webkit-scrollbar-thumb { 61 | background: #666; 62 | } 63 | 64 | .jexcel { 65 | border-collapse: separate; 66 | table-layout: fixed; 67 | white-space: nowrap; 68 | empty-cells: show; 69 | border: 0px; 70 | background-color: #fff; 71 | width: 0; 72 | 73 | border-top: 1px solid transparent; 74 | border-left: 1px solid transparent; 75 | border-right: 1px solid #ccc; 76 | border-bottom: 1px solid #ccc; 77 | } 78 | 79 | .jexcel > thead > tr > td 80 | { 81 | border-top: 1px solid #ccc; 82 | border-left: 1px solid #ccc; 83 | border-right: 1px solid transparent; 84 | border-bottom: 1px solid transparent; 85 | background-color: #f3f3f3; 86 | padding: 2px; 87 | cursor: pointer; 88 | box-sizing: border-box; 89 | overflow: hidden; 90 | position: -webkit-sticky; 91 | position: sticky; 92 | top: 0; 93 | z-index:2; 94 | } 95 | 96 | .jexcel_container.with-toolbar .jexcel > thead > tr > td 97 | { 98 | top:42px; 99 | } 100 | 101 | .jexcel > thead > tr > td.dragging 102 | { 103 | background-color:#fff; 104 | opacity:0.5; 105 | } 106 | 107 | .jexcel > thead > tr > td.selected 108 | { 109 | background-color:#dcdcdc; 110 | } 111 | 112 | .jexcel > thead > tr > td.arrow-up 113 | { 114 | background-repeat:no-repeat; 115 | background-position:center right 5px; 116 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 14l5-5 5 5H7z' fill='gray'/%3E%3C/svg%3E"); 117 | text-decoration:underline; 118 | } 119 | 120 | .jexcel > thead > tr > td.arrow-down 121 | { 122 | background-repeat:no-repeat; 123 | background-position:center right 5px; 124 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); 125 | text-decoration:underline; 126 | } 127 | 128 | .jexcel > tbody > tr > td:first-child 129 | { 130 | position:relative; 131 | background-color:#f3f3f3; 132 | text-align:center; 133 | } 134 | 135 | .jexcel > tbody.resizable > tr > td:first-child::before 136 | { 137 | content:'\00a0'; 138 | width:100%; 139 | height:3px; 140 | position:absolute; 141 | bottom:0px; 142 | left:0px; 143 | cursor:row-resize; 144 | } 145 | 146 | .jexcel > tbody.draggable > tr > td:first-child::after 147 | { 148 | content:'\00a0'; 149 | width:3px; 150 | height:100%; 151 | position:absolute; 152 | top:0px; 153 | right:0px; 154 | cursor:move; 155 | } 156 | 157 | .jexcel > tbody > tr.dragging > td 158 | { 159 | background-color:#eee; 160 | opacity:0.5; 161 | } 162 | 163 | .jexcel > tbody > tr > td 164 | { 165 | border-top:1px solid #ccc; 166 | border-left:1px solid #ccc; 167 | border-right:1px solid transparent; 168 | border-bottom:1px solid transparent; 169 | padding:4px; 170 | white-space: nowrap; 171 | box-sizing: border-box; 172 | line-height:1em; 173 | } 174 | 175 | .jexcel_overflow > tbody > tr > td { 176 | overflow: hidden; 177 | } 178 | 179 | .jexcel > tbody > tr > td:last-child 180 | { 181 | overflow: hidden; 182 | } 183 | 184 | .jexcel > tbody > tr > td > img 185 | { 186 | display:inline-block; 187 | max-width:100px; 188 | } 189 | 190 | .jexcel > tbody > tr > td.readonly 191 | { 192 | color:rgba(0,0,0,0.3) 193 | } 194 | .jexcel > tbody > tr.selected > td:first-child 195 | { 196 | background-color:#dcdcdc; 197 | } 198 | .jexcel > tbody > tr > td > select, 199 | .jexcel > tbody > tr > td > input, 200 | .jexcel > tbody > tr > td > textarea 201 | { 202 | border:0px; 203 | border-radius:0px; 204 | outline:0px; 205 | width:100%; 206 | margin:0px; 207 | padding:0px; 208 | padding-right:2px; 209 | background-color:transparent; 210 | box-sizing: border-box; 211 | } 212 | 213 | .jexcel > tbody > tr > td > textarea 214 | { 215 | resize: none; 216 | padding-top:6px !important; 217 | } 218 | 219 | .jexcel > tbody > tr > td > input[type=checkbox] 220 | { 221 | width:12px; 222 | margin-top:2px; 223 | } 224 | .jexcel > tbody > tr > td > input[type=radio] 225 | { 226 | width:12px; 227 | margin-top:2px; 228 | } 229 | 230 | .jexcel > tbody > tr > td > select 231 | { 232 | -webkit-appearance: none; 233 | -moz-appearance: none; 234 | appearance: none; 235 | background-repeat: no-repeat; 236 | background-position-x: 100%; 237 | background-position-y: 40%; 238 | background-image: url(data:image/svg+xml;base64,PHN2ZyBmaWxsPSdibGFjaycgaGVpZ2h0PScyNCcgdmlld0JveD0nMCAwIDI0IDI0JyB3aWR0aD0nMjQnIHhtbG5zPSdodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2Zyc+PHBhdGggZD0nTTcgMTBsNSA1IDUtNXonLz48cGF0aCBkPSdNMCAwaDI0djI0SDB6JyBmaWxsPSdub25lJy8+PC9zdmc+); 239 | } 240 | 241 | .jexcel > tbody > tr > td.jexcel_dropdown 242 | { 243 | background-repeat: no-repeat; 244 | background-position:top 50% right 5px; 245 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E"); 246 | text-overflow: ellipsis; 247 | overflow-x:hidden; 248 | } 249 | 250 | .jexcel > tbody > tr > td.jexcel_dropdown.jexcel_comments 251 | { 252 | background:url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='lightgray'/%3E%3C/svg%3E") top 50% right 5px no-repeat, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII=') top right no-repeat; 253 | } 254 | 255 | .jexcel > tbody > tr > td > .color 256 | { 257 | width:90%; 258 | height:10px; 259 | margin:auto; 260 | } 261 | 262 | .jexcel > tbody > tr > td > a { 263 | text-decoration: underline; 264 | } 265 | 266 | .jexcel > tbody > tr > td.highlight > a { 267 | color: blue; 268 | cursor: pointer; 269 | } 270 | 271 | .jexcel > tfoot > tr > td 272 | { 273 | border-top: 1px solid #ccc; 274 | border-left: 1px solid #ccc; 275 | border-right: 1px solid transparent; 276 | border-bottom: 1px solid transparent; 277 | background-color: #f3f3f3; 278 | padding: 2px; 279 | cursor: pointer; 280 | box-sizing: border-box; 281 | overflow: hidden; 282 | } 283 | 284 | .jexcel .highlight { 285 | background-color:rgba(0,0,0,0.05); 286 | } 287 | 288 | .jexcel .highlight-top { 289 | border-top:1px solid #000; /* var(--jexcel-border-color);*/ 290 | box-shadow: 0px -1px #ccc; 291 | } 292 | 293 | .jexcel .highlight-left { 294 | border-left:1px solid #000; /* var(--jexcel-border-color);*/ 295 | box-shadow: -1px 0px #ccc; 296 | } 297 | 298 | .jexcel .highlight-right { 299 | border-right:1px solid #000; /* var(--jexcel-border-color);*/ 300 | } 301 | 302 | .jexcel .highlight-bottom { 303 | border-bottom:1px solid #000; /* var(--jexcel-border-color);*/ 304 | } 305 | 306 | .jexcel .highlight-top.highlight-left { 307 | box-shadow: -1px -1px #ccc; 308 | -webkit-box-shadow: -1px -1px #ccc; 309 | -moz-box-shadow: -1px -1px #ccc; 310 | } 311 | 312 | .jexcel .highlight-selected 313 | { 314 | background-color:rgba(0,0,0,0.0); 315 | } 316 | .jexcel .selection 317 | { 318 | background-color:rgba(0,0,0,0.05); 319 | } 320 | .jexcel .selection-left 321 | { 322 | border-left:1px dotted #000; 323 | } 324 | .jexcel .selection-right 325 | { 326 | border-right:1px dotted #000; 327 | } 328 | .jexcel .selection-top 329 | { 330 | border-top:1px dotted #000; 331 | } 332 | .jexcel .selection-bottom 333 | { 334 | border-bottom:1px dotted #000; 335 | } 336 | .jexcel_corner 337 | { 338 | position:absolute; 339 | background-color: rgb(0, 0, 0); 340 | height: 1px; 341 | width: 1px; 342 | border: 1px solid rgb(255, 255, 255); 343 | top:-2000px; 344 | left:-2000px; 345 | cursor:crosshair; 346 | box-sizing: initial; 347 | z-index:20; 348 | padding: 2px; 349 | } 350 | 351 | .jexcel .editor 352 | { 353 | outline:0px solid transparent; 354 | overflow:visible; 355 | white-space: nowrap; 356 | text-align:left; 357 | padding:0px; 358 | box-sizing: border-box; 359 | overflow:visible !important; 360 | } 361 | 362 | .jexcel .editor > input 363 | { 364 | padding-left:4px; 365 | } 366 | 367 | .jexcel .editor .jupload 368 | { 369 | position:fixed; 370 | top:100%; 371 | z-index:40; 372 | user-select:none; 373 | -webkit-font-smoothing: antialiased; 374 | font-size: .875rem; 375 | letter-spacing: .2px; 376 | -webkit-border-radius: 4px; 377 | border-radius: 4px; 378 | -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 379 | box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 380 | padding:10px; 381 | background-color:#fff; 382 | width:300px; 383 | min-height:225px; 384 | margin-top:2px; 385 | } 386 | 387 | .jexcel .editor .jupload img 388 | { 389 | width:100%; 390 | height:auto; 391 | } 392 | 393 | .jexcel .editor .jexcel_richtext 394 | { 395 | position:fixed; 396 | top:100%; 397 | z-index:40; 398 | user-select:none; 399 | -webkit-font-smoothing: antialiased; 400 | font-size: .875rem; 401 | letter-spacing: .2px; 402 | -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 403 | box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 404 | padding:10px; 405 | background-color:#fff; 406 | min-width:280px; 407 | max-width:310px; 408 | margin-top:2px; 409 | text-align:left; 410 | } 411 | 412 | .jexcel .editor .jclose:after 413 | { 414 | position:absolute; 415 | top:0; 416 | right:0; 417 | margin:10px; 418 | content:'close'; 419 | font-family:'Material icons'; 420 | font-size:24px; 421 | width:24px; 422 | height:24px; 423 | line-height:24px; 424 | cursor:pointer; 425 | text-shadow: 0px 0px 5px #fff; 426 | } 427 | 428 | .jexcel, .jexcel td, .jexcel_corner 429 | { 430 | -webkit-touch-callout: none; 431 | -webkit-user-select: none; 432 | -khtml-user-select: none; 433 | -moz-user-select: none; 434 | -ms-user-select: none; 435 | user-select: none; 436 | -webkit-user-drag: none; 437 | -khtml-user-drag: none; 438 | -moz-user-drag: none; 439 | -o-user-drag: none; 440 | user-drag: none; 441 | } 442 | 443 | .jexcel_textarea 444 | { 445 | position:absolute; 446 | top:-999px; 447 | left:-999px; 448 | width:1px; 449 | height:1px; 450 | } 451 | .jexcel .dragline 452 | { 453 | position:absolute; 454 | } 455 | .jexcel .dragline div 456 | { 457 | position:relative; 458 | top:-6px; 459 | height:5px; 460 | width:22px; 461 | } 462 | .jexcel .dragline div:hover 463 | { 464 | cursor:move; 465 | } 466 | 467 | .jexcel .onDrag 468 | { 469 | background-color:rgba(0,0,0,0.6); 470 | } 471 | 472 | .jexcel .error 473 | { 474 | border:1px solid red; 475 | } 476 | 477 | .jexcel thead td.resizing 478 | { 479 | border-right-style:dotted !important; 480 | border-right-color:red !important; 481 | } 482 | 483 | .jexcel tbody tr.resizing > td 484 | { 485 | border-bottom-style:dotted !important; 486 | border-bottom-color:red !important; 487 | } 488 | 489 | .jexcel tbody td.resizing 490 | { 491 | border-right-style:dotted !important; 492 | border-right-color:red !important; 493 | } 494 | 495 | .jexcel .jdropdown-header 496 | { 497 | border:0px !important; 498 | outline:none !important; 499 | width:100% !important; 500 | height:100% !important; 501 | padding:0px !important; 502 | padding-left:8px !important; 503 | } 504 | 505 | .jexcel .jdropdown-container 506 | { 507 | margin-top:1px; 508 | } 509 | 510 | .jexcel .jdropdown-container-header { 511 | padding: 0px; 512 | margin: 0px; 513 | height: inherit; 514 | } 515 | 516 | .jexcel .jdropdown-picker 517 | { 518 | border:0px !important; 519 | padding:0px !important; 520 | width:inherit; 521 | height:inherit; 522 | } 523 | 524 | .jexcel .jexcel_comments 525 | { 526 | background:url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFuGlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHhtcDpNb2RpZnlEYXRlPSIyMDE5LTAxLTMxVDE4OjU1OjA4WiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDphMTlhZDJmOC1kMDI2LTI1NDItODhjOS1iZTRkYjkyMmQ0MmQiIHhtcE1NOkRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpkOGI5NDUyMS00ZjEwLWQ5NDktYjUwNC0wZmU1N2I3Nzk1MDEiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIiBwaG90b3Nob3A6Q29sb3JNb2RlPSIzIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDplMzdjYmE1ZS1hYTMwLWNkNDUtYTAyNS1lOWYxZjk2MzUzOGUiIHN0RXZ0OndoZW49IjIwMTktMDEtMzFUMTg6NTU6MDhaIiBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOSAoV2luZG93cykiLz4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOmExOWFkMmY4LWQwMjYtMjU0Mi04OGM5LWJlNGRiOTIyZDQyZCIgc3RFdnQ6d2hlbj0iMjAxOS0wMS0zMVQxODo1NTowOFoiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4En6MDAAAAX0lEQVQYlX3KOw6AIBBAwS32RpJADXfx0pTET+ERZJ8F8RODFtONsG0QAoh0CSDM82dqodaBdQXnfoLZQM7gPai+wjNNE8R4pTuAYNZSKZASqL7CMy0LxNgJp30fKYUDi3+vIqb/+rUAAAAASUVORK5CYII='); 527 | background-repeat: no-repeat; 528 | background-position: top right; 529 | } 530 | 531 | .jexcel .sp-replacer 532 | { 533 | margin: 2px; 534 | border:0px; 535 | } 536 | 537 | .jexcel > thead > tr.jexcel_filter > td > input 538 | { 539 | border:0px; 540 | width:100%; 541 | outline:none; 542 | } 543 | 544 | .jexcel_about { 545 | float: right; 546 | font-size: 0.7em; 547 | padding: 2px; 548 | text-transform: uppercase; 549 | letter-spacing: 1px; 550 | display: none; 551 | } 552 | .jexcel_about a { 553 | color: #ccc; 554 | text-decoration: none; 555 | } 556 | 557 | .jexcel_about img { 558 | display: none; 559 | } 560 | 561 | .jexcel_filter 562 | { 563 | display:flex; 564 | justify-content:space-between; 565 | margin-bottom:4px; 566 | } 567 | 568 | .jexcel_filter > div 569 | { 570 | padding:8px; 571 | align-items:center; 572 | } 573 | 574 | .jexcel_pagination 575 | { 576 | display:flex; 577 | justify-content:space-between; 578 | align-items:center; 579 | } 580 | 581 | .jexcel_pagination > div 582 | { 583 | display:flex; 584 | padding:10px; 585 | } 586 | 587 | .jexcel_pagination > div:last-child 588 | { 589 | padding-right:10px; 590 | padding-top:10px; 591 | } 592 | 593 | .jexcel_pagination > div > div 594 | { 595 | text-align:center; 596 | width:36px; 597 | height:36px; 598 | line-height:34px; 599 | border:1px solid #ccc; 600 | box-sizing: border-box; 601 | margin-left:2px; 602 | cursor:pointer; 603 | } 604 | 605 | .jexcel_page 606 | { 607 | font-size:0.8em; 608 | } 609 | 610 | .jexcel_page_selected 611 | { 612 | font-weight:bold; 613 | background-color:#f3f3f3; 614 | } 615 | 616 | .jexcel_toolbar 617 | { 618 | display:flex; 619 | background-color:#f3f3f3; 620 | border:1px solid #ccc; 621 | padding:4px; 622 | margin:0px 2px 4px 1px; 623 | position:sticky; 624 | top:0px; 625 | z-index:21; 626 | } 627 | 628 | .jexcel_toolbar:empty 629 | { 630 | display:none; 631 | } 632 | 633 | .jexcel_toolbar i.jexcel_toolbar_item 634 | { 635 | width:24px; 636 | height:24px; 637 | padding:4px; 638 | cursor:pointer; 639 | display:inline-block; 640 | } 641 | 642 | .jexcel_toolbar i.jexcel_toolbar_item:hover 643 | { 644 | background-color:#ddd; 645 | } 646 | 647 | .jexcel_toolbar select.jexcel_toolbar_item 648 | { 649 | margin-left:2px; 650 | margin-right:2px; 651 | display:inline-block; 652 | border:0px; 653 | background-color:transparent; 654 | padding-right:10px; 655 | } 656 | 657 | .jexcel .dragging-left 658 | { 659 | background-repeat: no-repeat; 660 | background-position:top 50% left 0px; 661 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M14 7l-5 5 5 5V7z'/%3E%3Cpath fill='none' d='M24 0v24H0V0h24z'/%3E%3C/svg%3E"); 662 | } 663 | 664 | .jexcel .dragging-right 665 | { 666 | background-repeat: no-repeat; 667 | background-position:top 50% right 0px; 668 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M10 17l5-5-5-5v10z'/%3E%3Cpath fill='none' d='M0 24V0h24v24H0z'/%3E%3C/svg%3E"); 669 | } 670 | 671 | .jexcel_tabs .jexcel_tab 672 | { 673 | display:none; 674 | } 675 | 676 | .jexcel_tabs .jexcel_tab_link 677 | { 678 | display:inline-block; 679 | padding:10px; 680 | padding-left:20px; 681 | padding-right:20px; 682 | margin-right:5px; 683 | margin-bottom:5px; 684 | background-color:#f3f3f3; 685 | cursor:pointer; 686 | } 687 | 688 | .jexcel_tabs .jexcel_tab_link.selected 689 | { 690 | background-color:#ddd; 691 | } 692 | 693 | .jexcel_hidden_index > tbody > tr > td:first-child, 694 | .jexcel_hidden_index > thead > tr > td:first-child, 695 | .jexcel_hidden_index > tfoot > tr > td:first-child, 696 | .jexcel_hidden_index > colgroup > col:first-child 697 | { 698 | display:none; 699 | } 700 | 701 | 702 | 703 | .jexcel .jrating { 704 | display: inline-flex; 705 | } 706 | .jexcel .jrating > div { 707 | zoom: 0.55; 708 | } 709 | 710 | .jexcel .copying-top { 711 | border-top:1px dashed #000; 712 | } 713 | 714 | .jexcel .copying-left { 715 | border-left:1px dashed #000; 716 | } 717 | 718 | .jexcel .copying-right { 719 | border-right:1px dashed #000; 720 | } 721 | 722 | .jexcel .copying-bottom { 723 | border-bottom:1px dashed #000; 724 | } 725 | 726 | .jexcel .jexcel_column_filter { 727 | background-repeat: no-repeat; 728 | background-position: top 50% right 5px; 729 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 730 | text-overflow: ellipsis; 731 | overflow: hidden; 732 | padding: 0px; 733 | padding-left: 6px; 734 | padding-right: 20px; 735 | } 736 | 737 | .jexcel thead .jexcel_freezed, .jexcel tfoot .jexcel_freezed { 738 | left: 0px; 739 | z-index: 3 !important; 740 | box-shadow: 2px 0px 2px 0.2px #ccc !important; 741 | -webkit-box-shadow: 2px 0px 2px 0.2px #ccc !important; 742 | -moz-box-shadow: 2px 0px 2px 0.2px #ccc !important; 743 | } 744 | 745 | .jexcel tbody .jexcel_freezed { 746 | position: relative; 747 | background-color: #fff; 748 | box-shadow: 1px 1px 1px 1px #ccc !important; 749 | -webkit-box-shadow: 2px 4px 4px 0.1px #ccc !important; 750 | -moz-box-shadow: 2px 4px 4px 0.1px #ccc !important; 751 | } 752 | 753 | .red { 754 | color: red; 755 | } -------------------------------------------------------------------------------- /web/jexcel.themes.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --jexcel_header_color: #888; 3 | --jexcel_header_color_highlighted: #444; 4 | --jexcel_header_background: #313131; 5 | --jexcel_header_background_highlighted: #777; 6 | --jexcel_content_color: rgb(220, 220, 220); 7 | --jexcel_content_color_highlighted: rgb(220, 220, 220); 8 | --jexcel_content_background: rgb(21, 32, 43); 9 | --jexcel_content_background_highlighted: rgb(37, 37, 37); 10 | --jexcel_menu_background: #7e7e7e; 11 | --jexcel_menu_background_highlighted: #ebebeb; 12 | --jexcel_menu_color: #ddd; 13 | --jexcel_menu_color_highlighted: #222; 14 | --jexcel_border_color: #5f5f5f; 15 | --jexcel_border_color_highlighted: #999; 16 | --active_color: rgb(238, 238, 238); 17 | --active-color: var(--active_color); 18 | } 19 | 20 | .jexcel { 21 | border-bottom: 1px solid var(--jexcel_border_color); 22 | border-right: 1px solid var(--jexcel_border_color); 23 | } 24 | 25 | .jexcel > tbody > tr > td, 26 | .jexcel > thead > tr > td { 27 | border-top: 1px solid var(--jexcel_border_color); 28 | border-left: 1px solid var(--jexcel_border_color); 29 | background-color: var(--jexcel_content_background); 30 | color: var(--jexcel_content_color); 31 | } 32 | 33 | .jexcel > tbody > tr > td:first-child, 34 | .jexcel > thead > tr > td { 35 | background-color: var(--jexcel_header_background); 36 | color: var(--jexcel_header_color); 37 | } 38 | 39 | .jexcel > thead > tr > td.selected, 40 | .jexcel > tbody > tr.selected > td:first-child { 41 | background-color: var(--jexcel_header_background_highlighted); 42 | color: var(--jexcel_header_color_highlighted); 43 | } 44 | 45 | .jexcel > tbody > tr > td.jexcel_cursor a { 46 | color: var(--active-color); 47 | } 48 | 49 | .jexcel_pagination > div > div { 50 | color: var(--jexcel_header_color); 51 | background: var(--jexcel_header_background); 52 | border: 1px solid var(--jexcel_border_color); 53 | } 54 | 55 | .jexcel_page, 56 | .jexcel_container input, 57 | .jexcel_container select { 58 | color: var(--jexcel_header_color); 59 | background: var(--jexcel_header_background); 60 | border: 1px solid var(--jexcel_border_color); 61 | } 62 | 63 | .jexcel_contextmenu { 64 | border: 1px solid var(--jexcel_border_color); 65 | background: var(--jexcel_menu_background); 66 | color: var(--jexcel_menu_color); 67 | box-shadow: var(--jexcel_menu_box_shadow); 68 | -webkit-box-shadow: var(--jexcel_menu_box_shadow); 69 | -moz-box-shadow: var(--jexcel_menu_box_shadow); 70 | } 71 | 72 | .jexcel_contextmenu > div a { 73 | color: var(--jexcel_menu_color); 74 | } 75 | 76 | .jexcel_contextmenu > div:not(.contextmenu-line):hover a { 77 | color: var(--jexcel_menu_color_highlighted); 78 | } 79 | 80 | .jexcel_contextmenu > div:not(.contextmenu-line):hover { 81 | background: var(--jexcel_menu_background_highlighted); 82 | } 83 | 84 | .jexcel_dropdown .jdropdown-container, 85 | .jexcel_dropdown .jdropdown-content { 86 | background-color: var(--jexcel_content_background); 87 | color: var(--jexcel_content_color); 88 | } 89 | 90 | .jexcel_dropdown .jdropdown-item { 91 | color: var(--jexcel_content_color); 92 | } 93 | 94 | .jexcel_dropdown .jdropdown-item:hover, 95 | .jexcel_dropdown .jdropdown-selected, 96 | .jexcel_dropdown .jdropdown-cursor { 97 | background-color: var(--jexcel_content_background_highlighted); 98 | color: var(--jexcel_content_color_highlighted); 99 | } 100 | 101 | .jexcel .jcalendar-content { 102 | background-color: var(--jexcel_header_background); 103 | color: var(--jexcel_header_color); 104 | } 105 | 106 | .jexcel .jcalendar-content > table { 107 | background-color: var(--jexcel_content_background); 108 | color: var(--jexcel_content_color); 109 | } 110 | 111 | .jexcel .jcalendar-weekday { 112 | background-color: var(--jexcel_content_background_highlighted); 113 | color: var(--jexcel_content_color_highlighted); 114 | } 115 | 116 | .jexcel .jcalendar-sunday { 117 | color: var(--jexcel_header_color); 118 | } 119 | 120 | .jexcel .jcalendar-selected { 121 | background-color: var(--jexcel_content_background_highlighted); 122 | color: var(--jexcel_content_color_highlighted); 123 | } 124 | 125 | .jexcel_toolbar i.jexcel_toolbar_item { 126 | color: var(--jexcel_content_color); 127 | } 128 | 129 | .jexcel_toolbar i.jexcel_toolbar_item:hover { 130 | background: var(--jexcel_content_background_highlighted); 131 | color: var(--jexcel_content_color_highlighted); 132 | } 133 | 134 | .jexcel_toolbar { 135 | background: var(--jexcel_header_background); 136 | } 137 | 138 | .jexcel_content::-webkit-scrollbar-track { 139 | background: var(--jexcel_background_head); 140 | } 141 | 142 | .jexcel_content::-webkit-scrollbar-thumb { 143 | background: var(--jexcel_background_head_highlighted); 144 | } 145 | 146 | 147 | .jexcel_border_main { 148 | border: 1px solid #000; 149 | border-color: var(--jexcel_border_color_highlighted); 150 | } 151 | 152 | .jexcel .highlight { 153 | background-color: var(--jexcel_content_background_highlighted); 154 | } 155 | 156 | .jexcel .highlight-bottom { 157 | border-bottom: 1px solid var(--jexcel_border_color_highlighted); 158 | } 159 | .jexcel .highlight-right { 160 | border-right: 1px solid var(--jexcel_border_color_highlighted); 161 | } 162 | .jexcel .highlight-left { 163 | border-left: 1px solid var(--jexcel_border_color_highlighted); 164 | } 165 | .jexcel .highlight-top { 166 | border-top: 1px solid var(--jexcel_border_color_highlighted); 167 | } 168 | .jexcel .copying-top { 169 | border-top-color: var(--jexcel_border_color_highlighted); 170 | } 171 | .jexcel .copying-right { 172 | border-right-color: var(--jexcel_border_color_highlighted); 173 | } 174 | .jexcel .copying-left { 175 | border-left-color: var(--jexcel_border_color_highlighted); 176 | } 177 | .jexcel .copying-bottom { 178 | border-bottom-color: var(--jexcel_border_color_highlighted); 179 | } 180 | .jexcel_border_main, .jexcel .highlight-top.highlight-left, .jexcel .highlight-top, .jexcel .highlight-left { 181 | -webkit-box-shadow: unset; 182 | box-shadow: unset; 183 | } -------------------------------------------------------------------------------- /web/jsuites.css: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * (c) jSuites Javascript Web Components 4 | * 5 | * Website: https://jsuites.net 6 | * Description: Create amazing web based applications. 7 | * 8 | * MIT License 9 | * 10 | */ 11 | 12 | :root { 13 | --button-color: #298BA8; 14 | --active-color: #007aff; 15 | --safe-area-top: env(safe-area-inset-top); 16 | --safe-area-bottom: env(safe-area-inset-bottom); 17 | } 18 | 19 | [data-visible="false"] { 20 | display: none; 21 | } 22 | 23 | div[data-before]:before { 24 | content: attr(data-before); 25 | } 26 | 27 | .unselectable { 28 | -webkit-touch-callout: none; 29 | -webkit-user-select: none; 30 | -khtml-user-select: none; 31 | -moz-user-select: none; 32 | -ms-user-select: none; 33 | user-select: none; 34 | } 35 | 36 | .jreadonly { 37 | pointer-events: none; 38 | } 39 | 40 | .jdragging { 41 | opacity:0.2; 42 | filter: alpha(opacity=20); 43 | } 44 | 45 | .jupload.input { 46 | position: relative; 47 | box-sizing: border-box; 48 | background-size: initial; 49 | height: 33px; 50 | min-height: initial; 51 | padding: 6px; 52 | padding-right: 30px; 53 | } 54 | 55 | .jupload.input:before { 56 | content: "save"; 57 | font-size: 18px; 58 | font-family: "Material Icons"; 59 | color: #000; 60 | position: absolute; 61 | right: 5px; 62 | } 63 | 64 | .jupload:empty:before { 65 | z-index: 0; 66 | } 67 | 68 | .jupload img { 69 | width: 100%; 70 | } 71 | 72 | .jupload.input img { 73 | width: initial; 74 | max-width: 100%; 75 | height: 100%; 76 | } 77 | 78 | .jupload[data-multiple] { 79 | padding: 10px; 80 | } 81 | 82 | .jupload[data-multiple] img { 83 | height: 70px; 84 | width: 100px; 85 | object-fit: cover; 86 | margin-right: 5px; 87 | margin-bottom: 5px; 88 | } 89 | 90 | .jupload { 91 | position: relative; 92 | border: 1px dotted #eee; 93 | cursor: pointer; 94 | box-sizing: border-box; 95 | width: 100%; 96 | max-height: 100%; 97 | min-height: 180px; 98 | } 99 | 100 | .jupload:not(.input):before { 101 | content: "\e2c3"; 102 | font-family: "Material Icons"; 103 | font-size: 90px; 104 | color: #eee; 105 | width: 100%; 106 | height: 100%; 107 | display: flex; 108 | align-items: center; 109 | justify-content: center; 110 | position: absolute; 111 | z-index: -1; 112 | } 113 | 114 | .jupload-item { 115 | padding-right: 22px; 116 | border-radius: 1px; 117 | display: inline-block; 118 | position: relative; 119 | } 120 | 121 | .jphoto { 122 | position: relative; 123 | border: 1px dotted #eee; 124 | cursor: pointer; 125 | box-sizing: border-box; 126 | width: 100%; 127 | display: flex; 128 | align-items: center; 129 | justify-content: center; 130 | } 131 | 132 | .jphoto:empty:before { 133 | content: "\e2c3"; 134 | font-family: "Material Icons"; 135 | font-size: 90px; 136 | color: #eee; 137 | width: 100%; 138 | height: 100%; 139 | } 140 | 141 | .jremove { 142 | opacity: 0.2; 143 | filter: alpha(opacity=20); 144 | } 145 | 146 | .round img { 147 | border-radius: 1000px; 148 | } 149 | 150 | .jtooltip { 151 | position: fixed; 152 | top: 10px; 153 | left: 10px; 154 | z-index: 5; 155 | 156 | font-family: initial; 157 | font-size: 12px; 158 | color: #000; 159 | background-color: #fff; 160 | border: 1px solid black; 161 | padding: 8px; 162 | margin: 10px; 163 | 164 | display: block; 165 | animation: fadeIn 0.5s; 166 | pointer-events: none; 167 | } 168 | 169 | .jtooltip:empty { 170 | display: none; 171 | } 172 | 173 | @keyframes fadeIn { 174 | 0% { 175 | opacity: 0; 176 | } 177 | 100% { 178 | opacity: 1; 179 | } 180 | } 181 | 182 | /** Animations **/ 183 | .fade-in { 184 | animation: fade-in 2s forwards; 185 | } 186 | 187 | .fade-out { 188 | animation: fade-out 1s forwards; 189 | } 190 | 191 | .slide-left-in { 192 | position: relative; 193 | animation: slide-left-in 0.4s forwards; 194 | } 195 | 196 | .slide-left-out { 197 | position: relative; 198 | animation: slide-left-out 0.4s forwards; 199 | } 200 | 201 | .slide-right-in { 202 | position: relative; 203 | animation: slide-right-in 0.4s forwards; 204 | } 205 | 206 | .slide-right-out { 207 | position: relative; 208 | animation: slide-right-out 0.4s forwards; 209 | } 210 | 211 | .slide-top-in { 212 | position: relative; 213 | animation: slide-top-in 0.4s forwards; 214 | } 215 | 216 | .slide-top-out { 217 | position: relative; 218 | animation: slide-top-out 0.2s forwards; 219 | } 220 | 221 | .slide-bottom-in { 222 | position: relative; 223 | animation: slide-bottom-in 0.4s forwards; 224 | } 225 | 226 | .slide-bottom-out { 227 | position: relative; 228 | animation: slide-bottom-out 0.1s forwards; 229 | } 230 | 231 | .slide-left-in > div { 232 | -webkit-transform: translateZ(0px); 233 | -webkit-transform: translate3d(0,0,0); 234 | } 235 | 236 | .slide-left-out > div { 237 | -webkit-transform: translateZ(0px); 238 | -webkit-transform: translate3d(0,0,0); 239 | } 240 | 241 | .slide-right-in > div { 242 | -webkit-transform: translateZ(0px); 243 | -webkit-transform: translate3d(0,0,0); 244 | } 245 | 246 | .slide-right-out > div { 247 | -webkit-transform: translateZ(0px); 248 | -webkit-transform: translate3d(0,0,0); 249 | } 250 | 251 | .spin { 252 | animation: spin 2s infinite linear; 253 | } 254 | 255 | /** Fadein and Fadeout **/ 256 | @keyframes fade-in { 257 | 0% { opacity: 0; } 258 | 100% { opacity: 100; } 259 | } 260 | 261 | @-webkit-keyframes fade-in { 262 | 0% { opacity: 0; } 263 | 100% { opacity: 100; } 264 | } 265 | 266 | @keyframes fade-out { 267 | 0% { opacity: 100; } 268 | 100% { opacity: 0; } 269 | } 270 | 271 | @-webkit-keyframes fade-out { 272 | 0% { opacity: 100; } 273 | 100% { opacity: 0; } 274 | } 275 | 276 | /** Keyframes Left to Right **/ 277 | @keyframes slide-left-in { 278 | 0% { left: -100%; } 279 | 100% { left: 0%; } 280 | } 281 | 282 | @-webkit-keyframes slide-left-in { 283 | 0% { left: -100%; } 284 | 100% { left: 0%; } 285 | } 286 | 287 | @keyframes slide-left-out { 288 | 0% { left: 0%; } 289 | 100% { left: -100%; } 290 | } 291 | 292 | @-webkit-keyframes slide-left-out { 293 | 0% { left: 0%; } 294 | 100% { left: -100%; } 295 | } 296 | 297 | /** Keyframes Right to Left **/ 298 | @keyframes slide-right-in { 299 | 0% { left: 100%; } 300 | 100% { left: 0%; } 301 | } 302 | 303 | @-webkit-keyframes slide-right-in 304 | { 305 | 0% { left: 100%; } 306 | 100% { left: 0%; } 307 | } 308 | 309 | @keyframes slide-right-out { 310 | 0% { left: 0%; } 311 | 100% { left: 100%; } 312 | } 313 | 314 | @-webkit-keyframes slide-right-out { 315 | 0% { left: 0%; } 316 | 100% { left: 100%; } 317 | } 318 | 319 | /** Keyframes Top to Bottom **/ 320 | @keyframes slide-top-in { 321 | 0% { transform: translateY(-100%); } 322 | 100% { transform: translateY(0%); } 323 | } 324 | 325 | @-webkit-keyframes slide-top-in { 326 | 0% { transform: translateY(-100%); } 327 | 100% { -webkit-transform: translateY(0%); } 328 | } 329 | 330 | @keyframes slide-top-out { 331 | 0% { transform: translateY(0%); } 332 | 100% { transform: translateY(-100%); } 333 | } 334 | 335 | @-webkit-keyframes slide-top-out { 336 | 0% { -webkit-transform: translateY(0%); } 337 | 100% { -webkit-transform: translateY(-100%); } 338 | } 339 | 340 | /** Keyframes Bottom to Top **/ 341 | @keyframes slide-bottom-in { 342 | 0% { transform: translateY(100%); } 343 | 100% { transform: translateY(0%); } 344 | } 345 | 346 | @-webkit-keyframes slide-bottom-in { 347 | 0% { transform: translateY(100%); } 348 | 100% { -webkit-transform: translateY(0%); } 349 | } 350 | 351 | @keyframes slide-bottom-out { 352 | 0% { transform: translateY(0%); } 353 | 100% { transform: translateY(100%); } 354 | } 355 | 356 | @-webkit-keyframes slide-bottom-out { 357 | 0% { -webkit-transform: translateY(0%); } 358 | 100% { -webkit-transform: translateY(100%); } 359 | } 360 | 361 | @-webkit-keyframes spin { 362 | from { 363 | -webkit-transform:rotate(0deg); 364 | } 365 | to { 366 | -webkit-transform:rotate(359deg); 367 | } 368 | } 369 | 370 | @keyframes spin { 371 | from { 372 | transform:rotate(0deg); 373 | } 374 | to { 375 | transform:rotate(359deg); 376 | } 377 | } 378 | 379 | .jcalendar { 380 | position:absolute; 381 | z-index:9000; 382 | display:none; 383 | box-sizing:border-box; 384 | -webkit-touch-callout: none; 385 | -webkit-user-select: none; 386 | -khtml-user-select: none; 387 | -moz-user-select: none; 388 | -ms-user-select: none; 389 | user-select: none; 390 | -webkit-tap-highlight-color: rgba(0,0,0,0); 391 | -webkit-tap-highlight-color: transparent; 392 | min-width:280px; 393 | } 394 | 395 | .jcalendar.jcalendar-focus { 396 | display:block; 397 | } 398 | 399 | .jcalendar .jcalendar-backdrop { 400 | position:fixed; 401 | top:0px; 402 | left:0px; 403 | z-index:9000; 404 | min-width:100%; 405 | min-height:100%; 406 | background-color:rgba(0,0,0,0.5); 407 | border:0px; 408 | padding:0px; 409 | display:none; 410 | } 411 | 412 | .jcalendar .jcalendar-container { 413 | position:relative; 414 | box-sizing:border-box; 415 | } 416 | 417 | .jcalendar .jcalendar-content { 418 | position:absolute; 419 | z-index:9001; 420 | -webkit-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 421 | -moz-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 422 | box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 423 | background-color:#fff; 424 | } 425 | 426 | .jcalendar-header { 427 | text-align:center; 428 | } 429 | 430 | .jcalendar-header span { 431 | margin-right:4px; 432 | font-size:1.1em; 433 | font-weight:bold; 434 | } 435 | 436 | .jcalendar-prev { 437 | cursor:pointer; 438 | background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M15.41 16.59L10.83 12l4.58-4.59L14 6l-6 6 6 6 1.41-1.41z' fill='%23000' /%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3C/svg%3E"); 439 | background-position:center; 440 | background-repeat:no-repeat; 441 | } 442 | 443 | .jcalendar-next { 444 | cursor:pointer; 445 | background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M8.59 16.59L13.17 12 8.59 7.41 10 6l6 6-6 6-1.41-1.41z' fill='%23000' /%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3C/svg%3E"); 446 | background-position:center; 447 | background-repeat:no-repeat; 448 | } 449 | 450 | .jcalendar-weekday { 451 | font-weight: 600; 452 | background-color: #fcfcfc; 453 | padding: 14px; 454 | } 455 | 456 | .jcalendar-table > table { 457 | width:100%; 458 | background-color:#fff; 459 | } 460 | 461 | .jcalendar-table > table > thead { 462 | cursor:pointer; 463 | } 464 | 465 | .jcalendar-table thead td { 466 | padding:10px; 467 | height:40px; 468 | } 469 | 470 | .jcalendar-table > table > tbody td { 471 | box-sizing:border-box; 472 | cursor:pointer; 473 | padding:9px; 474 | font-size:0.9em; 475 | } 476 | 477 | 478 | .jcalendar-table tfoot td { 479 | padding:10px; 480 | } 481 | 482 | .jcalendar-months td, .jcalendar-years td { 483 | height:24px; 484 | } 485 | 486 | .jcalendar-input { 487 | padding-right:18px; 488 | background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='gray'%3E%3Cpath d='M20 3h-1V1h-2v2H7V1H5v2H4c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm0 18H4V8h16v13z'/%3E%3Cpath fill='none' d='M0 0h24v24H0z'/%3E%3C/svg%3E"); 489 | background-position:top 50% right 5px; 490 | background-repeat:no-repeat; 491 | box-sizing: border-box; 492 | } 493 | 494 | .jcalendar-done { 495 | -webkit-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 496 | -moz-box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 497 | box-shadow: 1px 1px 5px 0px rgba(0,0,0,0.39); 498 | background-color:#fff; 499 | } 500 | 501 | .jcalendar-update { 502 | border:1px solid #ccc; 503 | background-color:#fff; 504 | border-radius:4px; 505 | padding:5px; 506 | width:100%; 507 | } 508 | 509 | .jcalendar-container select { 510 | width:55px; 511 | display:inline-block; 512 | border:0px; 513 | padding:4px; 514 | text-align:center; 515 | font-size:1.1em; 516 | user-select:none; 517 | margin-right:10px; 518 | } 519 | 520 | .jcalendar-container select:first-child { 521 | margin-right:2px; 522 | } 523 | 524 | .jcalendar-selected { 525 | background-color:#eee; 526 | } 527 | 528 | .jcalendar-reset, .jcalendar-confirm { 529 | text-transform:uppercase; 530 | cursor:pointer; 531 | color: var(--active-color); 532 | } 533 | 534 | .jcalendar-controls { 535 | padding:15px; 536 | 537 | -webkit-box-sizing: border-box; 538 | box-sizing: border-box; 539 | vertical-align:middle; 540 | 541 | display: -webkit-box; 542 | display: -moz-box; 543 | display: -ms-flexbox; 544 | display: -webkit-flex; 545 | display: flex; 546 | 547 | -webkit-flex-flow: row wrap; 548 | justify-content: space-between; 549 | align-items:center; 550 | } 551 | 552 | .jcalendar-controls div { 553 | font-weight:bold; 554 | } 555 | 556 | .jcalendar-fullsize { 557 | position:fixed; 558 | width:100%; 559 | top:0px; 560 | left:0px; 561 | } 562 | 563 | .jcalendar-fullsize .jcalendar-content 564 | { 565 | position:fixed; 566 | width:100%; 567 | left:0px; 568 | bottom:0px; 569 | } 570 | 571 | .jcalendar-focus.jcalendar-fullsize .jcalendar-backdrop { 572 | display:block; 573 | } 574 | 575 | .jcalendar-sunday { 576 | color: red; 577 | } 578 | .jcalendar-disabled { 579 | color: #ccc; 580 | } 581 | 582 | .jcalendar-time { 583 | display:flex; 584 | } 585 | 586 | 587 | .jcolor { 588 | display: none; 589 | outline: none; 590 | position: absolute; 591 | } 592 | 593 | .jcolor-input { 594 | padding-right: 24px !important; 595 | background: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 3c-4.97 0-9 4.03-9 9s4.03 9 9 9c.83 0 1.5-.67 1.5-1.5 0-.39-.15-.74-.39-1.01-.23-.26-.38-.61-.38-.99 0-.83.67-1.5 1.5-1.5H16c2.76 0 5-2.24 5-5 0-4.42-4.03-8-9-8zm-5.5 9c-.83 0-1.5-.67-1.5-1.5S5.67 9 6.5 9 8 9.67 8 10.5 7.33 12 6.5 12zm3-4C8.67 8 8 7.33 8 6.5S8.67 5 9.5 5s1.5.67 1.5 1.5S10.33 8 9.5 8zm5 0c-.83 0-1.5-.67-1.5-1.5S13.67 5 14.5 5s1.5.67 1.5 1.5S15.33 8 14.5 8zm3 4c-.83 0-1.5-.67-1.5-1.5S16.67 9 17.5 9s1.5.67 1.5 1.5-.67 1.5-1.5 1.5z'/%3E%3C/svg%3E") top 50% right 4px no-repeat, content-box; 596 | box-sizing: border-box; 597 | } 598 | 599 | .jcolor-content { 600 | position: absolute; 601 | z-index: 9000; 602 | user-select: none; 603 | -webkit-font-smoothing: antialiased; 604 | font-size: .875rem; 605 | letter-spacing: .2px; 606 | -webkit-border-radius: 4px; 607 | border-radius: 4px; 608 | -webkit-box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 609 | box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2); 610 | background-color:#fff; 611 | box-sizing: border-box; 612 | min-width: 260px; 613 | } 614 | 615 | .jmodal .jcolor-content { 616 | position: fixed; 617 | } 618 | 619 | .jcolor-controls { 620 | display: flex; 621 | padding: 10px; 622 | border-bottom: 1px solid #eee; 623 | margin-bottom: 5px; 624 | } 625 | 626 | .jcolor-controls div { 627 | flex: 1; 628 | font-size: 1em; 629 | color: var(--active-color); 630 | text-transform: uppercase; 631 | font-weight: bold; 632 | box-sizing: border-box; 633 | } 634 | 635 | .jcolor-content table { 636 | border-collapse: collapse; 637 | box-sizing: border-box; 638 | } 639 | 640 | .jcolor-focus { 641 | display:block; 642 | } 643 | 644 | .jcolor table { 645 | width:100%; 646 | height:100%; 647 | min-height: 160px; 648 | } 649 | 650 | .jcolor td { 651 | padding: 7px; 652 | } 653 | 654 | .jcolor-selected { 655 | background-repeat:no-repeat; 656 | background-size: 16px; 657 | background-position: center; 658 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z' fill='white'/%3E%3C/svg%3E"); 659 | } 660 | 661 | .jcolor-fullscreen { 662 | position: fixed; 663 | left: 0px; 664 | bottom: 0px; 665 | width:100%; 666 | max-height: 290px; 667 | border-radius: 0px; 668 | box-sizing: border-box; 669 | } 670 | 671 | .jcolor-fullscreen .jcolor-controls { 672 | padding: 15px; 673 | -webkit-box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); 674 | -moz-box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); 675 | box-shadow: 1px 0px 1px 0px rgba(0,0,0,0.39); 676 | } 677 | 678 | .jcolor-reset { 679 | text-align: left; 680 | } 681 | 682 | .jcolor-close { 683 | text-align: right; 684 | } 685 | 686 | .jcolor-backdrop { 687 | position: fixed; 688 | top: 0px; 689 | left: 0px; 690 | min-width: 100%; 691 | min-height: 100%; 692 | background-color: rgba(0,0,0,0.5); 693 | border: 0px; 694 | padding: 0px; 695 | z-index: 8000; 696 | display: none; 697 | 698 | -webkit-touch-callout: none; /* iOS Safari */ 699 | -webkit-user-select: none; /* Safari */ 700 | -khtml-user-select: none; /* Konqueror HTML */ 701 | -moz-user-select: none; /* Firefox */ 702 | -ms-user-select: none; /* Internet Explorer/Edge */ 703 | user-select: none; /* Non-prefixed version, currently 704 | supported by Chrome and Opera */ 705 | } 706 | 707 | .jcolor-content .jtabs-content { 708 | padding: 7px; 709 | } 710 | 711 | .jcolor-grid tr:first-child > td:first-child { 712 | border-top-left-radius: 3px; 713 | } 714 | 715 | .jcolor-grid tr:first-child > td:last-child { 716 | border-top-right-radius: 3px; 717 | } 718 | 719 | .jcolor-grid tr:last-child > td:first-child { 720 | border-bottom-left-radius: 3px; 721 | } 722 | 723 | .jcolor-grid tr:last-child > td:last-child { 724 | border-bottom-right-radius: 3px; 725 | } 726 | 727 | .jcolor-hsl { 728 | box-sizing: border-box; 729 | } 730 | 731 | .jcolor-hsl > div { 732 | height: 100%; 733 | position: relative; 734 | } 735 | 736 | .jcolor-hsl canvas { 737 | display: block; 738 | border-radius: 4px; 739 | -webkit-user-drag: none; 740 | } 741 | 742 | .jcolor-point { 743 | height: 5px; 744 | width: 5px; 745 | background-color: #000; 746 | position: absolute; 747 | top: 50%; 748 | left: 50%; 749 | transform: translate(-50%, -50%); 750 | border-radius: 50%; 751 | } 752 | 753 | .jcolor-sliders { 754 | padding: 10px 20px 10px 10px; 755 | } 756 | 757 | .jcolor-sliders input { 758 | -webkit-appearance: none; 759 | 760 | height: 12px; 761 | width: 80%; 762 | 763 | background: #d3d3d3; 764 | opacity: 1; 765 | 766 | border-radius: 30px; 767 | outline: none; 768 | } 769 | 770 | .jcolor-sliders-input-subcontainer { 771 | display: flex; 772 | justify-content: space-between; 773 | align-items: center; 774 | } 775 | 776 | .jcolor-sliders-input-container { 777 | margin-top: 4px; 778 | line-height: 0.8em; 779 | text-align: left; 780 | } 781 | 782 | .jcolor-sliders-input-container > label { 783 | font-size: 10px; 784 | text-transform: uppercase; 785 | color: #bbbbbd; 786 | } 787 | 788 | .jcolor-sliders-input-subcontainer > input { 789 | border: 0px; 790 | padding: 1px; 791 | } 792 | 793 | .jcolor-sliders-input-container input::-webkit-slider-thumb { 794 | -webkit-appearance: none; 795 | height: 12px; 796 | width: 12px; 797 | border-radius: 50%; 798 | background: #000; 799 | border: 2px solid #fff; 800 | cursor: pointer; 801 | } 802 | 803 | .jcolor-sliders-input-container input::-moz-range-thumb { 804 | -webkit-appearance: none; 805 | height: 12px; 806 | width: 12px; 807 | border-radius: 50%; 808 | background: #000; 809 | border: 2px solid #fff; 810 | cursor: pointer; 811 | } 812 | 813 | .jcolor-sliders-final-color { 814 | padding: 6px; 815 | user-select: all; 816 | margin-top: 10px; 817 | text-align: center; 818 | } 819 | 820 | .jcolor-sliders-final-color > div:nth-child(2) { 821 | width: 71px; 822 | text-transform: uppercase; 823 | } 824 | 825 | .jcolor .jtabs .jtabs-headers-container .jtabs-controls { 826 | display: none !important; 827 | } 828 | 829 | .jcolor .jtabs .jtabs-headers-container { 830 | display: flex !important; 831 | justify-content: center; 832 | padding: 4px; 833 | } 834 | 835 | .jcolor .jtabs-headers > div:not(.jtabs-border) { 836 | padding: 2px !important; 837 | padding-left: 15px !important; 838 | padding-right: 15px !important; 839 | font-size: 0.8em; 840 | } 841 | 842 | .jcontextmenu { 843 | position:fixed; 844 | z-index:10000; 845 | background:#fff; 846 | color: #555; 847 | font-size: 11px; 848 | -webkit-user-select: none; 849 | -moz-user-select: none; 850 | user-select: none; 851 | -webkit-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); 852 | -moz-box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); 853 | box-shadow: 2px 2px 2px 0px rgba(143, 144, 145, 1); 854 | border: 1px solid #C6C6C6; 855 | padding: 0px; 856 | padding-top:4px; 857 | padding-bottom:4px; 858 | margin:0px; 859 | outline:none; 860 | display:none; 861 | } 862 | 863 | .jcontextmenu.jcontextmenu-focus { 864 | display:inline-block; 865 | } 866 | 867 | .jcontextmenu > div { 868 | box-sizing: border-box; 869 | display: flex; 870 | padding: 8px 8px 8px 32px; 871 | width: 250px; 872 | position: relative; 873 | cursor: default; 874 | font-size: 11px; 875 | font-family:sans-serif; 876 | text-align: left; 877 | -webkit-box-align: center; 878 | align-items: center; 879 | } 880 | 881 | .jcontextmenu > div::before { 882 | content: attr(data-icon); 883 | font-family: 'Material Icons' !important; 884 | font-size: 15px; 885 | position: absolute; 886 | left: 9px; 887 | } 888 | 889 | .jcontextmenu.symbols > div::before { 890 | font-family: 'Material Symbols Outlined' !important; 891 | } 892 | 893 | .jcontextmenu > div.header { 894 | display: none; 895 | } 896 | 897 | .jcontextmenu > div a { 898 | color: #555; 899 | text-decoration: none; 900 | flex: 1; 901 | } 902 | 903 | .jcontextmenu > div span { 904 | margin-right:10px; 905 | } 906 | 907 | .jcontextmenu .jcontextmenu-disabled a { 908 | color: #ccc; 909 | } 910 | 911 | .jcontextmenu > div:hover { 912 | background: #ebebeb; 913 | } 914 | 915 | .jcontextmenu hr { 916 | border: 1px solid #e9e9e9; 917 | border-bottom: 0; 918 | margin-top:5px; 919 | margin-bottom:5px; 920 | } 921 | 922 | .jcontextmenu > hr:hover { 923 | background: transparent; 924 | } 925 | 926 | .jcontextmenu .jcontextmenu { 927 | top: 4px; 928 | left: 99%; 929 | opacity: 0; 930 | position: absolute; 931 | } 932 | 933 | .jcontextmenu > div:hover > .jcontextmenu { 934 | display: block; 935 | opacity: 1; 936 | -webkit-transform: translate(0, 0) scale(1); 937 | transform: translate(0, 0) scale(1); 938 | pointer-events: auto; 939 | } 940 | 941 | @media only screen and (max-width: 420px) { 942 | .jcontextmenu { 943 | top: initial !important; 944 | left: 0px !important; 945 | bottom: 0px !important; 946 | width: 100vw; 947 | height: 260px; 948 | overflow: scroll; 949 | animation: slide-bottom-in 0.4s forwards; 950 | padding-top: 0px; 951 | } 952 | .jcontextmenu div { 953 | width: 100%; 954 | text-align: center; 955 | border-bottom: 1px solid #ccc; 956 | padding: 15px; 957 | } 958 | .jcontextmenu > div.header { 959 | background-color: lightgray; 960 | padding: 5px; 961 | top: 0px; 962 | position: sticky; 963 | z-index: 2; 964 | } 965 | .jcontextmenu > div.header > a.title { 966 | text-align: left; 967 | } 968 | 969 | .jcontextmenu > div.header > a.close { 970 | text-align: right; 971 | } 972 | .jcontextmenu a { 973 | font-size: 1.4em; 974 | text-transform: uppercase; 975 | } 976 | .jcontextmenu span { 977 | display: none; 978 | } 979 | .jcontextmenu span { 980 | display: none; 981 | } 982 | .jcontextmenu hr { 983 | display: none; 984 | } 985 | } 986 | 987 | 988 | .jdropdown { 989 | cursor:pointer; 990 | -webkit-touch-callout: none; 991 | -webkit-user-select: none; 992 | -khtml-user-select: none; 993 | -moz-user-select: none; 994 | -ms-user-select: none; 995 | user-select: none; 996 | box-sizing: border-box; 997 | background:#fff; 998 | -webkit-tap-highlight-color: transparent; 999 | display: inline-block; 1000 | } 1001 | 1002 | .jdropdown-backdrop { 1003 | position:fixed; 1004 | top:0px; 1005 | left:0px; 1006 | min-width:100%; 1007 | min-height:100%; 1008 | background-color:rgba(0,0,0,0.5); 1009 | border:0px; 1010 | padding:0px; 1011 | z-index:8000; 1012 | display:none; 1013 | } 1014 | 1015 | .jdropdown[disabled] { 1016 | opacity: 0.5; 1017 | pointer-events: none; 1018 | } 1019 | 1020 | .jdropdown-focus { 1021 | position:relative; 1022 | } 1023 | 1024 | .jdropdown-focus .jdropdown-container { 1025 | transform: translate3d(0,0,0); 1026 | } 1027 | 1028 | .jdropdown-default.jdropdown-focus .jdropdown-header { 1029 | outline:auto 5px -webkit-focus-ring-color; 1030 | } 1031 | 1032 | .jdropdown-default.jdropdown-focus .jdropdown-header.jdropdown-add { 1033 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='24px' height='24px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z'/%3E%3C/svg%3E"); 1034 | } 1035 | 1036 | .jdropdown-container-header { 1037 | padding:0px; 1038 | margin:0px; 1039 | position:relative; 1040 | box-sizing: border-box; 1041 | } 1042 | 1043 | .jdropdown-header { 1044 | width:100%; 1045 | appearance: none; 1046 | background-repeat: no-repeat; 1047 | background-position:top 50% right 5px; 1048 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); 1049 | text-overflow: ellipsis; 1050 | cursor:pointer; 1051 | box-sizing: border-box; 1052 | -webkit-appearance: none; 1053 | -moz-appearance: none; 1054 | padding-right:30px !important; 1055 | } 1056 | 1057 | .jdropdown-insert-button { 1058 | font-size: 1.4em; 1059 | text-transform: uppercase; 1060 | position:absolute; 1061 | right: 30px; 1062 | top: 4px; 1063 | display:none; 1064 | } 1065 | 1066 | .jdropdown-container { 1067 | min-width: inherit; 1068 | transform: translate3d(-10000px,0,0); 1069 | position:absolute; 1070 | z-index:9001; 1071 | } 1072 | 1073 | .jdropdown-close { 1074 | display:none; 1075 | font-size:1em; 1076 | color: var(--active-color); 1077 | text-transform:uppercase; 1078 | text-align:right; 1079 | padding:15px; 1080 | font-weight:bold; 1081 | } 1082 | 1083 | .jdropdown-content { 1084 | min-width:inherit; 1085 | margin:0px; 1086 | box-sizing:border-box; 1087 | } 1088 | 1089 | .jdropdown-content:empty { 1090 | } 1091 | 1092 | .jdropdown-item { 1093 | white-space: nowrap; 1094 | text-align: left; 1095 | text-overflow: ellipsis; 1096 | overflow-x: hidden; 1097 | color: #000; 1098 | display: flex; 1099 | align-items: center; 1100 | } 1101 | 1102 | .jdropdown-description { 1103 | text-overflow: ellipsis; 1104 | overflow: hidden; 1105 | line-height: 1.5em; 1106 | } 1107 | 1108 | .jdropdown-image { 1109 | margin-right:10px; 1110 | width: 32px; 1111 | height: 32px; 1112 | border-radius:20px; 1113 | } 1114 | 1115 | .jdropdown-image-small { 1116 | width:24px; 1117 | height:24px; 1118 | } 1119 | 1120 | .jdropdown-icon { 1121 | margin-right:10px; 1122 | font-size: 30px; 1123 | margin-left: -5px; 1124 | } 1125 | 1126 | .jdropdown-icon-small { 1127 | font-size: 24px; 1128 | margin-left: 0px; 1129 | } 1130 | 1131 | .jdropdown-title { 1132 | font-size: 0.7em; 1133 | text-overflow: ellipsis; 1134 | overflow-x: hidden; 1135 | display: block; 1136 | } 1137 | 1138 | /** Default visual **/ 1139 | 1140 | .jdropdown-default .jdropdown-header { 1141 | border:1px solid #ccc; 1142 | padding:5px; 1143 | padding-left:10px; 1144 | padding-right:16px; 1145 | } 1146 | 1147 | .jdropdown-default .jdropdown-container { 1148 | background-color:#fff; 1149 | } 1150 | 1151 | .jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-header { 1152 | padding-right:50px; 1153 | } 1154 | 1155 | .jdropdown-default.jdropdown-focus.jdropdown-insert .jdropdown-insert-button { 1156 | display:block; 1157 | } 1158 | 1159 | .jdropdown-default .jdropdown-content 1160 | { 1161 | min-width:inherit; 1162 | border:1px solid #8fb1e3; 1163 | margin:0px; 1164 | background-color:#fff; 1165 | box-sizing:border-box; 1166 | min-height:10px; 1167 | max-height:215px; 1168 | overflow-y:auto; 1169 | } 1170 | 1171 | .jdropdown-default .jdropdown-item 1172 | { 1173 | padding:4px; 1174 | padding-left:8px; 1175 | padding-right:40px; 1176 | } 1177 | 1178 | .jdropdown-default .jdropdown-item:hover 1179 | { 1180 | background-color:#1f93ff; 1181 | color:#fff; 1182 | } 1183 | 1184 | .jdropdown-default .jdropdown-cursor 1185 | { 1186 | background-color:#eee; 1187 | } 1188 | 1189 | .jdropdown-default .jdropdown-selected 1190 | { 1191 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo='); 1192 | background-repeat:no-repeat; 1193 | background-position:top 50% right 5px; 1194 | background-color:#1f93ff; 1195 | color:#fff; 1196 | } 1197 | 1198 | .jdropdown-default .jdropdown-group { 1199 | margin-top:5px; 1200 | } 1201 | 1202 | .jdropdown-default .jdropdown-group .jdropdown-item { 1203 | padding-left:16px; 1204 | } 1205 | 1206 | .jdropdown-default .jdropdown-group-name { 1207 | padding-left: 8px; 1208 | font-weight: bold; 1209 | text-align: left; 1210 | } 1211 | 1212 | .jdropdown-default .jdropdown-reset_ { 1213 | content:'x'; 1214 | position:absolute; 1215 | top:0; 1216 | right:0; 1217 | margin:5px; 1218 | margin-right:10px; 1219 | font-size:12px; 1220 | width:12px; 1221 | cursor:pointer; 1222 | text-shadow: 0px 0px 5px #fff; 1223 | display:none; 1224 | line-height: 1.8em; 1225 | } 1226 | 1227 | .jdropdown-default.jdropdown-focus .jdropdown-reset_ { 1228 | display:block; 1229 | } 1230 | 1231 | /** Default render for mobile **/ 1232 | 1233 | .jdropdown-picker.jdropdown-focus .jdropdown-backdrop { 1234 | display:block; 1235 | } 1236 | 1237 | .jdropdown-picker .jdropdown-header { 1238 | outline: none; 1239 | } 1240 | 1241 | .jdropdown-picker .jdropdown-container 1242 | { 1243 | position:fixed; 1244 | bottom:0px; 1245 | left:0px; 1246 | border-bottom:1px solid #e6e6e8; 1247 | width:100%; 1248 | background-color:#fff; 1249 | box-sizing: border-box; 1250 | } 1251 | 1252 | .jdropdown-picker .jdropdown-close 1253 | { 1254 | -webkit-box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); 1255 | -moz-box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); 1256 | box-shadow: 0px -1px 5px 0px rgba(0,0,0,0.39); 1257 | background-color:#fff; 1258 | display:block; 1259 | } 1260 | 1261 | .jdropdown-picker .jdropdown-content 1262 | { 1263 | overflow-y:scroll; 1264 | height:280px; 1265 | background-color:#fafafa; 1266 | border-top:1px solid #e6e6e8; 1267 | } 1268 | 1269 | .jdropdown-picker .jdropdown-group-name 1270 | { 1271 | font-size: 1em; 1272 | text-transform: uppercase; 1273 | padding-top:10px; 1274 | padding-bottom:10px; 1275 | display: block; 1276 | border-bottom: 1px solid #e6e6e8; 1277 | padding-left:20px; 1278 | padding-right:20px; 1279 | text-align:center; 1280 | font-weight:bold; 1281 | } 1282 | 1283 | .jdropdown-picker .jdropdown-item 1284 | { 1285 | font-size: 1em; 1286 | text-transform: uppercase; 1287 | padding-top:10px; 1288 | padding-bottom:10px; 1289 | border-bottom: 1px solid #e6e6e8; 1290 | padding-left:20px; 1291 | padding-right:20px; 1292 | } 1293 | 1294 | .jdropdown-picker .jdropdown-selected 1295 | { 1296 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIiAvPjxwYXRoIGQ9Ik05IDE2LjE3TDQuODMgMTJsLTEuNDIgMS40MUw5IDE5IDIxIDdsLTEuNDEtMS40MXoiIGZpbGw9IndoaXRlIiAvPjwvc3ZnPgo='); 1297 | background-repeat:no-repeat; 1298 | background-position:top 50% right 15px; 1299 | background-color:#1f93ff; 1300 | color:#fff; 1301 | } 1302 | 1303 | .jdropdown-picker .jdropdown-cursor 1304 | { 1305 | background-color:#1f93ff; 1306 | color:#fff; 1307 | } 1308 | 1309 | /** Default render for mobile searchbar **/ 1310 | 1311 | .jdropdown-searchbar.jdropdown-focus 1312 | { 1313 | position:fixed; 1314 | top:0px !important; 1315 | left:0px !important; 1316 | width:100% !important; 1317 | height:100% !important; 1318 | background-color:#fafafa; 1319 | padding:0px; 1320 | z-index:9001; 1321 | overflow-y:scroll; 1322 | will-change: scroll-position; 1323 | -webkit-overflow-scrolling: touch; 1324 | } 1325 | 1326 | .jdropdown-searchbar.jdropdown-focus .jdropdown-container-header 1327 | { 1328 | position: fixed; 1329 | top: 0px; 1330 | left: 0px; 1331 | z-index: 9002; 1332 | padding:10px; 1333 | background-color:#fff; 1334 | box-shadow: 0 1px 2px rgba(0,0,0,.1); 1335 | width: 100%; 1336 | } 1337 | 1338 | .jdropdown-searchbar.jdropdown-focus .jdropdown-header 1339 | { 1340 | border: 0px; 1341 | background-repeat: no-repeat; 1342 | background-position-x: 0%; 1343 | background-position-y: 40%; 1344 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMTUuNSAxNGgtLjc5bC0uMjgtLjI3QzE1LjQxIDEyLjU5IDE2IDExLjExIDE2IDkuNSAxNiA1LjkxIDEzLjA5IDMgOS41IDNTMyA1LjkxIDMgOS41IDUuOTEgMTYgOS41IDE2YzEuNjEgMCAzLjA5LS41OSA0LjIzLTEuNTdsLjI3LjI4di43OWw1IDQuOTlMMjAuNDkgMTlsLTQuOTktNXptLTYgMEM3LjAxIDE0IDUgMTEuOTkgNSA5LjVTNy4wMSA1IDkuNSA1IDE0IDcuMDEgMTQgOS41IDExLjk5IDE0IDkuNSAxNHoiIGZpbGw9IiNlNmU2ZTgiLz48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PC9zdmc+); 1345 | padding-left: 30px !important; 1346 | padding-right: 60px !important; 1347 | } 1348 | 1349 | .jdropdown-searchbar.jdropdown-focus .jdropdown-close 1350 | { 1351 | display:block; 1352 | } 1353 | 1354 | .jdropdown-searchbar .jdropdown-header { 1355 | outline: none; 1356 | } 1357 | 1358 | .jdropdown-searchbar .jdropdown-container 1359 | { 1360 | margin-top: 40px; 1361 | width:100%; 1362 | } 1363 | 1364 | .jdropdown-searchbar .jdropdown-close 1365 | { 1366 | position:fixed; 1367 | top:0px; 1368 | right:0px; 1369 | } 1370 | 1371 | .jdropdown-searchbar .jdropdown-content 1372 | { 1373 | margin-top:10px; 1374 | } 1375 | 1376 | .jdropdown-searchbar .jdropdown-group 1377 | { 1378 | margin-top:10px; 1379 | margin-bottom:15px; 1380 | background-color:#fff; 1381 | } 1382 | 1383 | .jdropdown-searchbar .jdropdown-group-name 1384 | { 1385 | border-top: 1px solid #e6e6e8; 1386 | border-bottom: 1px solid #e6e6e8; 1387 | padding:10px; 1388 | padding-left:12px; 1389 | font-weight:bold; 1390 | } 1391 | 1392 | .jdropdown-searchbar .jdropdown-group-arrow 1393 | { 1394 | float:right; 1395 | width:24px; 1396 | height:24px; 1397 | background-repeat:no-repeat; 1398 | } 1399 | 1400 | .jdropdown-searchbar .jdropdown-group-arrow-down 1401 | { 1402 | background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNNy40MSA4LjU5TDEyIDEzLjE3bDQuNTktNC41OEwxOCAxMGwtNiA2LTYtNiAxLjQxLTEuNDF6Ii8+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgyNHYyNEgwVjB6Ii8+PC9zdmc+); 1403 | } 1404 | 1405 | .jdropdown-searchbar .jdropdown-group-arrow-up 1406 | { 1407 | background-image: url(data:image/svg+xml;base64,CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZD0iTTcuNDEgMTUuNDFMMTIgMTAuODNsNC41OSA0LjU4TDE4IDE0bC02LTYtNiA2eiIvPjxwYXRoIGQ9Ik0wIDBoMjR2MjRIMHoiIGZpbGw9Im5vbmUiLz48L3N2Zz4=); 1408 | } 1409 | 1410 | .jdropdown-searchbar .jdropdown-item 1411 | { 1412 | padding-top:10px; 1413 | padding-bottom:10px; 1414 | border-bottom: 1px solid #e6e6e8; 1415 | padding-left:15px; 1416 | padding-right:40px; 1417 | background-color:#fff; 1418 | font-size:0.9em; 1419 | } 1420 | 1421 | .jdropdown-searchbar .jdropdown-description { 1422 | text-overflow: ellipsis; 1423 | overflow: hidden; 1424 | max-width: calc(100% - 20px); 1425 | } 1426 | 1427 | .jdropdown-searchbar .jdropdown-content > .jdropdown-item:first-child 1428 | { 1429 | border-top: 1px solid #e6e6e8; 1430 | } 1431 | 1432 | .jdropdown-searchbar .jdropdown-selected 1433 | { 1434 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg=='); 1435 | background-repeat:no-repeat; 1436 | background-position:top 50% right 15px; 1437 | } 1438 | 1439 | /** List render **/ 1440 | 1441 | .jdropdown-list 1442 | { 1443 | } 1444 | 1445 | .jdropdown-list .jdropdown-container 1446 | { 1447 | display:block; 1448 | } 1449 | 1450 | .jdropdown-list .jdropdown-header 1451 | { 1452 | display:none; 1453 | } 1454 | 1455 | .jdropdown-list .jdropdown-group 1456 | { 1457 | background-color:#fff; 1458 | } 1459 | 1460 | .jdropdown-list .jdropdown-group-name 1461 | { 1462 | border-bottom: 1px solid #e6e6e8; 1463 | padding-top:10px; 1464 | padding-bottom:10px; 1465 | font-weight:bold; 1466 | } 1467 | 1468 | .jdropdown-list .jdropdown-item 1469 | { 1470 | padding-top:10px; 1471 | padding-bottom:10px; 1472 | border-bottom: 1px solid #e6e6e8; 1473 | padding-left:10px; 1474 | padding-right:40px; 1475 | background-color:#fff; 1476 | } 1477 | 1478 | .jdropdown-list .jdropdown-selected 1479 | { 1480 | background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0Ij48cGF0aCBkPSJNMCAwaDI0djI0SDB6IiBmaWxsPSJub25lIi8+PHBhdGggZD0iTTkgMTYuMTdMNC44MyAxMmwtMS40MiAxLjQxTDkgMTkgMjEgN2wtMS40MS0xLjQxeiIgZmlsbD0iIzAwN2FmZiIvPjwvc3ZnPg=='); 1481 | background-repeat:no-repeat; 1482 | background-position:top 50% right 10px; 1483 | } 1484 | 1485 | @media only screen and (max-width : 800px) 1486 | { 1487 | .jdropdown-list { 1488 | width:100% !important; 1489 | border:0px; 1490 | padding:0px; 1491 | } 1492 | 1493 | .jdropdown-list .jdropdown-container { 1494 | min-width:100%; 1495 | } 1496 | 1497 | .jdropdown-searchbar.jdropdown-focus .jdropdown-description { 1498 | text-transform: uppercase; 1499 | } 1500 | } 1501 | 1502 | .app .jdropdown-item { 1503 | text-transform:uppercase; 1504 | } 1505 | 1506 | .jdropdown-create-container { 1507 | margin: 10px; 1508 | border: 1px solid #ccc; 1509 | border-radius: 2px; 1510 | padding: 6px; 1511 | } 1512 | 1513 | .jdropdown-color { 1514 | background-color: #fff; 1515 | border: 1px solid transparent; 1516 | border-radius: 12px; 1517 | width: 12px; 1518 | height: 12px; 1519 | margin-right: 6px; 1520 | } 1521 | 1522 | .jdropdown-item[data-disabled] { 1523 | opacity: 0.5; 1524 | pointer-events: none; 1525 | } 1526 | 1527 | .jeditor-container { 1528 | border:1px solid rgb(177, 9, 9); 1529 | box-sizing: border-box; 1530 | } 1531 | 1532 | .jeditor-dragging { 1533 | border:1px dashed #000; 1534 | } 1535 | 1536 | .jeditor-container.jeditor-padding { 1537 | padding:10px; 1538 | } 1539 | 1540 | .jeditor { 1541 | outline:none; 1542 | word-break: break-word; 1543 | 1544 | } 1545 | 1546 | .jeditor[data-placeholder]:empty:before { 1547 | content: attr(data-placeholder); 1548 | color: lightgray; 1549 | } 1550 | 1551 | .jeditor-container.jeditor-padding .jeditor { 1552 | min-height:100px; 1553 | margin-bottom:10px; 1554 | padding:10px; 1555 | } 1556 | 1557 | /** Snippet **/ 1558 | 1559 | .jsnippet { 1560 | margin-top:15px; 1561 | cursor:pointer; 1562 | border: 1px solid #ccc; 1563 | position:relative; 1564 | } 1565 | 1566 | .jsnippet:focus { 1567 | outline: none; 1568 | } 1569 | 1570 | .jsnippet img { 1571 | width:100%; 1572 | } 1573 | 1574 | .jsnippet .jsnippet-title { 1575 | padding:15px; 1576 | font-size:1.4em; 1577 | } 1578 | 1579 | .jsnippet .jsnippet-description { 1580 | padding-left:15px; 1581 | padding-right:15px; 1582 | font-size:1em; 1583 | } 1584 | 1585 | .jsnippet .jsnippet-host { 1586 | padding:15px; 1587 | text-transform:uppercase; 1588 | font-size:0.8em; 1589 | color:#777; 1590 | text-align:right; 1591 | } 1592 | 1593 | .jsnippet .jsnippet-url { 1594 | display:none; 1595 | } 1596 | 1597 | .jeditor .jsnippet:after { 1598 | content: 'close'; 1599 | font-family: 'Material icons'; 1600 | font-size: 24px; 1601 | width: 24px; 1602 | height: 24px; 1603 | line-height: 24px; 1604 | cursor: pointer; 1605 | text-shadow: 0px 0px 2px #fff; 1606 | position: absolute; 1607 | top: 12px; 1608 | right: 12px; 1609 | } 1610 | 1611 | .jsnippet * { 1612 | -webkit-user-select: none; 1613 | -khtml-user-select: none; 1614 | -moz-user-select: none; 1615 | -o-user-select: none; 1616 | user-select: none; 1617 | 1618 | -webkit-user-drag: none; 1619 | -khtml-user-drag: none; 1620 | -moz-user-drag: none; 1621 | -o-user-drag: none; 1622 | } 1623 | 1624 | .jeditor img { 1625 | border:2px solid transparent; 1626 | box-sizing: border-box; 1627 | } 1628 | 1629 | .jeditor img.resizing { 1630 | -webkit-user-select: none; 1631 | -khtml-user-select: none; 1632 | -moz-user-select: none; 1633 | -o-user-select: none; 1634 | user-select: none; 1635 | 1636 | -webkit-user-drag: none; 1637 | -khtml-user-drag: none; 1638 | -moz-user-drag: none; 1639 | -o-user-drag: none; 1640 | } 1641 | 1642 | .jeditor img:focus { 1643 | border:2px solid #0096FD; 1644 | outline: #0096FD; 1645 | } 1646 | 1647 | .jeditor .pdf { 1648 | background-image: url("data:image/svg+xml,%3Csvg version='1.1' id='Layer_1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' x='0px' y='0px' viewBox='0 0 512 512' style='enable-background:new 0 0 512 512;' xml:space='preserve'%3E%3Cpath style='fill:%23C30B15;' d='M511.344,274.266C511.77,268.231,512,262.143,512,256C512,114.615,397.385,0,256,0S0,114.615,0,256 c0,117.769,79.53,216.949,187.809,246.801L511.344,274.266z'/%3E%3Cpath style='fill:%2385080E;' d='M511.344,274.266L314.991,77.913L119.096,434.087l68.714,68.714C209.522,508.787,232.385,512,256,512 C391.243,512,501.976,407.125,511.344,274.266z'/%3E%3Cpolygon style='fill:%23FFFFFF;' points='278.328,333.913 255.711,77.913 119.096,77.913 119.096,311.652 '/%3E%3Cpolygon style='fill:%23E8E6E6;' points='392.904,311.652 392.904,155.826 337.252,133.565 314.991,77.913 255.711,77.913 256.067,333.913 '/%3E%3Cpolygon style='fill:%23FFFFFF;' points='314.991,155.826 314.991,77.913 392.904,155.826 '/%3E%3Crect x='119.096' y='311.652' style='fill:%23FC0F1A;' width='273.809' height='122.435'/%3E%3Cg%3E%3Cpath style='fill:%23FFFFFF;' d='M204.871,346.387c13.547,0,21.341,6.659,21.341,18.465c0,12.412-7.795,19.601-21.341,19.601h-9.611 v14.909h-13.471v-52.975L204.871,346.387L204.871,346.387z M195.26,373.858h8.93c5.904,0,9.308-2.952,9.308-8.552 c0-5.525-3.406-8.324-9.308-8.324h-8.93V373.858z'/%3E%3Cpath style='fill:%23FFFFFF;' d='M257.928,346.387c16.649,0,28.152,10.746,28.152,26.487c0,15.666-11.655,26.488-28.683,26.488 h-22.25v-52.975H257.928z M248.619,388.615h9.611c8.249,0,14.151-6.357,14.151-15.665c0-9.384-6.205-15.817-14.757-15.817h-9.006 V388.615z'/%3E%3Cpath style='fill:%23FFFFFF;' d='M308.563,356.982v12.26h23.763v10.596h-23.763v19.525h-13.471v-52.975h39.277v10.595h-25.806 V356.982z'/%3E%3C/g%3E%3C/svg%3E%0A"); 1649 | background-repeat: no-repeat; 1650 | background-size: cover; 1651 | width:60px; 1652 | height:60px; 1653 | } 1654 | 1655 | .jeditor-toolbar { 1656 | width: fit-content; 1657 | max-width: 100%; 1658 | box-sizing: border-box; 1659 | } 1660 | 1661 | .jloading { 1662 | position:fixed; 1663 | z-index:10001; 1664 | width:100%; 1665 | left:0; 1666 | right:0; 1667 | top:0; 1668 | bottom:0; 1669 | background-color: rgba(0,0,0,0.7); 1670 | } 1671 | 1672 | .jloading::after { 1673 | content:''; 1674 | display:block; 1675 | margin:0 auto; 1676 | margin-top:50vh; 1677 | width:40px; 1678 | height:40px; 1679 | border-style:solid; 1680 | border-color:white; 1681 | border-top-color:transparent; 1682 | border-width:4px; 1683 | border-radius:50%; 1684 | -webkit-animation: spin .8s linear infinite; 1685 | animation: spin .8s linear infinite; 1686 | } 1687 | 1688 | .jloading.spin { 1689 | background-color:transparent; 1690 | } 1691 | 1692 | .jloading.spin::after { 1693 | margin:0 auto; 1694 | margin-top:80px; 1695 | border-color:#aaa; 1696 | border-top-color:transparent; 1697 | } 1698 | 1699 | 1700 | .jmodal { 1701 | position:fixed; 1702 | top:50%; 1703 | left:50%; 1704 | width:60%; 1705 | height:60%; 1706 | -webkit-box-shadow: 0 2px 12px rgba(0,0,0,.2); 1707 | -moz-box-shadow: 0 2px 12px rgba(0,0,0,.2); 1708 | border:1px solid #ccc; 1709 | background-color:#fff; 1710 | transform: translate(-50%, -50%); 1711 | box-sizing: border-box; 1712 | z-index:9002; 1713 | border-radius: 4px; 1714 | } 1715 | 1716 | .jmodal_title { 1717 | padding: 20px; 1718 | height: 70px; 1719 | box-sizing: border-box; 1720 | font-size: 1.4em; 1721 | background-color: #fff; 1722 | border-radius: 8px 8px 0px 0px; 1723 | pointer-events: none; 1724 | display: flex; 1725 | -webkit-align-items: center; 1726 | -webkit-box-align: center; 1727 | align-items: center; 1728 | border-bottom: 1px solid #eee; 1729 | } 1730 | 1731 | .jmodal_title[data-icon]:before { 1732 | content: attr(data-icon); 1733 | font-family: 'Material Icons' !important; 1734 | width: 24px; 1735 | height: 24px; 1736 | font-size: 24px; 1737 | margin-right: 10px; 1738 | line-height: 24px; 1739 | } 1740 | 1741 | .jmodal_content { 1742 | padding: 20px; 1743 | overflow-y: auto; 1744 | max-height: 100%; 1745 | box-sizing: border-box; 1746 | height: calc(100% - 65px); 1747 | scrollbar-width: thin; 1748 | scrollbar-color: #333 transparent; 1749 | } 1750 | 1751 | .jmodal_title:empty { 1752 | display: none; 1753 | } 1754 | 1755 | .jmodal_title:empty + .jmodal_content { 1756 | height: -webkit-fill-available; 1757 | } 1758 | 1759 | .jmodal_content::-webkit-scrollbar { 1760 | height: 12px; 1761 | } 1762 | 1763 | .jmodal_content::-webkit-scrollbar { 1764 | width: 12px; 1765 | } 1766 | 1767 | .jmodal_content::-webkit-scrollbar-track { 1768 | border: 1px solid #fff; 1769 | background: #eee; 1770 | } 1771 | 1772 | .jmodal_content::-webkit-scrollbar-thumb { 1773 | border: 1px solid #fff; 1774 | background: #888; 1775 | } 1776 | 1777 | .jmodal:after { 1778 | content: ''; 1779 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 1780 | position: absolute; 1781 | top: 0; 1782 | right: 0; 1783 | margin: 25px; 1784 | font-size: 24px; 1785 | width: 24px; 1786 | height: 24px; 1787 | cursor: pointer; 1788 | text-shadow: 0px 0px 5px #fff; 1789 | } 1790 | 1791 | .jmodal_fullscreen { 1792 | width: 100% !important; 1793 | height: 100% !important; 1794 | top: 0px; 1795 | left: 0px; 1796 | transform: none; 1797 | border: 0px; 1798 | border-radius: 0px; 1799 | } 1800 | 1801 | .jmodal_backdrop { 1802 | position: fixed; 1803 | top: 0px; 1804 | left: 0px; 1805 | min-width: 100%; 1806 | min-height: 100%; 1807 | background-color: rgba(0,0,0,0.5); 1808 | border: 0px; 1809 | padding: 0px; 1810 | z-index: 8000; 1811 | display: none; 1812 | 1813 | -webkit-touch-callout: none; /* iOS Safari */ 1814 | -webkit-user-select: none; /* Safari */ 1815 | -khtml-user-select: none; /* Konqueror HTML */ 1816 | -moz-user-select: none; /* Firefox */ 1817 | -ms-user-select: none; /* Internet Explorer/Edge */ 1818 | user-select: none; /* Non-prefixed version, currently 1819 | supported by Chrome and Opera */ 1820 | } 1821 | 1822 | 1823 | .jnotification { 1824 | position: fixed; 1825 | z-index: 10000; 1826 | -webkit-box-sizing: border-box; 1827 | box-sizing: border-box; 1828 | padding: 10px; 1829 | bottom: 0px; 1830 | } 1831 | 1832 | .jnotification-container { 1833 | -webkit-box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); 1834 | box-shadow: 0px 2px 15px -5px rgba(0, 0, 0, 0.7); 1835 | padding: 12px; 1836 | border-radius: 8px; 1837 | 1838 | background-color: #000; 1839 | background: rgba(92,92,92,1); 1840 | background: linear-gradient(0deg, rgba(92,92,92,1) 0%, rgba(77,77,77,1) 100%); 1841 | color: #fff; 1842 | width: 320px; 1843 | margin: 30px; 1844 | padding: 20px; 1845 | } 1846 | 1847 | .jnotification-close { 1848 | content: ''; 1849 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 1850 | font-size: 20px; 1851 | width: 20px; 1852 | height: 20px; 1853 | cursor: pointer; 1854 | } 1855 | 1856 | .jnotification-title { 1857 | font-weight: bold; 1858 | } 1859 | 1860 | .jnotification-header { 1861 | display: flex; 1862 | padding-bottom: 5px; 1863 | } 1864 | 1865 | .jnotification-header:empty { 1866 | display: none; 1867 | } 1868 | 1869 | .jnotification-image { 1870 | margin-right: 5px; 1871 | } 1872 | 1873 | .jnotification-image:empty { 1874 | display: none; 1875 | } 1876 | 1877 | .jnotification-image img { 1878 | width: 24px; 1879 | } 1880 | 1881 | .jnotification-name { 1882 | text-transform: uppercase; 1883 | font-size: 0.9em; 1884 | flex: 1; 1885 | letter-spacing: 0.1em; 1886 | } 1887 | 1888 | .jnotification-error .jnotification-container { 1889 | background: rgb(182,38,6); 1890 | background: linear-gradient(0deg, rgba(170,41,13,1) 0%, rgba(149,11,11,1) 100%); 1891 | } 1892 | 1893 | @media (max-width: 800px) { 1894 | .jnotification { 1895 | top: calc(0px + var(--safe-area-top)); 1896 | width: 100%; 1897 | } 1898 | .jnotification-container { 1899 | background: rgba(255,255,255,0.95); 1900 | border: 1px solid #eee; 1901 | color: #444; 1902 | margin: 0px; 1903 | width: initial; 1904 | } 1905 | .jnotification-error .jnotification-container { 1906 | background: rgba(255,255,255,0.95); 1907 | color: #790909; 1908 | } 1909 | .jnotification-close { 1910 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 1911 | } 1912 | } 1913 | 1914 | .jnotification-header { 1915 | display: -webkit-box; 1916 | display: -webkit-flex; 1917 | display: -ms-flexbox; 1918 | display: flex; 1919 | -webkit-box-pack: start; 1920 | -webkit-justify-content: flex-start; 1921 | -ms-flex-pack: start; 1922 | justify-content: flex-start; 1923 | -webkit-box-align: center; 1924 | -webkit-align-items: center; 1925 | -ms-flex-align: center; 1926 | align-items: center; 1927 | } 1928 | 1929 | .jpicker { 1930 | cursor: pointer; 1931 | white-space: nowrap; 1932 | display: inline-flex; 1933 | -webkit-user-select: none; 1934 | -moz-user-select: none; 1935 | -ms-user-select: none; 1936 | user-select: none; 1937 | outline: none; 1938 | position: relative; 1939 | } 1940 | 1941 | .jpicker-header { 1942 | background-repeat: no-repeat; 1943 | background-position: top 50% right 5px; 1944 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='none' d='M0 0h24v24H0V0z'/%3E%3Cpath d='M7 10l5 5 5-5H7z' fill='gray'/%3E%3C/svg%3E"); 1945 | text-overflow: ellipsis; 1946 | cursor: pointer; 1947 | box-sizing: border-box; 1948 | text-align: left; 1949 | outline: none; 1950 | 1951 | line-height: 24px; 1952 | padding: 2px; 1953 | padding-left: 12px; 1954 | padding-right: 35px; 1955 | outline: none; 1956 | border-radius: 4px; 1957 | } 1958 | 1959 | .jpicker-header:hover { 1960 | background-color: #eee; 1961 | } 1962 | 1963 | .jpicker-content { 1964 | position: absolute; 1965 | top: 0; 1966 | display: none; 1967 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); 1968 | border-radius: 4px; 1969 | background-color: #fff; 1970 | padding: 4px; 1971 | z-index: 50; 1972 | text-align: left; 1973 | max-height: 200px; 1974 | scrollbar-width: thin; 1975 | scrollbar-color: #333 transparent; 1976 | } 1977 | 1978 | .jpicker-content::-webkit-scrollbar { 1979 | width: 8px; 1980 | } 1981 | 1982 | .jpicker-content::-webkit-scrollbar-track { 1983 | background: #eee; 1984 | } 1985 | 1986 | .jpicker-content::-webkit-scrollbar-thumb { 1987 | background: #888; 1988 | } 1989 | 1990 | .jpicker-content > div { 1991 | padding: 6px; 1992 | padding-left: 15px; 1993 | padding-right: 15px; 1994 | } 1995 | 1996 | .jpicker-focus > .jpicker-content { 1997 | display: block; 1998 | } 1999 | 2000 | .jpicker-content > div:hover { 2001 | background-color:#efefef; 2002 | } 2003 | 2004 | .jpicker-content > div:empty { 2005 | opacity: 0; 2006 | } 2007 | 2008 | .jpicker-header > i, .jpicker-header > div { 2009 | display: block; 2010 | } 2011 | 2012 | .jpicker-focus > .jpicker-content.jpicker-columns { 2013 | display: flex !important ; 2014 | justify-content: center; 2015 | flex-wrap: wrap; 2016 | } 2017 | 2018 | 2019 | 2020 | .jprogressbar 2021 | { 2022 | cursor:pointer; 2023 | -webkit-touch-callout: none; 2024 | -webkit-user-select: none; 2025 | -khtml-user-select: none; 2026 | -moz-user-select: none; 2027 | -ms-user-select: none; 2028 | user-select: none; 2029 | box-sizing: border-box; 2030 | background:#fff; 2031 | -webkit-tap-highlight-color: transparent; 2032 | display: inline-block; 2033 | box-sizing: border-box; 2034 | cursor:pointer; 2035 | border:1px solid #ccc; 2036 | position:relative; 2037 | } 2038 | 2039 | .jprogressbar::before { 2040 | content:attr(data-value); 2041 | position:absolute; 2042 | margin:5px; 2043 | margin-left:10px; 2044 | } 2045 | 2046 | .jprogressbar-header::placeholder 2047 | { 2048 | color:#000; 2049 | } 2050 | 2051 | .jprogressbar::focus { 2052 | outline: auto 5px -webkit-focus-ring-color; 2053 | } 2054 | 2055 | .jprogressbar > div { 2056 | background-color: #eee; 2057 | background-color: red; 2058 | box-sizing: border-box; 2059 | height:31px; 2060 | } 2061 | 2062 | .jrating { 2063 | display:flex; 2064 | } 2065 | .jrating > div { 2066 | width:24px; 2067 | height:24px; 2068 | line-height:24px; 2069 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath d='M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z' fill='gray'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 2070 | } 2071 | 2072 | .jrating .jrating-over { 2073 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='black'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 2074 | opacity: 0.7; 2075 | } 2076 | 2077 | .jrating .jrating-selected { 2078 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='red'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 2079 | } 2080 | 2081 | 2082 | .jsearch { 2083 | position: relative; 2084 | display: none; 2085 | -webkit-user-select: none; 2086 | -moz-user-select: none; 2087 | -ms-user-select: none; 2088 | user-select: none; 2089 | } 2090 | 2091 | .jsearch_container { 2092 | position: absolute; 2093 | box-shadow: 0 1px 2px 0 rgba(60,64,67,0.302), 0 2px 6px 2px rgba(60,64,67,0.149); 2094 | border: none; 2095 | -webkit-border-radius: 4px; 2096 | border-radius: 4px; 2097 | width: 280px; 2098 | padding: 8px 0; 2099 | 2100 | -webkit-box-shadow: 0 2px 4px rgba(0,0,0,0.2); 2101 | box-shadow: 0 2px 4px rgba(0,0,0,0.2); 2102 | -webkit-transition: opacity .218s; 2103 | transition: opacity .218s; 2104 | background: #fff; 2105 | border: 1px solid rgba(0,0,0,.2); 2106 | cursor: pointer; 2107 | margin: 0; 2108 | min-width: 300px; 2109 | outline: none; 2110 | width: auto; 2111 | -webkit-user-select: none; 2112 | -moz-user-select: none; 2113 | -ms-user-select: none; 2114 | user-select: none; 2115 | } 2116 | 2117 | .jsearch_container:empty:after { 2118 | content: attr(data-placeholder); 2119 | } 2120 | 2121 | .jsearch_container > div { 2122 | color: #333; 2123 | cursor: pointer; 2124 | display: -webkit-box; 2125 | display: -webkit-flex; 2126 | display: flex; 2127 | padding: 5px 10px; 2128 | user-select: none; 2129 | -webkit-align-items: center; 2130 | align-items: center; 2131 | 2132 | -webkit-user-select: none; 2133 | -moz-user-select: none; 2134 | -ms-user-select: none; 2135 | user-select: none; 2136 | } 2137 | 2138 | .jsearch_container > div:hover { 2139 | background-color: #e8eaed; 2140 | } 2141 | 2142 | .jsearch_container > div > img { 2143 | width: 32px; 2144 | height: 32px; 2145 | user-select: none; 2146 | border-radius: 16px; 2147 | margin-right: 2px; 2148 | } 2149 | 2150 | .jsearch_container > div > div { 2151 | overflow: hidden; 2152 | text-overflow: ellipsis; 2153 | margin-left: 2px; 2154 | max-width: 300px; 2155 | white-space: nowrap; 2156 | user-select: none; 2157 | } 2158 | 2159 | .jsearch_container .selected { 2160 | background-color: #e8eaed; 2161 | } 2162 | 2163 | .jslider { 2164 | outline: none; 2165 | } 2166 | 2167 | .jslider-focus { 2168 | width: 100% !important; 2169 | height: 100% !important; 2170 | } 2171 | 2172 | .jslider-focus img { 2173 | display: none; 2174 | } 2175 | 2176 | .jslider img { 2177 | width: 100px; 2178 | } 2179 | 2180 | .jslider-left::before { 2181 | position: fixed; 2182 | left: 15px; 2183 | top: 50%; 2184 | content:'arrow_back_ios'; 2185 | color: #fff; 2186 | width: 30px; 2187 | height: 30px; 2188 | font-family: 'Material Icons'; 2189 | font-size: 30px; 2190 | /* before it was 0px 0px 0px #000 */ 2191 | text-shadow: 0px 0px 6px rgb(56,56,56); 2192 | text-align: center; 2193 | cursor: pointer; 2194 | } 2195 | 2196 | .jslider-right::after { 2197 | position: fixed; 2198 | right: 15px; 2199 | top: 50%; 2200 | content: 'arrow_forward_ios'; 2201 | color: #fff; 2202 | width: 30px; 2203 | height: 30px; 2204 | font-family: 'Material Icons'; 2205 | font-size: 30px; 2206 | /* before it was 0px 0px 0px #000 */ 2207 | text-shadow: 0px 0px 6px rgb(56,56,56); 2208 | text-align: center; 2209 | cursor: pointer; 2210 | } 2211 | 2212 | .jslider-close { 2213 | width:24px; 2214 | height:24px; 2215 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 2216 | position:fixed; 2217 | top:15px; 2218 | right:15px; 2219 | cursor:pointer; 2220 | z-index:3000; 2221 | 2222 | display: block !important; 2223 | } 2224 | 2225 | .jslider-counter { 2226 | height:24px; 2227 | background-color: transparent; 2228 | position:fixed; 2229 | left: 50%; 2230 | transform: translateX(-50%); 2231 | bottom: 15px; 2232 | cursor:pointer; 2233 | z-index:3000; 2234 | 2235 | display: flex; 2236 | display: -webkit-flex; 2237 | -webkit-justify-content: center; 2238 | -webkit-align-items: center; 2239 | -webkit-flex-direction: row; 2240 | justify-content: center; 2241 | align-items: center; 2242 | flex-direction: row; 2243 | } 2244 | 2245 | .jslider-caption { 2246 | position: fixed; 2247 | max-width: 90vw; 2248 | text-overflow: ellipsis; 2249 | white-space: nowrap; 2250 | overflow: hidden; 2251 | top:15px; 2252 | left: 15px; 2253 | z-index:3000; 2254 | color: #FFF; 2255 | font-size: 1rem; 2256 | 2257 | display: block !important; 2258 | } 2259 | 2260 | .jslider-counter div { 2261 | width: 10px; 2262 | height: 10px; 2263 | background: #fff; 2264 | border-radius: 50%; 2265 | margin: 0px 5px; 2266 | 2267 | display: block !important; 2268 | } 2269 | 2270 | .jslider-counter .jslider-counter-focus { 2271 | background-color: cornflowerblue; 2272 | pointer-events: none; 2273 | } 2274 | 2275 | .jslider-focus { 2276 | position:fixed; 2277 | left:0; 2278 | top:0; 2279 | width: 100%; 2280 | min-height:100%; 2281 | max-height:100%; 2282 | z-index:2000; 2283 | margin:0px; 2284 | box-sizing:border-box; 2285 | 2286 | background-color:rgba(0,0,0,0.8); 2287 | -webkit-transition-duration: .05s; 2288 | transition-duration: .05s; 2289 | display: flex; 2290 | -ms-flex-align: center; 2291 | -webkit-align-items: center; 2292 | -webkit-box-align: center; 2293 | 2294 | align-items: center; 2295 | } 2296 | 2297 | .jslider-focus img { 2298 | width: 50vw; 2299 | height: auto; 2300 | box-sizing: border-box; 2301 | margin:0 auto; 2302 | vertical-align:middle; 2303 | display:none; 2304 | } 2305 | 2306 | .jslider-focus img.jslider-vertical { 2307 | width: auto; 2308 | /* before it was 50vh */ 2309 | height: 80vh; 2310 | } 2311 | 2312 | @media only screen and (max-width: 576px) { 2313 | .jslider-focus img.jslider-vertical { 2314 | width: 99vw !important; 2315 | height: auto !important; 2316 | } 2317 | 2318 | .jslider-focus img { 2319 | width: 100vw !important; 2320 | height: auto !important; 2321 | } 2322 | } 2323 | 2324 | .jslider-grid { 2325 | display: -ms-grid; 2326 | display: grid; 2327 | grid-gap: 1px; 2328 | position: relative; 2329 | } 2330 | 2331 | .jslider-grid[data-number='2'] { 2332 | -ms-grid-columns: 1fr 50%; 2333 | grid-template-columns: 1fr 50%; 2334 | } 2335 | 2336 | .jslider-grid[data-number='3'] { 2337 | -ms-grid-columns: 1fr 33%; 2338 | grid-template-columns: 1fr 33%; 2339 | } 2340 | 2341 | .jslider-grid[data-number='4'] { 2342 | -ms-grid-columns: 1fr 25%; 2343 | grid-template-columns: 1fr 25%; 2344 | } 2345 | 2346 | .jslider-grid img { 2347 | display: none; 2348 | width: 100%; 2349 | height: 100%; 2350 | object-fit: cover; 2351 | } 2352 | 2353 | .jslider-grid[data-total]:after { 2354 | content: attr(data-total) "+"; 2355 | font-size: 1.5em; 2356 | position:absolute; 2357 | color: #fff; 2358 | right: 15px; 2359 | bottom: 6px; 2360 | } 2361 | 2362 | .jslider-grid img:first-child { 2363 | -ms-grid-column: 1; 2364 | -ms-grid-row: 1; 2365 | grid-column: 1; 2366 | grid-row: 1; 2367 | display: block; 2368 | } 2369 | 2370 | .jslider-grid[data-number='2'] img:nth-child(2) { 2371 | -ms-grid-column: 2; 2372 | -ms-grid-row: 1; 2373 | grid-column: 2; 2374 | grid-row: 1; 2375 | display: block; 2376 | } 2377 | 2378 | .jslider-grid[data-number='3'] img:first-child { 2379 | -ms-grid-column: 1 / 2; 2380 | -ms-grid-row: 1 / 4; 2381 | grid-column: 1 / 2; 2382 | grid-row: 1 / 4; 2383 | } 2384 | 2385 | .jslider-grid[data-number='3'] img:nth-child(2) { 2386 | -ms-grid-column: 2; 2387 | -ms-grid-row: 1; 2388 | grid-column: 2; 2389 | grid-row: 1; 2390 | display: block; 2391 | } 2392 | 2393 | .jslider-grid[data-number='3'] img:nth-child(3) { 2394 | -ms-grid-column: 2; 2395 | -ms-grid-row: 2; 2396 | grid-column: 2; 2397 | grid-row: 2; 2398 | display: block; 2399 | } 2400 | 2401 | .jslider-grid[data-number='4'] img:first-child { 2402 | -ms-grid-column: 1 / 2; 2403 | -ms-grid-row: 1 / 4; 2404 | grid-column: 1 / 2; 2405 | grid-row: 1 / 4; 2406 | } 2407 | 2408 | .jslider-grid[data-number='4'] img:nth-child(2) { 2409 | -ms-grid-column: 2; 2410 | -ms-grid-row: 1; 2411 | grid-column: 2; 2412 | grid-row: 1; 2413 | display: block; 2414 | } 2415 | 2416 | .jslider-grid[data-number='4'] img:nth-child(3) { 2417 | -ms-grid-column: 2; 2418 | -ms-grid-row: 2; 2419 | grid-column: 2; 2420 | grid-row: 2; 2421 | display: block; 2422 | } 2423 | 2424 | .jslider-grid[data-number='4'] img:nth-child(4) { 2425 | -ms-grid-column: 2; 2426 | -ms-grid-row: 3; 2427 | grid-column: 2; 2428 | grid-row: 3; 2429 | display: block; 2430 | } 2431 | 2432 | 2433 | .jtabs { 2434 | max-width: 100vw; 2435 | position: relative; 2436 | } 2437 | 2438 | .jtabs .jtabs-headers-container { 2439 | display: flex; 2440 | align-items: center; 2441 | } 2442 | 2443 | .jtabs .jtabs-headers { 2444 | display: flex; 2445 | align-items: center; 2446 | overflow: hidden; 2447 | position: relative; 2448 | } 2449 | 2450 | .jtabs .jtabs-headers > div:not(.jtabs-border) { 2451 | padding: 8px; 2452 | padding-left: 20px; 2453 | padding-right: 20px; 2454 | margin-left: 1px; 2455 | margin-right: 1px; 2456 | background-color: #f1f1f1; 2457 | cursor: pointer; 2458 | white-space: nowrap; 2459 | text-align: center; 2460 | } 2461 | 2462 | .jtabs .jtabs-headers > div.jtabs-selected { 2463 | background-color: #e8e8e8; 2464 | color: #000; 2465 | } 2466 | 2467 | .jtabs .jtabs-headers > div > div { 2468 | color: #555; 2469 | width: 100%; 2470 | overflow: hidden; 2471 | } 2472 | 2473 | .jtabs .jtabs-headers i { 2474 | display: block; 2475 | margin: auto; 2476 | } 2477 | 2478 | .jtabs .jtabs-content { 2479 | box-sizing: border-box; 2480 | } 2481 | 2482 | .jtabs .jtabs-content > div { 2483 | display: none; 2484 | box-sizing: border-box; 2485 | } 2486 | 2487 | .jtabs .jtabs-content > div.jtabs-selected { 2488 | display: block; 2489 | } 2490 | 2491 | .jtabs .jtabs-border { 2492 | position: absolute; 2493 | height: 2px; 2494 | background-color: #888; 2495 | transform-origin: left; 2496 | transition: all .2s cubic-bezier(0.4,0,0.2,1); 2497 | transition-property: color,left,transform; 2498 | display: none; 2499 | } 2500 | 2501 | .jtabs-animation .jtabs-border { 2502 | display: initial; 2503 | } 2504 | 2505 | .jtabs .jtabs-controls { 2506 | margin: 3px; 2507 | margin-left: 10px; 2508 | display: flex; 2509 | min-width: 82px; 2510 | } 2511 | 2512 | .jtabs .jtabs-controls > div { 2513 | cursor: pointer; 2514 | background-position: center; 2515 | background-repeat: no-repeat; 2516 | width: 24px; 2517 | height: 24px; 2518 | line-height: 24px; 2519 | } 2520 | 2521 | .jtabs .jtabs-prev { 2522 | margin-left: 10px; 2523 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E"); 2524 | } 2525 | 2526 | .jtabs .jtabs-prev.disabled { 2527 | margin-left: 10px; 2528 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='lightgray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z'/%3E%3C/svg%3E"); 2529 | } 2530 | 2531 | .jtabs .jtabs-next { 2532 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='gray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E"); 2533 | } 2534 | 2535 | .jtabs .jtabs-next.disabled { 2536 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='lightgray' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z'/%3E%3C/svg%3E"); 2537 | } 2538 | 2539 | .jtabs .jtabs-add { 2540 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24' viewBox='0 0 24 24' width='24'%3E%3Cpath d='M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-2 10h-4v4h-2v-4H7v-2h4V7h2v4h4v2z' fill='%23bbbbbb'/%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3C/svg%3E"); 2541 | } 2542 | 2543 | /** Modern skin **/ 2544 | 2545 | .jtabs.jtabs-modern .jtabs-headers > div:not(.jtabs-border) { 2546 | padding: 4px; 2547 | padding-left: 10px; 2548 | padding-right: 10px; 2549 | background-color: #fff; 2550 | } 2551 | 2552 | .jtabs.jtabs-modern .jtabs-headers > .jtabs-selected { 2553 | color: #000; 2554 | } 2555 | 2556 | .jtabs.jtabs-modern .jtabs-headers > .jtabs-selected .material-icons { 2557 | color: #000; 2558 | } 2559 | 2560 | .jtabs.jtabs-modern .jtabs-headers { 2561 | background: #EEEEEF !important; 2562 | padding: 2px; 2563 | border-radius: 4px; 2564 | } 2565 | 2566 | .jtabs.jtabs-modern .jtabs-headers .jtabs-border { 2567 | border-color: #EEEEEF !important; 2568 | } 2569 | 2570 | .jtabs.jtabs-modern .jtabs-border { 2571 | background-color: rgba(194, 197, 188, 0.884); 2572 | } 2573 | 2574 | .jtags { 2575 | display: flex; 2576 | flex-wrap: wrap; 2577 | -ms-flex-direction: row; 2578 | -webkit-flex-direction: row; 2579 | flex-direction: row; 2580 | -ms-flex-pack: flex-start; 2581 | -webkit-justify-content: space-between; 2582 | justify-content: flex-start; 2583 | padding: 2px; 2584 | border: 1px solid #ccc; 2585 | } 2586 | 2587 | .jtags.jtags-empty:not(.jtags-focus)::before { 2588 | position: absolute; 2589 | margin: 5px; 2590 | color: #ccc; 2591 | content:attr(data-placeholder); 2592 | } 2593 | 2594 | .jtags > div { 2595 | padding: 3px; 2596 | padding-left: 10px; 2597 | padding-right: 22px; 2598 | position: relative; 2599 | border-radius: 1px; 2600 | margin: 2px; 2601 | display: block; 2602 | outline: none; 2603 | } 2604 | 2605 | .jtags > div:empty:before { 2606 | content: " "; 2607 | white-space: pre; 2608 | } 2609 | 2610 | .jtags > div::after { 2611 | content: 'x'; 2612 | position: absolute; 2613 | top: 4px; 2614 | right: 4px; 2615 | width: 12px; 2616 | height: 12px; 2617 | cursor: pointer; 2618 | font-size: 11px; 2619 | display: none; 2620 | } 2621 | 2622 | .jtags_label { 2623 | background-color: #eeeeee !important; 2624 | } 2625 | 2626 | .jtags_label::after { 2627 | display: inline-block !important; 2628 | } 2629 | 2630 | .jtags_error::after { 2631 | color: #fff !important; 2632 | } 2633 | 2634 | .jtags_error { 2635 | background-color: #d93025 !important; 2636 | color: #fff; 2637 | } 2638 | 2639 | 2640 | .jtoolbar-container { 2641 | border-radius: 2px; 2642 | margin-bottom: 5px; 2643 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); 2644 | display: inline-flex !important; 2645 | } 2646 | 2647 | .jtoolbar { 2648 | cursor: pointer; 2649 | white-space: nowrap; 2650 | display: flex; 2651 | padding:4px; 2652 | -webkit-user-select: none; 2653 | -moz-user-select: none; 2654 | -ms-user-select: none; 2655 | user-select: none; 2656 | font-size: 13px; 2657 | } 2658 | 2659 | .jtoolbar-readonly { 2660 | pointer-events: none; 2661 | } 2662 | 2663 | .jtoolbar-readonly div, 2664 | .jtoolbar-readonly .jtoolbar-item i { 2665 | color: gray; 2666 | } 2667 | 2668 | .jtoolbar-mobile { 2669 | display: flex; 2670 | position:fixed; 2671 | bottom: 0; 2672 | margin: 0; 2673 | left: 0; 2674 | width: 100%; 2675 | background: #f7f7f8; 2676 | z-index: 1; 2677 | box-sizing: border-box; 2678 | box-shadow: 0 -1px 2px rgba(0,0,0,.1); 2679 | border-radius: 0px; 2680 | } 2681 | 2682 | .jtoolbar > div { 2683 | display: inline-flex; 2684 | align-items: center; 2685 | box-sizing: border-box; 2686 | vertical-align:middle; 2687 | justify-content: space-evenly; 2688 | } 2689 | 2690 | .jtoolbar-mobile > div { 2691 | display: flex; 2692 | width: 100%; 2693 | } 2694 | 2695 | .jtoolbar .jtoolbar-item { 2696 | text-align: center; 2697 | margin: auto; 2698 | padding: 2px; 2699 | padding-left:4px; 2700 | padding-right:4px; 2701 | } 2702 | 2703 | .jtoolbar-mobile .jtoolbar-item { 2704 | position: relative; 2705 | flex:1; 2706 | } 2707 | 2708 | .jtoolbar .jtoolbar-divisor { 2709 | width: 2px; 2710 | height: 18px; 2711 | padding: 0px; 2712 | margin-left: 4px; 2713 | margin-right: 4px; 2714 | background-color: #ddd; 2715 | } 2716 | 2717 | .jtoolbar .jtoolbar-label { 2718 | padding-left: 8px; 2719 | padding-right: 8px; 2720 | } 2721 | 2722 | 2723 | 2724 | .jtoolbar-mobile a 2725 | { 2726 | text-decoration:none; 2727 | display:inline-block; 2728 | } 2729 | 2730 | .jtoolbar-mobile i { 2731 | display: inline-flex !important; 2732 | color:#929292; 2733 | } 2734 | 2735 | .jtoolbar-mobile span { 2736 | font-size:0.7em; 2737 | display:block; 2738 | color:#929292; 2739 | } 2740 | 2741 | .jtoolbar-mobile .jtoolbar-selected a, .jtoolbar-mobile .jtoolbar-selected i, .jtoolbar-mobile .jtoolbar-selected span { 2742 | color:var(--active-color) !important; 2743 | background-color:transparent; 2744 | } 2745 | 2746 | .jtoolbar-item { 2747 | -webkit-user-select: none; 2748 | -moz-user-select: none; 2749 | -ms-user-select: none; 2750 | user-select: none; 2751 | } 2752 | 2753 | .jtoolbar-item i { 2754 | display: block; 2755 | color:#333; 2756 | } 2757 | 2758 | .jtoolbar-item:hover { 2759 | background-color:#f2f2f2; 2760 | } 2761 | 2762 | 2763 | .jtoolbar .jpicker { 2764 | padding-left:0px; 2765 | padding-right:0px; 2766 | } 2767 | 2768 | .jtoolbar .jpicker-header { 2769 | height: 24px; 2770 | line-height: 24px; 2771 | padding: 0px; 2772 | padding-right: 20px; 2773 | padding-left: 8px; 2774 | background-position: top 50% right 0px; 2775 | display: flex; 2776 | align-items: center; 2777 | font-size: 0.9em; 2778 | } 2779 | 2780 | .jtoolbar .jpicker-content > div { 2781 | padding: 6px; 2782 | } 2783 | 2784 | .jtoolbar-active { 2785 | background-color:#eee; 2786 | } 2787 | 2788 | .jtoolbar .fa { 2789 | width: 18px; 2790 | height: 18px; 2791 | display: block; 2792 | line-height: 18px; 2793 | font-size: 14px; 2794 | } 2795 | 2796 | .jtoolbar .material-icons { 2797 | font-size: 18px; 2798 | width: 24px; 2799 | height: 24px; 2800 | display: block; 2801 | line-height: 24px; 2802 | transform: rotate(0.03deg); 2803 | text-align: center; 2804 | } 2805 | 2806 | .jtoolbar .jtoolbar-arrow { 2807 | background-repeat: no-repeat; 2808 | background-position: center; 2809 | background-image: url("data:image/svg+xml,%0A%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='black' width='18px' height='18px'%3E%3Cpath d='M0 0h24v24H0z' fill='none'/%3E%3Cpath d='M12 8c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z'/%3E%3C/svg%3E"); 2810 | width: 24px; 2811 | height: 16px; 2812 | margin-left: 4px; 2813 | border-left: 1px solid #f2f2f2; 2814 | } 2815 | 2816 | .jtoolbar-floating { 2817 | position: absolute; 2818 | display: none; 2819 | box-shadow: 0 4px 5px 0 rgba(0,0,0,0.14), 0 1px 10px 0 rgba(0,0,0,0.12), 0 2px 4px -1px rgba(0,0,0,0.2); 2820 | border-radius: 4px; 2821 | background-color: #fff; 2822 | padding: 4px; 2823 | z-index: 50; 2824 | text-align: left; 2825 | margin-right: 20px; 2826 | } 2827 | 2828 | .jtoolbar-floating .jtoolbar-divisor { 2829 | display: none; 2830 | } 2831 | 2832 | .jtoolbar-arrow-selected .jtoolbar-floating { 2833 | display: flex; 2834 | flex-wrap: wrap; 2835 | } 2836 | 2837 | 2838 | -------------------------------------------------------------------------------- /web/reconnecting_websocket.js: -------------------------------------------------------------------------------- 1 | // MIT License: 2 | // 3 | // Copyright (c) 2010-2012, Joe Walnes 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 13 | // all 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 21 | // THE SOFTWARE. 22 | 23 | /** 24 | * This behaves like a WebSocket in every way, except if it fails to connect, 25 | * or it gets disconnected, it will repeatedly poll until it successfully connects 26 | * again. 27 | * 28 | * It is API compatible, so when you have: 29 | * ws = new WebSocket('ws://....'); 30 | * you can replace with: 31 | * ws = new ReconnectingWebSocket('ws://....'); 32 | * 33 | * The event stream will typically look like: 34 | * onconnecting 35 | * onopen 36 | * onmessage 37 | * onmessage 38 | * onclose // lost connection 39 | * onconnecting 40 | * onopen // sometime later... 41 | * onmessage 42 | * onmessage 43 | * etc... 44 | * 45 | * It is API compatible with the standard WebSocket API, apart from the following members: 46 | * 47 | * - `bufferedAmount` 48 | * - `extensions` 49 | * - `binaryType` 50 | * 51 | * Latest version: https://github.com/joewalnes/reconnecting-websocket/ 52 | * - Joe Walnes 53 | * 54 | * Syntax 55 | * ====== 56 | * var socket = new ReconnectingWebSocket(url, protocols, options); 57 | * 58 | * Parameters 59 | * ========== 60 | * url - The url you are connecting to. 61 | * protocols - Optional string or array of protocols. 62 | * options - See below 63 | * 64 | * Options 65 | * ======= 66 | * Options can either be passed upon instantiation or set after instantiation: 67 | * 68 | * var socket = new ReconnectingWebSocket(url, null, { debug: true, reconnectInterval: 4000 }); 69 | * 70 | * or 71 | * 72 | * var socket = new ReconnectingWebSocket(url); 73 | * socket.debug = true; 74 | * socket.reconnectInterval = 4000; 75 | * 76 | * debug 77 | * - Whether this instance should log debug messages. Accepts true or false. Default: false. 78 | * 79 | * automaticOpen 80 | * - Whether or not the websocket should attempt to connect immediately upon instantiation. The socket can be manually opened or closed at any time using ws.open() and ws.close(). 81 | * 82 | * reconnectInterval 83 | * - The number of milliseconds to delay before attempting to reconnect. Accepts integer. Default: 1000. 84 | * 85 | * maxReconnectInterval 86 | * - The maximum number of milliseconds to delay a reconnection attempt. Accepts integer. Default: 30000. 87 | * 88 | * reconnectDecay 89 | * - The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. Accepts integer or float. Default: 1.5. 90 | * 91 | * timeoutInterval 92 | * - The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. Accepts integer. Default: 2000. 93 | * 94 | */ 95 | (function(global, factory) { 96 | if (typeof define === 'function' && define.amd) { 97 | define([], factory); 98 | } else if (typeof module !== 'undefined' && module.exports) { 99 | module.exports = factory(); 100 | } else { 101 | global.ReconnectingWebSocket = factory(); 102 | } 103 | })(this, function() { 104 | 105 | if (!('WebSocket' in window)) { 106 | return; 107 | } 108 | 109 | function ReconnectingWebSocket(url, protocols, options) { 110 | 111 | // Default settings 112 | var settings = { 113 | 114 | /** Whether this instance should log debug messages. */ 115 | debug: false, 116 | 117 | /** Whether or not the websocket should attempt to connect immediately upon instantiation. */ 118 | automaticOpen: true, 119 | 120 | /** The number of milliseconds to delay before attempting to reconnect. */ 121 | reconnectInterval: 1000, 122 | /** The maximum number of milliseconds to delay a reconnection attempt. */ 123 | maxReconnectInterval: 30000, 124 | /** The rate of increase of the reconnect delay. Allows reconnect attempts to back off when problems persist. */ 125 | reconnectDecay: 1.5, 126 | 127 | /** The maximum time in milliseconds to wait for a connection to succeed before closing and retrying. */ 128 | timeoutInterval: 2000, 129 | 130 | /** The maximum number of reconnection attempts to make. Unlimited if null. */ 131 | maxReconnectAttempts: null, 132 | 133 | /** The binary type, possible values 'blob' or 'arraybuffer', default 'blob'. */ 134 | binaryType: 'blob' 135 | } 136 | if (!options) { options = {}; } 137 | 138 | // Overwrite and define settings with options if they exist. 139 | for (var key in settings) { 140 | if (typeof options[key] !== 'undefined') { 141 | this[key] = options[key]; 142 | } else { 143 | this[key] = settings[key]; 144 | } 145 | } 146 | 147 | // These should be treated as read-only properties 148 | 149 | /** The URL as resolved by the constructor. This is always an absolute URL. Read only. */ 150 | this.url = url; 151 | 152 | /** The number of attempted reconnects since starting, or the last successful connection. Read only. */ 153 | this.reconnectAttempts = 0; 154 | 155 | /** 156 | * The current state of the connection. 157 | * Can be one of: WebSocket.CONNECTING, WebSocket.OPEN, WebSocket.CLOSING, WebSocket.CLOSED 158 | * Read only. 159 | */ 160 | this.readyState = WebSocket.CONNECTING; 161 | 162 | /** 163 | * A string indicating the name of the sub-protocol the server selected; this will be one of 164 | * the strings specified in the protocols parameter when creating the WebSocket object. 165 | * Read only. 166 | */ 167 | this.protocol = null; 168 | 169 | // Private state variables 170 | 171 | var self = this; 172 | var ws; 173 | var forcedClose = false; 174 | var timedOut = false; 175 | var eventTarget = document.createElement('div'); 176 | 177 | // Wire up "on*" properties as event handlers 178 | 179 | eventTarget.addEventListener('open', function(event) { self.onopen(event); }); 180 | eventTarget.addEventListener('close', function(event) { self.onclose(event); }); 181 | eventTarget.addEventListener('connecting', function(event) { self.onconnecting(event); }); 182 | eventTarget.addEventListener('message', function(event) { self.onmessage(event); }); 183 | eventTarget.addEventListener('error', function(event) { self.onerror(event); }); 184 | 185 | // Expose the API required by EventTarget 186 | 187 | this.addEventListener = eventTarget.addEventListener.bind(eventTarget); 188 | this.removeEventListener = eventTarget.removeEventListener.bind(eventTarget); 189 | this.dispatchEvent = eventTarget.dispatchEvent.bind(eventTarget); 190 | 191 | /** 192 | * This function generates an event that is compatible with standard 193 | * compliant browsers and IE9 - IE11 194 | * 195 | * This will prevent the error: 196 | * Object doesn't support this action 197 | * 198 | * http://stackoverflow.com/questions/19345392/why-arent-my-parameters-getting-passed-through-to-a-dispatched-event/19345563#19345563 199 | * @param s String The name that the event should use 200 | * @param args Object an optional object that the event will use 201 | */ 202 | function generateEvent(s, args) { 203 | var evt = document.createEvent("CustomEvent"); 204 | evt.initCustomEvent(s, false, false, args); 205 | return evt; 206 | }; 207 | 208 | this.open = function(reconnectAttempt) { 209 | ws = new WebSocket(self.url, protocols || []); 210 | ws.binaryType = this.binaryType; 211 | 212 | if (reconnectAttempt) { 213 | if (this.maxReconnectAttempts && this.reconnectAttempts > this.maxReconnectAttempts) { 214 | return; 215 | } 216 | } else { 217 | eventTarget.dispatchEvent(generateEvent('connecting')); 218 | this.reconnectAttempts = 0; 219 | } 220 | 221 | if (self.debug || ReconnectingWebSocket.debugAll) { 222 | console.debug('ReconnectingWebSocket', 'attempt-connect', self.url); 223 | } 224 | 225 | var localWs = ws; 226 | var timeout = setTimeout(function() { 227 | if (self.debug || ReconnectingWebSocket.debugAll) { 228 | console.debug('ReconnectingWebSocket', 'connection-timeout', self.url); 229 | } 230 | timedOut = true; 231 | localWs.close(); 232 | timedOut = false; 233 | }, self.timeoutInterval); 234 | 235 | ws.onopen = function(event) { 236 | clearTimeout(timeout); 237 | if (self.debug || ReconnectingWebSocket.debugAll) { 238 | console.debug('ReconnectingWebSocket', 'onopen', self.url); 239 | } 240 | self.protocol = ws.protocol; 241 | self.readyState = WebSocket.OPEN; 242 | self.reconnectAttempts = 0; 243 | var e = generateEvent('open'); 244 | e.isReconnect = reconnectAttempt; 245 | reconnectAttempt = false; 246 | eventTarget.dispatchEvent(e); 247 | }; 248 | 249 | ws.onclose = function(event) { 250 | clearTimeout(timeout); 251 | ws = null; 252 | if (forcedClose) { 253 | self.readyState = WebSocket.CLOSED; 254 | eventTarget.dispatchEvent(generateEvent('close')); 255 | } else { 256 | self.readyState = WebSocket.CONNECTING; 257 | var e = generateEvent('connecting'); 258 | e.code = event.code; 259 | e.reason = event.reason; 260 | e.wasClean = event.wasClean; 261 | eventTarget.dispatchEvent(e); 262 | if (!reconnectAttempt && !timedOut) { 263 | if (self.debug || ReconnectingWebSocket.debugAll) { 264 | console.debug('ReconnectingWebSocket', 'onclose', self.url); 265 | } 266 | eventTarget.dispatchEvent(generateEvent('close')); 267 | } 268 | 269 | var timeout = self.reconnectInterval * Math.pow(self.reconnectDecay, self.reconnectAttempts); 270 | setTimeout(function() { 271 | self.reconnectAttempts++; 272 | self.open(true); 273 | }, timeout > self.maxReconnectInterval ? self.maxReconnectInterval : timeout); 274 | } 275 | }; 276 | ws.onmessage = function(event) { 277 | if (self.debug || ReconnectingWebSocket.debugAll) { 278 | console.debug('ReconnectingWebSocket', 'onmessage', self.url, event.data); 279 | } 280 | var e = generateEvent('message'); 281 | e.data = event.data; 282 | eventTarget.dispatchEvent(e); 283 | }; 284 | ws.onerror = function(event) { 285 | if (self.debug || ReconnectingWebSocket.debugAll) { 286 | console.debug('ReconnectingWebSocket', 'onerror', self.url, event); 287 | } 288 | eventTarget.dispatchEvent(generateEvent('error')); 289 | }; 290 | } 291 | 292 | // Whether or not to create a websocket upon instantiation 293 | if (this.automaticOpen == true) { 294 | this.open(false); 295 | } 296 | 297 | /** 298 | * Transmits data to the server over the WebSocket connection. 299 | * 300 | * @param data a text string, ArrayBuffer or Blob to send to the server. 301 | */ 302 | this.send = function(data) { 303 | if (ws) { 304 | if (self.debug || ReconnectingWebSocket.debugAll) { 305 | console.debug('ReconnectingWebSocket', 'send', self.url, data); 306 | } 307 | return ws.send(data); 308 | } else { 309 | throw 'INVALID_STATE_ERR : Pausing to reconnect websocket'; 310 | } 311 | }; 312 | 313 | /** 314 | * Closes the WebSocket connection or connection attempt, if any. 315 | * If the connection is already CLOSED, this method does nothing. 316 | */ 317 | this.close = function(code, reason) { 318 | // Default CLOSE_NORMAL code 319 | if (typeof code == 'undefined') { 320 | code = 1000; 321 | } 322 | forcedClose = true; 323 | if (ws) { 324 | ws.close(code, reason); 325 | } 326 | }; 327 | 328 | /** 329 | * Additional public API method to refresh the connection if still open (close, re-open). 330 | * For example, if the app suspects bad data / missed heart beats, it can try to refresh. 331 | */ 332 | this.refresh = function() { 333 | if (ws) { 334 | ws.close(); 335 | } 336 | }; 337 | } 338 | 339 | /** 340 | * An event listener to be called when the WebSocket connection's readyState changes to OPEN; 341 | * this indicates that the connection is ready to send and receive data. 342 | */ 343 | ReconnectingWebSocket.prototype.onopen = function(event) {}; 344 | /** An event listener to be called when the WebSocket connection's readyState changes to CLOSED. */ 345 | ReconnectingWebSocket.prototype.onclose = function(event) {}; 346 | /** An event listener to be called when a connection begins being attempted. */ 347 | ReconnectingWebSocket.prototype.onconnecting = function(event) {}; 348 | /** An event listener to be called when a message is received from the server. */ 349 | ReconnectingWebSocket.prototype.onmessage = function(event) {}; 350 | /** An event listener to be called when an error occurs. */ 351 | ReconnectingWebSocket.prototype.onerror = function(event) {}; 352 | 353 | /** 354 | * Whether all instances of ReconnectingWebSocket should log debug messages. 355 | * Setting this to true is the equivalent of setting all instances of ReconnectingWebSocket.debug to true. 356 | */ 357 | ReconnectingWebSocket.debugAll = false; 358 | 359 | ReconnectingWebSocket.CONNECTING = WebSocket.CONNECTING; 360 | ReconnectingWebSocket.OPEN = WebSocket.OPEN; 361 | ReconnectingWebSocket.CLOSING = WebSocket.CLOSING; 362 | ReconnectingWebSocket.CLOSED = WebSocket.CLOSED; 363 | 364 | return ReconnectingWebSocket; 365 | }); -------------------------------------------------------------------------------- /web_page_to_c.py: -------------------------------------------------------------------------------- 1 | #!/bin/env python 2 | import os 3 | import gzip 4 | 5 | site_files = ['index.html', 'favicon.png', 'csv.png', 'reconnecting_websocket.js', 'LCD.woff', 'jexcel.css', 'jexcel.js', 'jexcel.themes.css', 'jsuites.css', 'jsuites.js'] 6 | 7 | with open(os.path.join('include','web_assets.h'), 'w') as f_out: 8 | for file_name in site_files: 9 | gz = gzip.compress(open(os.path.join('web',file_name), 'rb').read()) 10 | fn = file_name.replace('.', '_').replace('-', '_') 11 | f_out.write('const char PROGMEM {}[{}] = {{'.format(fn, len(gz))) 12 | for b in gz: 13 | f_out.write('{}, '.format(hex(b))) 14 | f_out.write('};\n') 15 | 16 | --------------------------------------------------------------------------------