├── .dockerignore ├── .gitignore ├── docker-compose.yml ├── src ├── web │ ├── css │ │ ├── syntax.css │ │ ├── hoverMessage.css │ │ ├── settingsView.css │ │ ├── methodCodeView.css │ │ ├── helpView.css │ │ ├── joint.min.css │ │ ├── interface.css │ │ ├── classView.css │ │ ├── treeView.css │ │ └── extras.css │ ├── js │ │ ├── UI.js │ │ ├── HoverMessage.js │ │ ├── Source.js │ │ ├── Logic.js │ │ ├── ClassTree.js │ │ ├── CacheClassExplorer.js │ │ └── Lib.js │ ├── jsLib │ │ ├── ImageExporter.js │ │ ├── joint.shapes.uml.js │ │ └── backbone-min.js │ └── index.html └── cls │ └── ClassExplorer │ ├── StaticContent.cls │ ├── Installer.cls │ ├── Router.cls │ └── ClassView.cls ├── .github └── workflows │ └── objectscript-quality.yml ├── irissession.sh ├── Dockerfile-build-npm ├── module.xml ├── Installer.cls ├── LICENSE ├── import.bat ├── package.json ├── Dockerfile ├── gulpfile.js └── readme.md /.dockerignore: -------------------------------------------------------------------------------- 1 | build-for-zpm/* 2 | readme.md -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /build 3 | /package-lock.json -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | iris: 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | restart: always 8 | ports: 9 | - 51773 10 | - 52773:52773 11 | - 53773 12 | volumes: 13 | - ~/iris.key:/usr/irissys/mgr/iris.key 14 | - ./:/irisdev/app -------------------------------------------------------------------------------- /src/web/css/syntax.css: -------------------------------------------------------------------------------- 1 | .syntax-comment { 2 | color: green; 3 | } 4 | 5 | .syntax-string { 6 | color: #394; 7 | } 8 | 9 | .syntax-vars, .syntax-keyword { 10 | color: #00b; 11 | } 12 | 13 | .syntax-names { 14 | color: #299; 15 | } 16 | 17 | .syntax-functions { 18 | color: #986; 19 | } 20 | 21 | .syntax-global { 22 | color: #800; 23 | } 24 | 25 | .syntax-other { 26 | color: red; 27 | } -------------------------------------------------------------------------------- /.github/workflows/objectscript-quality.yml: -------------------------------------------------------------------------------- 1 | name: objectscriptquality 2 | on: push 3 | 4 | jobs: 5 | linux: 6 | name: Linux build 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - name: Execute ObjectScript Quality Analysis 11 | run: wget https://raw.githubusercontent.com/litesolutions/objectscriptquality-jenkins-integration/master/iris-community-hook.sh && sh ./iris-community-hook.sh 12 | 13 | -------------------------------------------------------------------------------- /irissession.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | iris start $ISC_PACKAGE_INSTANCENAME quietly 4 | 5 | cat << EOF | iris session $ISC_PACKAGE_INSTANCENAME -U %SYS 6 | do ##class(%SYSTEM.Process).CurrentDirectory("$PWD") 7 | $@ 8 | if '\$Get(sc) do ##class(%SYSTEM.Process).Terminate(, 1) 9 | zn "%SYS" 10 | do ##class(SYS.Container).QuiesceForBundling() 11 | Do ##class(Security.Users).UnExpireUserPasswords("*") 12 | halt 13 | EOF 14 | 15 | exit=$? 16 | 17 | iris stop $ISC_PACKAGE_INSTANCENAME quietly 18 | 19 | exit $exit -------------------------------------------------------------------------------- /src/web/css/hoverMessage.css: -------------------------------------------------------------------------------- 1 | .hoverMessage { 2 | 3 | white-space: pre-line; 4 | background: rgba(255, 255, 255, 0.9); 5 | box-shadow: 0 2px 6px rgba(0, 0, 0, 0.4); 6 | border: 1px solid dimgray; 7 | padding: .3em; 8 | overflow: hidden; 9 | line-height: 1em; 10 | max-height: 10em; 11 | 12 | } 13 | 14 | .line-hoverable { 15 | cursor: pointer; 16 | font-style: italic; 17 | -webkit-transition: all .5s ease; 18 | -moz-transition: all .5s ease; 19 | -o-transition: all .5s ease; 20 | transition: all .5s ease; 21 | } 22 | 23 | .line-hoverable:hover { 24 | fill: red; 25 | } 26 | 27 | .hoverContainer { 28 | 29 | position: fixed; 30 | box-sizing: border-box; 31 | left: 0; 32 | top: 0; 33 | padding: 15px; 34 | 35 | } -------------------------------------------------------------------------------- /Dockerfile-build-npm: -------------------------------------------------------------------------------- 1 | FROM ubuntu:latest 2 | ARG NODE_VERSION=10 3 | 4 | # install curl 5 | RUN apt update && apt install curl -y 6 | 7 | # install nvm 8 | RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash 9 | 10 | # set env 11 | ENV NVM_DIR=/root/.nvm 12 | 13 | # install node 14 | RUN bash -c "source $NVM_DIR/nvm.sh && nvm install ${NODE_VERSION} && nvm use ${NODE_VERSION}" 15 | 16 | # copy code 17 | WORKDIR /opt/irisapp 18 | COPY src ./src 19 | COPY gulpfile.js . 20 | COPY package.json . 21 | 22 | # build npm 23 | RUN bash -c "source $NVM_DIR/nvm.sh && npm install && npm run gulp" 24 | 25 | 26 | # set ENTRYPOINT for reloading nvm-environment 27 | ENTRYPOINT ["bash", "-c", "source $NVM_DIR/nvm.sh && exec \"$@\"", "--"] 28 | 29 | # set cmd to bash 30 | CMD ["/bin/bash"] -------------------------------------------------------------------------------- /module.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | classexplorer 6 | 1.21.5 7 | UML Class Diagram Builder for InterSystems Data Platforms 8 | module 9 | build-for-zpm 10 | 11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/web/css/settingsView.css: -------------------------------------------------------------------------------- 1 | #settingsView { 2 | position: absolute; 3 | width: 100%; 4 | bottom: 0; 5 | height: 0; 6 | background: rgba(245, 245, 245, 0.95); 7 | z-index: 10; 8 | -webkit-transition: all .5s ease; 9 | -moz-transition: all .5s ease; 10 | -o-transition: all .5s ease; 11 | transition: all .5s ease; 12 | } 13 | 14 | #settingsView.active { 15 | box-shadow: 0 0 5px black; 16 | height: 100%; 17 | } 18 | 19 | #settingsView > div.head { 20 | position: absolute; 21 | width: 100%; 22 | box-sizing: border-box; 23 | top: 0; 24 | left: 0; 25 | padding: 1em; 26 | z-index: 2; 27 | text-align: right; 28 | } 29 | 30 | #settingsView table td { 31 | text-align: left; 32 | } 33 | 34 | #settingsView table td:nth-child(3) { 35 | color: #888; 36 | padding-left: 1em; 37 | } -------------------------------------------------------------------------------- /Installer.cls: -------------------------------------------------------------------------------- 1 | Class App.Installer 2 | { 3 | 4 | XData setup 5 | { 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | } 19 | 20 | ClassMethod setup(ByRef pVars, pLogLevel As %Integer = 3, pInstaller As %Installer.Installer, pLogger As %Installer.AbstractLogger) As %Status [ CodeMode = objectgenerator, Internal ] 21 | { 22 | #; Let XGL document generate code for this method. 23 | Quit ##class(%Installer.Manifest).%Generate(%compiledclass, %code, "setup") 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/web/css/methodCodeView.css: -------------------------------------------------------------------------------- 1 | #methodCodeView { 2 | position: absolute; 3 | width: 100%; 4 | bottom: 0; 5 | height: 0; 6 | background: rgba(245, 245, 245, 0.95); 7 | z-index: 10; 8 | -webkit-transition: all .5s ease; 9 | -moz-transition: all .5s ease; 10 | -o-transition: all .5s ease; 11 | transition: all .5s ease; 12 | } 13 | 14 | #methodCodeView > div.head { 15 | position: relative; 16 | overflow: visible; /* kept for button shadow */ 17 | margin: 1em; 18 | } 19 | 20 | #methodDescription { 21 | box-sizing: border-box; 22 | padding: 1em; 23 | color: gray; 24 | font-style: italic; 25 | } 26 | 27 | #methodViewBounds { 28 | overflow: auto; 29 | } 30 | 31 | #methodCode { 32 | padding: 1em; 33 | box-sizing: border-box; 34 | white-space: pre; 35 | } 36 | 37 | #closeMethodCodeView { 38 | float: right; 39 | } 40 | 41 | #methodCodeView.active { 42 | box-shadow: 0 0 5px black; 43 | height: 100%; 44 | } -------------------------------------------------------------------------------- /src/cls/ClassExplorer/StaticContent.cls: -------------------------------------------------------------------------------- 1 | /// Cache Class Explorer v generated static content. 2 | /// Class contains methods that return JS/CSS/HTML data for single page application. 3 | Class ClassExplorer.StaticContent 4 | { 5 | 6 | /// Write the contents of xData tag 7 | ClassMethod Write(Const As %String) As %Status 8 | { 9 | Set xdata = ##class(%Dictionary.CompiledXData).%OpenId("ClassExplorer.StaticContent||"_Const).Data 10 | set status=##class(%XML.TextReader).ParseStream(xdata, .textreader) 11 | while textreader.Read() { if (textreader.NodeType="chars") { w textreader.Value } } 12 | return $$$OK 13 | } 14 | 15 | XData HTML 16 | { 17 | 18 | ]]> 19 | 20 | } 21 | 22 | XData CSS 23 | { 24 | 25 | ]]> 26 | 27 | } 28 | 29 | XData JS 30 | { 31 | 32 | ]]> 33 | 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/web/css/helpView.css: -------------------------------------------------------------------------------- 1 | #helpView { 2 | position: absolute; 3 | width: 100%; 4 | bottom: 0; 5 | height: 0; 6 | background: rgba(245, 245, 245, 0.95); 7 | z-index: 10; 8 | -webkit-transition: all .5s ease; 9 | -moz-transition: all .5s ease; 10 | -o-transition: all .5s ease; 11 | transition: all .5s ease; 12 | } 13 | 14 | #helpView.active { 15 | box-shadow: 0 0 5px black; 16 | height: 100%; 17 | } 18 | 19 | #helpView > div.head { 20 | position: absolute; 21 | width: 100%; 22 | box-sizing: border-box; 23 | top: 0; 24 | left: 0; 25 | padding: 1em; 26 | z-index: 2; 27 | } 28 | 29 | #helpView > div.head > h2 { 30 | margin: 0; 31 | } 32 | 33 | #closeHelp { 34 | float: right; 35 | } 36 | 37 | #helpView .body { 38 | padding: 4em 1em 1em 1em; 39 | box-sizing: border-box; 40 | height: 100%; 41 | overflow: auto; 42 | } 43 | 44 | .help-class-box { 45 | border: 1px solid black; 46 | padding: 3px 10px; 47 | font-weight: bold; 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-2019 Nikita Savchenko . 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 | 23 | -------------------------------------------------------------------------------- /import.bat: -------------------------------------------------------------------------------- 1 | :: This batch script makes the Caché application deployment much faster by building and importing 2 | :: the project. Replace the path below to your Caché installation and build & import application to 3 | :: Caché using only one command. 4 | 5 | :: Latest NodeJS & Caché 2016.2+ IS REQUIRED TO PROCEED 6 | @echo off 7 | 8 | :: CHANGE THIS PATH TO YOUR CACHÉ INSTALLATION PATH ON WINDOWS (folder that contains bin, CSP, mgr and other folders) 9 | set CACHE_DIR=C:\Program Files\Ensemble-2017 10 | :: NAMESPACE TO IMPORT PACKAGE TO 11 | set NAMESPACE=USER 12 | :: Other variables 13 | set BUILD_DIR=build\cls 14 | :: Export 15 | set XML_EXPORT_DIR=build 16 | set PACKAGE_NAME=ClassExplorer 17 | 18 | npm run gulp && ^ 19 | echo s st = $system.Status.GetErrorText($system.OBJ.ImportDir("%~dp0%BUILD_DIR%",,"ck")) w "IMPORT STATUS: "_$case(st="",1:"OK",:st) halt | "%CACHE_DIR%\bin\cache.exe" -s "%CACHE_DIR%\mgr" -U %NAMESPACE% && ^ 20 | echo s st = $system.Status.GetErrorText($system.OBJ.ExportPackage("%PACKAGE_NAME%", "%~dp0%XML_EXPORT_DIR%\%PACKAGE_NAME%-v"_##class(%PACKAGE_NAME%.Installer).#VERSION_".xml")) w $c(13,10)_"EXPORT STATUS: "_$case(st="",1:"OK",:st) halt | "%CACHE_DIR%\bin\cache.exe" -s "%CACHE_DIR%\mgr" -U %NAMESPACE% -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CacheClassExplorer", 3 | "version": "1.21.4", 4 | "description": "UML Class Diagram Builder for InterSystems Data Platforms", 5 | "directories": { 6 | "test": "test" 7 | }, 8 | "dependencies": {}, 9 | "devDependencies": { 10 | "autoprefixer-core": "^6.0.1", 11 | "gulp": "^3.9.0", 12 | "gulp-add-src": "^0.2.0", 13 | "gulp-clean": "^0.3.1", 14 | "gulp-concat": "^2.6.1", 15 | "gulp-header": "^1.8.8", 16 | "gulp-html-replace": "^1.6.2", 17 | "gulp-minify-css": "^1.2.4", 18 | "gulp-postcss": "^6.4.0", 19 | "gulp-preprocess": "^2.0.0", 20 | "gulp-rename": "^1.2.0", 21 | "gulp-replace": "^0.5.3", 22 | "gulp-strip-comments": "^2.4.5", 23 | "gulp-uglify": "^2.1.2", 24 | "gulp-wrap": "^0.13.0", 25 | "gulp-zip": "^4.0.0" 26 | }, 27 | "scripts": { 28 | "gulp": "gulp", 29 | "zip": "gulp zipRelease" 30 | }, 31 | "repository": { 32 | "type": "git", 33 | "url": "https://github.com/ZitRos/CacheUMLExplorer" 34 | }, 35 | "keywords": [ 36 | "UMLExplorer", 37 | "Caché", 38 | "Class", 39 | "Explorer", 40 | "UML", 41 | "diagram", 42 | "builder" 43 | ], 44 | "author": "ZitRo", 45 | "license": "MIT", 46 | "bugs": { 47 | "url": "https://github.com/ZitRos/CacheUMLExplorer/issues" 48 | }, 49 | "homepage": "https://github.com/ZitRos/CacheUMLExplorer" 50 | } 51 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG IMAGE=intersystems/iris:2019.1.0S.111.0 2 | ARG IMAGE=store/intersystems/irishealth:2019.3.0.308.0-community 3 | ARG IMAGE=store/intersystems/iris-community:2019.3.0.309.0 4 | ARG IMAGE=store/intersystems/iris-community:2019.4.0.379.0 5 | ARG IMAGE=store/intersystems/iris-community:2020.1.0.197.0 6 | ARG IMAGE=intersystemsdc/iris-community:2020.1.0.209.0-zpm 7 | ARG IMAGE=intersystemsdc/iris-community:2020.1.0.215.0-zpm 8 | ARG IMAGE=intersystemsdc/iris-community:latest 9 | FROM $IMAGE 10 | 11 | USER irisowner 12 | 13 | WORKDIR /opt/irisapp 14 | 15 | COPY Installer.cls . 16 | COPY src src 17 | COPY build-for-zpm build-for-zpm 18 | COPY irissession.sh . 19 | 20 | USER root 21 | 22 | RUN chmod +x ./irissession.sh 23 | USER irisowner 24 | SHELL ["./irissession.sh"] 25 | 26 | RUN \ 27 | do $SYSTEM.OBJ.Load("Installer.cls", "ck") \ 28 | set sc = ##class(App.Installer).setup() \ 29 | zn "%SYS" \ 30 | write "Creating web application ..." \ 31 | set webName = "/ClassExplorer" \ 32 | set webProperties("DispatchClass") = "ClassExplorer.Router" \ 33 | set webProperties("NameSpace") = "IRISAPP" \ 34 | set webProperties("Enabled") = 1 \ 35 | set webProperties("AutheEnabled") = 32 \ 36 | set sc = ##class(Security.Applications).Create(webName, .webProperties) \ 37 | write sc \ 38 | write "Web application "_webName_" has been created!" 39 | 40 | # bringing the standard shell back 41 | SHELL ["/bin/bash", "-c"] 42 | CMD [ "-l", "/usr/irissys/mgr/messages.log" ] 43 | -------------------------------------------------------------------------------- /src/web/js/UI.js: -------------------------------------------------------------------------------- 1 | /** 2 | * User interface functions. 3 | * @param {CacheClassExplorer} cacheUMLExplorer 4 | * @constructor 5 | */ 6 | var UI = function (cacheUMLExplorer) { 7 | 8 | this.cacheClassExplorer = cacheUMLExplorer; 9 | this.BODY = cacheUMLExplorer.elements.uiBody; 10 | 11 | /** 12 | * @type {HTMLElement} 13 | * @private 14 | */ 15 | this.messageElement = null; 16 | 17 | }; 18 | 19 | /** 20 | * Display hovering message. 21 | * 22 | * @param {string|HTMLElement} content 23 | * @param {boolean} [removeByClick] - Define whether user be able to remove message by clicking on 24 | * it. 25 | */ 26 | UI.prototype.displayMessage = function (content, removeByClick) { 27 | 28 | this.removeMessage(); 29 | 30 | var self = this, 31 | d1 = document.createElement("div"), 32 | d2 = document.createElement("div"), 33 | d3 = document.createElement("div"); 34 | 35 | d1.className = "central message"; 36 | d1.style.opacity = 0; 37 | if (content instanceof HTMLElement) { 38 | d3.appendChild(content); 39 | } else { 40 | d3.innerHTML = content; 41 | } 42 | d2.appendChild(d3); 43 | d1.appendChild(d2); 44 | this.BODY.appendChild(d1); 45 | this.messageElement = d1; 46 | setTimeout(function () { if (d1) d1.style.opacity = 1; }, 25); 47 | if (removeByClick === undefined || removeByClick) d1.addEventListener("click", function () { 48 | self.removeMessage(); 49 | }); 50 | 51 | }; 52 | 53 | UI.prototype.removeMessage = function () { 54 | 55 | if (this.messageElement) { 56 | this.messageElement.parentNode.removeChild(this.messageElement); 57 | this.messageElement = null; 58 | } 59 | 60 | }; -------------------------------------------------------------------------------- /src/web/css/joint.min.css: -------------------------------------------------------------------------------- 1 | /*! JointJS v0.9.6 - JavaScript diagramming library 2015-12-19 2 | 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | .viewport{-webkit-user-select:none;-moz-user-select:none;user-select:none}[magnet=true]:not(.element){cursor:crosshair}[magnet=true]:not(.element):hover{opacity:.7}.element{cursor:move}.element *{user-drag:none}.connection-wrap{fill:none;stroke:#000;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;opacity:0;cursor:move}.connection-wrap:hover{opacity:.4;stroke-opacity:.4}.connection{fill:none;stroke-linejoin:round}.marker-source,.marker-target{vector-effect:non-scaling-stroke}.marker-vertices{opacity:0;cursor:move}.marker-arrowheads{opacity:0;cursor:move;cursor:-webkit-grab;cursor:-moz-grab}.link-tools{opacity:0;cursor:pointer}.link-tools .tool-options{display:none}.link-tools .tool-remove circle{fill:red}.link-tools .tool-remove path{fill:#fff}.link:hover .marker-vertices,.link:hover .marker-arrowheads,.link:hover .link-tools{opacity:1}.marker-vertex{fill:#1ABC9C}.marker-vertex:hover{fill:#34495E;stroke:none}.marker-arrowhead{fill:#1ABC9C}.marker-arrowhead:hover{fill:#F39C12;stroke:none}.marker-vertex-remove{cursor:pointer;opacity:.1;fill:#fff}.marker-vertex-group:hover .marker-vertex-remove{opacity:1}.marker-vertex-remove-area{opacity:.1;cursor:pointer}.marker-vertex-group:hover .marker-vertex-remove-area{opacity:1}.highlighted{opacity:.7}text.highlighted{fill:red}@media screen and (-webkit-min-device-pixel-ratio:0){.highlighted{outline:2px solid red;opacity:initial}}.element .fobj{overflow:hidden}.element .fobj body{background-color:transparent;margin:0}.element .fobj div{text-align:center;vertical-align:middle;display:table-cell;padding:0 5px} -------------------------------------------------------------------------------- /src/web/js/HoverMessage.js: -------------------------------------------------------------------------------- 1 | var HoverMessage = function (text, clickHandler) { 2 | 3 | var self = this; 4 | 5 | this.clickHandler = typeof clickHandler === "function" ? clickHandler : null; 6 | this.element = document.createElement("div"); 7 | this.element.className = "hoverMessage"; 8 | this.element.innerHTML = text; 9 | this.container = document.createElement("div"); 10 | this.container.className = "hoverContainer"; 11 | this.container.appendChild(this.element); 12 | 13 | this.container.addEventListener("mouseout", function (event) { 14 | var e = event.toElement || event.relatedTarget; 15 | if (e && ((function check (e, t) { // if one of the parents is this object 16 | if (e === t) return true; 17 | if (!e.parentNode) return false; 18 | return check(e.parentNode, t); 19 | })(e, this))) return; 20 | self.detach(); 21 | }); 22 | 23 | if (this.clickHandler) { 24 | if (this.container.classList) this.container.classList.add("clickable"); 25 | this.container.addEventListener("click", function () { 26 | if (!lib.getSelection()) self.clickHandler(); 27 | }); 28 | } 29 | 30 | }; 31 | 32 | HoverMessage.prototype.attach = function (screenX, screenY) { 33 | 34 | var e = this.container, w; 35 | 36 | document.body.appendChild(e); 37 | // +1 to width fixes "X.4234" rational part that may appear on SVG 38 | e.style.width = (w = Math.ceil(Math.min(e.offsetWidth, window.innerWidth/2) + 1)) + "px"; 39 | e.style.top = (screenY - e.offsetHeight + 15) + "px"; 40 | e.style.left = Math.min(window.innerWidth - w - 10, screenX - w/2) + "px"; 41 | 42 | }; 43 | 44 | HoverMessage.prototype.detach = function () { 45 | 46 | if (!this.element.parentNode) return; 47 | 48 | this.container.parentNode.removeChild(this.container); 49 | 50 | }; -------------------------------------------------------------------------------- /src/cls/ClassExplorer/Installer.cls: -------------------------------------------------------------------------------- 1 | Class ClassExplorer.Installer Extends %Projection.AbstractProjection 2 | { 3 | 4 | Parameter VERSION = ""; 5 | 6 | Parameter Name = "/ClassExplorer"; 7 | 8 | Parameter DispatchClass = "ClassExplorer.Router"; 9 | 10 | Projection Reference As Installer; 11 | 12 | /// This method is invoked when a class is compiled. 13 | ClassMethod CreateProjection(cls As %String, ByRef params) As %Status 14 | { 15 | set properties("NameSpace") = $Namespace 16 | set properties("Description") = "A WEB application for Cache UML Explorer." 17 | set properties("IsNameSpaceDefault") = $$$NO 18 | set properties("DispatchClass") = ..#DispatchClass 19 | 20 | new $Namespace 21 | set $Namespace = "%SYS" 22 | do ##class(Security.System).GetInstallationSecuritySetting(.security) 23 | if (security="None") { 24 | set properties("AutheEnabled") = $$$AutheUnauthenticated 25 | } else { 26 | set properties("AutheEnabled") = $$$AutheCache 27 | } 28 | 29 | if ('##class(Security.Applications).Exists(..#Name)) { 30 | write !, "Creating WEB application " _ ..#Name _ "..." 31 | set st = ##class(Security.Applications).Create(..#Name, .properties) 32 | write !, "WEB application " _ ..#Name _ " created." 33 | } else { 34 | set st = $$$OK 35 | write !, "WEB application " _ ..#Name _ " already exists, so it is ready to use." 36 | } 37 | quit st 38 | } 39 | 40 | /// This method is invoked when a class is 'uncompiled'. 41 | ClassMethod RemoveProjection(cls As %String, ByRef params, recompile As %Boolean) As %Status 42 | { 43 | new $Namespace 44 | set $Namespace = "%SYS" 45 | set st = $$$OK 46 | if (##class(Security.Applications).Exists(..#Name)) { 47 | write !, "Deleting WEB application " _ ..#Name _"..." 48 | set st = ##class(Security.Applications).Delete(..#Name) 49 | write !, "WEB application " _ ..#Name _ " was successfully removed." 50 | } 51 | quit st 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /src/web/css/interface.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | margin: 0; 3 | padding: 0; 4 | width: 100%; 5 | height: 100%; 6 | overflow: hidden; 7 | background-color: whitesmoke; 8 | font-family: monospace; 9 | } 10 | 11 | .ui-body { 12 | height: 100%; 13 | } 14 | 15 | .ui-sideBlock { 16 | position: relative; 17 | float: left; 18 | width: 200px; 19 | height: 100%; 20 | box-shadow: 0 0 0 1px lightgray; 21 | } 22 | 23 | .ui-mainBlock { 24 | position: relative; 25 | margin-left: 200px; 26 | height: 100%; 27 | } 28 | 29 | .ui-ClassInfo { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | padding: .5em 200px .5em .5em; 34 | font-weight: 600; 35 | font-size: 14pt; 36 | z-index: 1; 37 | box-sizing: border-box; 38 | width: 100%; 39 | } 40 | 41 | .ui-wrapTextOverflow { 42 | overflow: hidden; 43 | text-overflow: ellipsis; 44 | } 45 | 46 | .ui-rightBottomToolBar { 47 | position: absolute; 48 | bottom: 0; 49 | right: 0; 50 | padding: .5em; 51 | z-index: 1; 52 | } 53 | 54 | .ui-leftBottomToolBar { 55 | position: absolute; 56 | bottom: 0; 57 | left: 0; 58 | padding: .5em; 59 | z-index: 1; 60 | } 61 | 62 | .ui-topRightToolBar { 63 | position: absolute; 64 | top: 0; 65 | right: 0; 66 | padding: .5em; 67 | z-index: 1; 68 | } 69 | 70 | #className { 71 | text-shadow: 1px 1px 0 white, -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white; 72 | } 73 | 74 | #subLabel { 75 | position: absolute; 76 | font-size: initial; 77 | font-style: initial; 78 | font-weight: initial; 79 | } 80 | 81 | .central { 82 | position: absolute; 83 | left: 0; 84 | top: 0; 85 | width: 100%; 86 | height: 100%; 87 | display: table; 88 | } 89 | 90 | .central > div { 91 | display: table-cell; 92 | vertical-align: middle; 93 | text-align: center; 94 | } 95 | 96 | .central > div > div { 97 | display: inline-block; 98 | } 99 | 100 | .message { 101 | font-size: 14pt; 102 | background: rgba(245, 245, 245, 0.9); 103 | z-index: 1; 104 | -webkit-transition: all .2s ease; 105 | -moz-transition: all .2s ease; 106 | -o-transition: all .2s ease; 107 | transition: all .2s ease; 108 | } -------------------------------------------------------------------------------- /src/web/css/classView.css: -------------------------------------------------------------------------------- 1 | /* IE fix */ 2 | svg { 3 | overflow: hidden; 4 | } 5 | 6 | #classView { 7 | position: relative; 8 | height: 100%; 9 | } 10 | 11 | #svgContainer { 12 | z-index: 0; 13 | position: relative; 14 | height: 100%; 15 | cursor: -webkit-grab; cursor: -moz-grab; 16 | } 17 | 18 | .uml-class-name-rect { 19 | /*fill: lightgray;*/ 20 | } 21 | 22 | .uml-class-attrs-rect, .uml-class-methods-rect, .uml-class-queries-rect, .uml-class-xdatas-rect { 23 | fill: white; 24 | } 25 | 26 | text { 27 | font-family: monospace; 28 | } 29 | 30 | .centralText { 31 | display: table; 32 | position: absolute; 33 | left: 0; 34 | top: 0; 35 | width: 100%; 36 | height: 100%; 37 | text-align: center; 38 | } 39 | 40 | .centralText > div { 41 | display: table-cell; 42 | vertical-align: middle; 43 | } 44 | 45 | .uml-class-name-text { 46 | cursor: help; 47 | -webkit-transition: all .2s ease; 48 | -moz-transition: all .2s ease; 49 | -o-transition: all .2s ease; 50 | transition: all .2s ease; 51 | } 52 | 53 | .uml-class-name-text:hover { 54 | fill: red; 55 | } 56 | 57 | .uml-class-attrs-label { 58 | font-family: monospace; 59 | font-weight: 900; 60 | fill: rgb(255, 97, 0); 61 | } 62 | 63 | .uml-class-methods-label { 64 | font-family: monospace; 65 | font-weight: 900; 66 | fill: blue; 67 | } 68 | 69 | .uml-class-queries-label { 70 | font-family: monospace; 71 | font-weight: 900; 72 | fill: #0d0; 73 | } 74 | 75 | .uml-class-params-label { 76 | font-family: monospace; 77 | font-weight: 900; 78 | fill: magenta; 79 | } 80 | 81 | .uml-class-xdatas-label { 82 | font-family: monospace; 83 | font-weight: 900; 84 | fill: #0ab; 85 | } 86 | 87 | .line-clickable { 88 | cursor: pointer; 89 | -webkit-transition: all .2s ease; 90 | -moz-transition: all .2s ease; 91 | -o-transition: all .2s ease; 92 | transition: all .2s ease; 93 | } 94 | 95 | .line-clickable:hover { 96 | fill: red; 97 | } 98 | 99 | .line-selected { 100 | fill: red; 101 | text-shadow: 0 1px 3px #CCC; 102 | -webkit-transition: all .2s ease; 103 | -moz-transition: all .2s ease; 104 | -o-transition: all .2s ease; 105 | transition: all .2s ease; 106 | } 107 | 108 | .inlineSearchBlock { 109 | display: inline-block; 110 | vertical-align: bottom; 111 | } 112 | 113 | #diagramSearch { 114 | display: block; 115 | box-sizing: border-box; 116 | border: 1px solid gray; 117 | border-radius: 5px; 118 | width: 200px; 119 | margin: 4px 0 4px 4px; 120 | height: 22px; 121 | } 122 | 123 | .element.highlighted { 124 | outline: 4px solid rgba(255, 0, 0, 0.6); 125 | } 126 | 127 | .element.uml.Class .tool-remove { 128 | display: none; 129 | cursor: pointer; 130 | } 131 | 132 | .element.uml.Class:hover .tool-remove { 133 | display: block; 134 | } 135 | 136 | .marker-source, .marker-target { 137 | vector-effect: none !important; 138 | } 139 | 140 | .connection { 141 | stroke: black; 142 | } -------------------------------------------------------------------------------- /src/web/js/Source.js: -------------------------------------------------------------------------------- 1 | var Source = function (cacheUMLExplorer) { 2 | 3 | // need to figure out path prefix when not at /ClassExplorer 4 | var prefix = ''; 5 | try { 6 | var path = window.location.pathname.split('/'); 7 | for (var i = 1; i < path.length; i++) { 8 | if (path[i]=='ClassExplorer') break; 9 | prefix += '/'+path[i]; 10 | } 11 | } catch (ex) { 12 | // shrug it off 13 | } 14 | 15 | this.URL = window.location.protocol + "//" + window.location.hostname + ":" + 16 | 57772/*build.replace:window.location.port*/ + prefix + "/ClassExplorer"; 17 | 18 | this.cue = cacheUMLExplorer; 19 | 20 | }; 21 | 22 | /** 23 | * Return class tree. 24 | * @param {boolean} includeMapped 25 | * @param {Source~dataCallback} callback 26 | */ 27 | Source.prototype.getClassTree = function (includeMapped, callback) { 28 | 29 | var ns = (this.cue.NAMESPACE ? "?namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""); 30 | 31 | lib.load( 32 | this.URL + "/GetClassTree" 33 | + ns 34 | + (ns ? "&" : "?") + "mapped=" + (includeMapped ? "1" : "0"), 35 | null, 36 | callback 37 | ); 38 | 39 | }; 40 | 41 | /** 42 | * Return namespaces & current namespace. 43 | * @param {Source~dataCallback} callback 44 | */ 45 | Source.prototype.getNamespacesInfo = function (callback) { 46 | 47 | lib.load(this.URL + "/GetAllNamespacesList", null, callback); 48 | 49 | }; 50 | 51 | /** 52 | * Return method data. 53 | * @param {string} className 54 | * @param {string} methodName 55 | * @param {Source~dataCallback} callback 56 | */ 57 | Source.prototype.getMethod = function (className, methodName, callback) { 58 | 59 | lib.load( 60 | this.URL + "/GetMethod?className=" + encodeURIComponent(className) + "&methodName=" 61 | + encodeURIComponent(methodName) 62 | + (this.cue.NAMESPACE ? "&namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""), 63 | null, 64 | callback 65 | ); 66 | 67 | }; 68 | 69 | Source.prototype.saveView = function (packageName, data) { 70 | 71 | lib.load( 72 | this.URL + "/SaveView?name=" + encodeURIComponent(packageName), 73 | data, 74 | function () { console.log("View saved."); } 75 | ); 76 | 77 | }; 78 | 79 | Source.prototype.resetView = function (packageName) { 80 | 81 | lib.load( 82 | this.URL + "/ResetView?name=" + encodeURIComponent(packageName) 83 | ); 84 | 85 | }; 86 | 87 | /** 88 | * Return package view. 89 | * @param {string} packageName 90 | * @param {string} level 91 | * @param {Source~dataCallback} callback 92 | */ 93 | Source.prototype.getPackageView = function (packageName, level, callback) { 94 | 95 | lib.load( 96 | this.URL + "/GetPackageView?name=" + encodeURIComponent(packageName) 97 | + "&level=" + encodeURIComponent(level) 98 | + (this.cue.NAMESPACE ? "&namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""), 99 | null, 100 | callback 101 | ); 102 | 103 | }; 104 | 105 | /** 106 | * Return arbitrary class list view. 107 | * @param {string[]} classList 108 | * @param {string} level 109 | * @param {Source~dataCallback} callback 110 | */ 111 | Source.prototype.getArbitraryView = function (classList, level, callback) { 112 | 113 | lib.load( 114 | this.URL + "/GetArbitraryView?list=" + encodeURIComponent(classList.join(",")) 115 | + "&level=" + encodeURIComponent(level) 116 | + (this.cue.NAMESPACE ? "&namespace=" + encodeURIComponent(this.cue.NAMESPACE) : ""), 117 | null, 118 | callback 119 | ); 120 | 121 | }; 122 | 123 | /** 124 | * This callback handles data received directly from server. 125 | * @callback Source~dataCallback 126 | * @param {null|{error:string}} error 127 | * @param data 128 | */ -------------------------------------------------------------------------------- /src/web/css/treeView.css: -------------------------------------------------------------------------------- 1 | #treeView { 2 | position: relative; 3 | display: block; 4 | font-size: 16px; 5 | height: 100%; 6 | overflow: auto; 7 | box-sizing: border-box; 8 | padding-top: 24px; 9 | } 10 | 11 | .ui-sideSearchBlock { 12 | position: absolute; 13 | top: 0; 14 | left: 0; 15 | width: 100%; 16 | z-index: 1; 17 | } 18 | 19 | .ui-sideSearchBlock input[type=search] { 20 | display: block; 21 | box-sizing: border-box; 22 | border: 1px solid gray; 23 | border-radius: 5px; 24 | width: 196px; 25 | margin: 1px 1px 1px 2px; 26 | height: 22px; 27 | } 28 | 29 | .ui-sideSearchBlock label { 30 | cursor: pointer; 31 | user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | } 35 | 36 | .ui-sideSearchBlock input[type=checkbox] { 37 | vertical-align: middle; 38 | } 39 | 40 | .tv-package-name.minimized + .tv-package-content { 41 | display: none; 42 | } 43 | 44 | .tv-class-name.selected, .tv-package-name.selected { 45 | box-shadow: inset 0 0 2px 2px #ffcc1b; 46 | } 47 | 48 | .tv-class-name, .tv-package-name { 49 | position: relative; 50 | padding: 3px; 51 | cursor: pointer; 52 | border-radius: 5px; 53 | -webkit-transition: all .2s ease; 54 | -moz-transition: all .2s ease; 55 | -o-transition: all .2s ease; 56 | transition: all .2s ease; 57 | -webkit-user-select: none; 58 | -ms-user-select: none; 59 | user-select: none; 60 | } 61 | 62 | .tv-package-name:hover { 63 | background: #ffcc1b; 64 | font-weight: 900; 65 | padding-left: 30px; 66 | } 67 | 68 | .tv-class-name:hover { 69 | background: #ffcc1b; 70 | font-weight: 900; 71 | } 72 | 73 | .tv-class-name > input[type=checkbox] { 74 | vertical-align: top; 75 | } 76 | 77 | .tv-class-name > span { 78 | -webkit-transition: all .2s ease; 79 | -moz-transition: all .2s ease; 80 | -o-transition: all .2s ease; 81 | transition: all .2s ease; 82 | } 83 | 84 | .tv-class-name:hover > span { 85 | padding-left: 10px; 86 | } 87 | 88 | .tv-package-name:hover { 89 | background: #7dcdeb; 90 | } 91 | 92 | .tv-package-name { 93 | position: relative; 94 | padding-left: 20px; 95 | } 96 | 97 | .tv-package-name:before { 98 | content: ""; 99 | box-sizing: border-box; 100 | position: absolute; 101 | left: 2px; 102 | top: 5px; 103 | display: block; 104 | width: 8px; 105 | height: 4px; 106 | background: black; 107 | border: 1px solid gray; 108 | border-radius: 2px 2px 0 2px; 109 | -webkit-transition: all .2s ease; 110 | -moz-transition: all .2s ease; 111 | -o-transition: all .2s ease; 112 | transition: all .2s ease; 113 | } 114 | 115 | .tv-package-name:after { 116 | content: ""; 117 | box-sizing: border-box; 118 | position: absolute; 119 | left: 2px; 120 | top: 8px; 121 | display: block; 122 | width: 16px; 123 | height: 10px; 124 | background: black; 125 | border: 1px solid gray; 126 | border-radius: 0 2px 2px 2px; 127 | -webkit-transition: all .2s ease; 128 | -moz-transition: all .2s ease; 129 | -o-transition: all .2s ease; 130 | transition: all .2s ease; 131 | } 132 | 133 | .tv-package-name:hover:before { 134 | left: 4px; 135 | width: 22px; 136 | } 137 | 138 | .tv-package-name:hover:after { 139 | width: 26px; 140 | } 141 | 142 | .tv-class-name { 143 | position: relative; 144 | white-space: pre; 145 | } 146 | 147 | /*.tv-class-name:after { 148 | content: ""; 149 | box-sizing: border-box; 150 | position: absolute; 151 | left: 6px; 152 | top: 7px; 153 | display: block; 154 | width: 10px; 155 | height: 10px; 156 | background: black; 157 | border: 1px solid gray; 158 | border-radius: 5px 5px 5px 5px; 159 | }*/ 160 | 161 | .tv-package .tv-package-content { 162 | padding-left: 20px; 163 | } 164 | 165 | .tv-rightListIcon { 166 | position: absolute !important; 167 | top: 3px; 168 | opacity: 0; 169 | right: 0; 170 | } 171 | 172 | .tv-package-name:hover .tv-rightListIcon { 173 | opacity: 1; 174 | } -------------------------------------------------------------------------------- /src/cls/ClassExplorer/Router.cls: -------------------------------------------------------------------------------- 1 | /// Cache Class Explorer v 2 | /// REST interface for ClassExplorer 3 | Class ClassExplorer.Router Extends %CSP.REST 4 | { 5 | 6 | XData UrlMap 7 | { 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | } 23 | 24 | /// Method returns whole class tree visible in the current namespace. 25 | ClassMethod GetClassTree() As %Status 26 | { 27 | do ##class(ClassExplorer.ClassView).getClassTree(%request.Get("namespace"), %request.Get("mapped")).%ToJSON(, "o") 28 | return $$$OK 29 | } 30 | 31 | /// Returns classTree by given class name 32 | ClassMethod GetArbitraryView() As %Status 33 | { 34 | set classList = %request.Get("list") 35 | set classData = ##class(ClassView).getArbitraryView(classList, %request.Get("namespace"), %request.Get("level")) 36 | do classData.%ToJSON(, "o") 37 | return $$$OK 38 | } 39 | 40 | /// Saves the view preferences 41 | ClassMethod SaveView() As %Status 42 | { 43 | set name = %request.Get("name") 44 | set content = %request.Content.Read($$$MaxStringLength) // ~ 7mb 45 | set ^ClassExplorer("savedView", ##class(ClassExplorer.ClassView).shortenSavedName(name)) = content 46 | write "{""OK"":true}" 47 | return $$$OK 48 | } 49 | 50 | /// Saves the view preferences 51 | ClassMethod ResetView() As %Status 52 | { 53 | set name = %request.Get("name") 54 | kill ^ClassExplorer("savedView", ##class(ClassExplorer.ClassView).shortenSavedName(name)) 55 | write "{""OK"":true}" 56 | return $$$OK 57 | } 58 | 59 | /// Returns all package class trees by given package name 60 | ClassMethod GetPackageView() As %Status 61 | { 62 | set packageName = %request.Get("name") 63 | set classData = ##class(ClassView).getPackageView(packageName, %request.Get("namespace")) 64 | do classData.%ToJSON(, "o") 65 | return $$$OK 66 | } 67 | 68 | /// Return the list of all namespaces 69 | ClassMethod GetAllNamespacesList() As %Status 70 | { 71 | do ##class(ClassExplorer.ClassView).getAllNamespacesList().%ToJSON(, "o") 72 | return $$$OK 73 | } 74 | 75 | /// Returns method description and code 76 | ClassMethod GetMethod() As %Status 77 | { 78 | set className = %request.Get("className") 79 | set methodName = %request.Get("methodName") 80 | set methodData = ##class(ClassView).getMethod(className, methodName, %request.Get("namespace")) 81 | do methodData.%ToJSON(, "o") 82 | return $$$OK 83 | } 84 | 85 | /// Method returns user application CSS. 86 | ClassMethod GetCss() As %Status 87 | { 88 | #define CompileTime ##Expression("""" _ $zd($h, 11) _ ", "_ $zdt($NOW(0), 2,1) _ " GMT""") 89 | set %response.CharSet = "utf-8" 90 | set %response.ContentType = "text/css" 91 | do %response.SetHeader("Last-Modified", $$$CompileTime) 92 | 93 | if %request.GetCgiEnv("HTTP_IF_MODIFIED_SINCE")=$$$CompileTime { 94 | do ..Http304() 95 | } else { 96 | do ##class(StaticContent).Write("CSS") 97 | } 98 | 99 | return $$$OK 100 | } 101 | 102 | /// Method returns user application JavaScript. 103 | ClassMethod GetJs() As %Status 104 | { 105 | #define CompileTime ##Expression("""" _ $zd($h, 11) _ ", "_ $zdt($NOW(0), 2,1) _ " GMT""") 106 | set %response.CharSet = "utf-8" 107 | set %response.ContentType = "text/javascript" 108 | do %response.SetHeader("Last-Modified", $$$CompileTime) 109 | 110 | if %request.GetCgiEnv("HTTP_IF_MODIFIED_SINCE")=$$$CompileTime { 111 | do ..Http304() 112 | } else { 113 | do ##class(StaticContent).Write("JS") 114 | } 115 | 116 | return $$$OK 117 | } 118 | 119 | /// Method returns user application HTML. 120 | ClassMethod Index() As %Status 121 | { 122 | #define CompileTime ##Expression("""" _ $zd($h, 11) _ ", "_ $zdt($NOW(0), 2,1) _ " GMT""") 123 | do %response.SetHeader("Last-Modified", $$$CompileTime) 124 | 125 | if %request.GetCgiEnv("HTTP_IF_MODIFIED_SINCE")=$$$CompileTime { 126 | do ..Http304() 127 | } else { 128 | do ##class(StaticContent).Write("HTML") 129 | } 130 | 131 | return $$$OK 132 | } 133 | 134 | /// Issue an "304 Not Modified" status 135 | ClassMethod Http304() As %Status 136 | { 137 | Set %response.Status="304 Not Modified" 138 | Quit $$$OK 139 | } 140 | 141 | } 142 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var gulp = require("gulp"), 2 | fs = require("fs"), 3 | clean = require("gulp-clean"), 4 | concat = require("gulp-concat"), 5 | uglify = require("gulp-uglify"), 6 | wrap = require("gulp-wrap"), 7 | stripComments = require("gulp-strip-comments"), 8 | addsrc = require('gulp-add-src'), 9 | minifyCSS = require("gulp-minify-css"), 10 | htmlReplace = require("gulp-html-replace"), 11 | header = require("gulp-header"), 12 | replace = require("gulp-replace"), 13 | postcss = require('gulp-postcss'), 14 | autoprefixer = require('autoprefixer-core'), 15 | pkg = require("./package.json"), 16 | zip = require("gulp-zip"), 17 | rename = require("gulp-rename"), 18 | preprocess = require("gulp-preprocess"); 19 | 20 | var INSTALLER_CLASS_NAME = "ClassExplorer.Installer"; 21 | 22 | var banner = [ 23 | "", 24 | "/*! <%= pkg.name %>", 25 | " ** <%= pkg.description %>", 26 | " ** @author <%= pkg.author %>", 27 | " ** @version <%= pkg.version %>", 28 | " ** @license <%= pkg.license %>", 29 | " ** @see https://github.com/ZitRos/CacheClassExplorer", 30 | " **/", 31 | "" 32 | ].join("\n"), 33 | context = { 34 | context: { 35 | package: pkg 36 | } 37 | }; 38 | 39 | var specialReplace = function () { 40 | return replace(/[^\s]+\/\*build\.replace:(.*)\*\//g, function (part, match) { 41 | var s = match.toString(); 42 | return s.replace(/pkg\.([a-zA-Z]+)/g, function (p,a) { return pkg[a]; }); 43 | }); 44 | }; 45 | 46 | gulp.task("clean", function () { 47 | return gulp.src("build", {read: false}) 48 | .pipe(clean()); 49 | }); 50 | 51 | gulp.task("gatherLibs", ["clean"], function () { 52 | return gulp.src([ 53 | "src/web/jsLib/jquery.min.js", 54 | "src/web/jsLib/lodash.min.js", 55 | "src/web/jsLib/backbone-min.js", 56 | "src/web/jsLib/joint.js", 57 | "src/web/jsLib/joint.shapes.uml.js", 58 | "src/web/jsLib/ImageExporter.js" 59 | ]) 60 | .pipe(uglify({ 61 | output: { 62 | ascii_only: true, 63 | width: 30000, 64 | max_line_len: 30000 65 | }, 66 | preserveComments: "some" 67 | })) 68 | .pipe(addsrc.append([ 69 | "src/web/jsLib/joint.layout.DirectedGraph.min.js" 70 | ])) 71 | .pipe(stripComments({ safe: true })) 72 | .pipe(concat("index.js")) 73 | .pipe(replace(/ /g, "\\x0B")) 74 | .pipe(replace(/\x1b/g, "\\x1B")) 75 | .pipe(gulp.dest("build/web/js/")); 76 | }); 77 | 78 | gulp.task("gatherScripts", ["clean", "gatherLibs"], function () { 79 | return gulp.src("src/web/js/*.js") 80 | .pipe(concat("index.js")) 81 | .pipe(specialReplace()) 82 | .pipe(wrap("CacheClassExplorer = (function(){<%= contents %> return CacheClassExplorer;}());")) 83 | .pipe(uglify({ 84 | output: { 85 | ascii_only: true, 86 | width: 30000, 87 | max_line_len: 30000 88 | }, 89 | preserveComments: "some" 90 | })) 91 | .pipe(header(banner, { pkg: pkg })) 92 | .pipe(addsrc.prepend("build/web/js/index.js")) 93 | .pipe(concat("index.js")) 94 | .pipe(replace(/\x1b/g, "\\x1B")) 95 | .pipe(gulp.dest("build/web/js/")); 96 | }); 97 | 98 | gulp.task("gatherCSS", ["clean"], function () { 99 | return gulp.src("src/web/css/*.css") 100 | .pipe(concat("index.css")) 101 | .pipe(postcss([ autoprefixer({ browsers: ["last 3 version"] }) ])) 102 | .pipe(minifyCSS({ keepSpecialComments: 0 })) 103 | .pipe(gulp.dest("build/web/css/")); 104 | }); 105 | 106 | gulp.task("addHTMLFile", ["clean"], function () { 107 | return gulp.src("src/web/index.html") 108 | .pipe(htmlReplace({ 109 | "css": "css/index.css", 110 | "js": "js/index.js" 111 | })) 112 | .pipe(gulp.dest("build/web/")); 113 | }); 114 | 115 | gulp.task("copyLICENSE", ["clean"], function () { 116 | return gulp.src("LICENSE") 117 | .pipe(gulp.dest("build/")); 118 | }); 119 | 120 | gulp.task("copyREADME", ["clean"], function () { 121 | return gulp.src("readme.md") 122 | .pipe(gulp.dest("build/")); 123 | }); 124 | 125 | gulp.task("pre-cls", ["clean"], function () { 126 | return gulp.src(["src/cls/**/*.cls"]) 127 | .pipe(rename(function (f) { 128 | f.basename = (f.dirname === "." ? "" : f.dirname + ".") + f.basename; 129 | f.dirname = "."; 130 | if (f.basename !== INSTALLER_CLASS_NAME) 131 | context.context.compileAfter += 132 | (context.context.compileAfter ? "," : "") + f.basename; 133 | })) 134 | .pipe(gulp.dest("build/cls/")); 135 | }); 136 | 137 | gulp.task("cls", ["pre-cls", "copyLICENSE", "copyREADME", "addHTMLFile", "gatherScripts", 138 | "gatherCSS"], function () { 139 | return gulp.src(["build/cls/**/*.cls"]) 140 | .pipe(preprocess(context)) 141 | .pipe(gulp.dest("build/cls")); 142 | }); 143 | 144 | gulp.task("zipRelease", function () { 145 | return gulp.src(["build/**/*", "!build/web/**/*.*", "!build/cls/**/*.*"]) 146 | .pipe(zip("CacheClassExplorer-v" + pkg["version"] + ".zip", { 147 | comment: "Cache UML explorer v" + pkg["version"] + " by Nikita Savchenko\n\n" + 148 | "+ Cache folder holds XML file to import to InterSystems Cache.\n\n" + 149 | "For further information about installation and information, check README.md file.\n\n" 150 | + "See https://github.com/intersystems-ru/UMLExplorer" 151 | })) 152 | .pipe(gulp.dest("build")); 153 | }); 154 | 155 | gulp.task("desktop", ["default"], function () { 156 | return gulp.src("build/Cache/*") 157 | .pipe(gulp.dest("C:/Users/ZitRo/Desktop")); 158 | }); 159 | 160 | gulp.task("default", ["cls"]); -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ObjectScript Class Explorer 2 | [![Quality Gate Status](https://community.objectscriptquality.com/api/project_badges/measure?project=intersystems_iris_community%2FClassExplorer&metric=alert_status)](https://community.objectscriptquality.com/dashboard?id=intersystems_iris_community%2FClassExplorer) 3 | 4 | An UML class explorer for InterSystems products: IRIS, Ensemble, HealthShare, Caché. Read more about class explorer [on InterSystems Developer Community](https://community.intersystems.com/post/cach%C3%A9-class-explorer-%E2%80%94-exploring-cach%C3%A9-uml-notation). 5 | 6 | ##### Key features 7 | 8 | + Build class diagrams for arbitrary list of classes; 9 | + Build diagrams for whole package or subpackage; 10 | + Edit diagrams after build; 11 | + Switch between strict UML notation and designed view; 12 | + Export diagrams as an image; 13 | + See Class methods, properties, parameters, SQL queries, xDatas and more; 14 | + See any keywords and related information by hovering over everything with pointer; 15 | + Check which fields are connected by hovering over link; 16 | + View methods code, sql queries and xDatas with syntax highlighting; 17 | + Zoom in and out; 18 | + Search on diagram or in class tree; 19 | + Explore! 20 | 21 | ## Screenshots 22 | 23 | ![Demo](https://cloud.githubusercontent.com/assets/4989256/14227108/bad7a9ae-f8fd-11e5-85c6-06e746d281be.png) 24 | 25 | ## Docker Quick Demo 26 | 27 | 1. Clone the repository and run `docker-compose up -d --build` to bring up ClassExplorer to a docker container. 28 | 2. Check `localhost:52773/ClassExplorer/` in a little while - it should have the app already. 29 | 30 | ## Full Installation from XML File 31 | 32 | To install latest Caché Class Explorer, you just need to import ClassExplorer package. Download the 33 | archive from [latest releases](https://github.com/intersystems-ru/UMLExplorer/releases), and then import 34 | Cache/CacheClassExplorer-vX.X.X.xml file. 35 | 36 | ###### Web application 37 | 38 | Note that importing ClassExplorer.WebAppInstaller class will also create a /ClassExplorer application. 39 | If you want to create WEB application manually, please, do not import this class. Anyway, 40 | importing this class requires %SYS permission. 41 | 42 | ## Usage 43 | 44 | Visit [server domain and port]/ClassExplorer/ (slash at end required) to enter 45 | application. 46 | 47 | ## Development 48 | 49 | ### Local 50 | 51 | To build project, you need [NodeJS](https://nodejs.org) platform to be installed. Then, clone source 52 | code and run npm install from the root of the project. This will install all necessary 53 | modules from NPM for the project. 54 | 55 | After that and each next time just run npm run gulp command from the project root. 56 | This will generate build directory, where you will find XML file ready to import. 57 | 58 | One can import/export the built source to the local Cache/URIS instance (see `import.bat`): 59 | 60 | ``` 61 | ./import.bat 62 | ``` 63 | 64 | This will bring `ClassExplorer-v*.*.*.xml` to the `build` directory, which you can then package with `npm run zip`. 65 | 66 | ### Using docker 67 | 68 | :warning: The following is a rather naive way of building the web app for ZPM packaging using an [nvm docker script](https://github.com/nvm-sh/nvm?tab=readme-ov-file#installing-in-docker), starting the image as a daemon, and then manually copying the files back out. 69 | 70 | ```Shell 71 | docker build -f ./Dockerfile-build-npm -t build-npm . 72 | docker run --rm -d --name build-npm build-npm /bin/bash -c 'while true; do sleep 30; done' 73 | docker cp build-npm:/opt/irisapp/build/cls/ClassExplorer.ClassView.cls ./build-for-zpm/ClassExplorer/ClassView.cls 74 | docker cp build-npm:/opt/irisapp/build/cls/ClassExplorer.Router.cls ./build-for-zpm/ClassExplorer/Router.cls 75 | docker cp build-npm:/opt/irisapp/build/cls/ClassExplorer.StaticContent.cls ./build-for-zpm/ClassExplorer/StaticContent.cls 76 | docker stop build-npm 77 | ``` 78 | 79 | ### ZPM 80 | 81 | [ZPM](https://github.com/intersystems-community/zpm) is the package manager for InterSystems products. Currently, 82 | the release pipeline for it is manual. The ZPM release should happen once the package is uploaded 83 | to [InterSystems OpenExchange](https://openexchange.intersystems.com/) with the "ZPM" option checked in. This 84 | should grab **manually moved** files from `build-for-zpm/ClassExplorer` directory for the release. 85 | 86 | Locally, one can run and test the application using Docker: 87 | 88 | 0. Before each release, **manually** patch the version in the `module.xml` file. 89 | 1. Run `docker-compose up -d --build` in the repository root to bring up ClassExplorer to a docker container. 90 | 2. Check `localhost:52773/ClassExplorer/` in a little while - it should have the app already. 91 | 3. Run `docker-compose exec iris iris session iris` and then type `zn "IRISAPP" zpm` to start ZPM session in the "IRISAPP" namespace. 92 | 4. Type `load /irisdev/app` to test whether ZPM can parse the repository root. 93 | 5. Type `classexplorer package` to try to compile the package. It should say something like `Module package generated: /tmp/dirymgtBA/classexplorer-1.20.0.tgz`. 94 | 6. Configure the test registry to publish the package `repo -n registry -r -url https://test.pm.community.intersystems.com/registry/ -user test -pass test` (type `search` to see the registries list). 95 | 7. Finally publish the package `classexplorer publish`. 96 | 8. Further steps to test it: [https://community.intersystems.com/post/testing-packages-zpm](https://community.intersystems.com/post/testing-packages-zpm) 97 | 98 | ## Related Discussion 99 | 100 | See the detailed description and discussion [in this article](https://community.intersystems.com/node/407056). 101 | Have a look at [InterSystems Developer Community](community.intersystems.com) to learn about InterSystems technology, sharing solutions and staying up-to-date on the latest developments. 102 | 103 | ## License 104 | 105 | [MIT](LICENSE) © [Nikita Savchenko](https://nikita.tk) 106 | -------------------------------------------------------------------------------- /src/web/jsLib/ImageExporter.js: -------------------------------------------------------------------------------- 1 | /* 2 | * @see https://github.com/NYTimes/svg-crowbar - A bit of used code from here, thanks to it's author. 3 | */ 4 | var enableSVGDownload = function (classView) { 5 | 6 | var doctype = ''; 7 | 8 | window.URL = (window.URL || window.webkitURL); 9 | 10 | var prefix = { 11 | xmlns: "http://www.w3.org/2000/xmlns/", 12 | xlink: "http://www.w3.org/1999/xlink", 13 | svg: "http://www.w3.org/2000/svg" 14 | }; 15 | 16 | function getSources(doc, emptySvgDeclarationComputed) { 17 | 18 | var svgInfo = [], 19 | svgs = doc.querySelectorAll("#svgContainer > svg"); 20 | 21 | [].forEach.call(svgs, function (svg) { 22 | 23 | var par = svg.parentNode; 24 | svg = svg.cloneNode(true); 25 | svg.style.position = "fixed"; 26 | svg.style.left = 0; 27 | svg.style.top = 0; 28 | par.appendChild(svg); 29 | var gGroup = svg.childNodes[0]; 30 | 31 | svg.setAttribute("version", "1.1"); 32 | 33 | // removing attributes so they aren't doubled up 34 | svg.removeAttribute("xmlns"); 35 | svg.removeAttribute("xlink"); 36 | 37 | // These are needed for the svg 38 | if (!svg.hasAttributeNS(prefix.xmlns, "xmlns")) { 39 | svg.setAttributeNS(prefix.xmlns, "xmlns", prefix.svg); 40 | } 41 | 42 | if (!svg.hasAttributeNS(prefix.xmlns, "xmlns:xlink")) { 43 | svg.setAttributeNS(prefix.xmlns, "xmlns:xlink", prefix.xlink); 44 | } 45 | 46 | svg.style.zIndex = 0; 47 | gGroup.setAttribute("transform", ""); 48 | var gRect = gGroup.getBoundingClientRect(); 49 | gGroup.setAttribute("transform", "translate("+(-gRect.left)+","+(-gRect.top)+")"); 50 | svg.setAttribute("width", gGroup.getBBox().width); 51 | svg.setAttribute("height", gGroup.getBBox().height); 52 | 53 | setInlineStyles(svg, emptySvgDeclarationComputed); 54 | 55 | var source = (new XMLSerializer()).serializeToString(svg); 56 | var rect = svg.getBoundingClientRect(); 57 | svgInfo.push({ 58 | top: rect.top, 59 | left: rect.left, 60 | width: rect.width, 61 | height: rect.height, 62 | class: svg.getAttribute("class"), 63 | id: svg.getAttribute("id"), 64 | childElementCount: svg.childElementCount, 65 | source: [doctype + source] 66 | }); 67 | 68 | par.removeChild(svg); 69 | 70 | }); 71 | return svgInfo; 72 | } 73 | 74 | document.getElementById("button.downloadSVG").addEventListener("click", function () { 75 | 76 | var emptySvg = document.createElementNS(prefix.svg, 'svg'); 77 | document.body.appendChild(emptySvg); 78 | var emptySvgDeclarationComputed = getComputedStyle(emptySvg); 79 | var source = getSources(document, emptySvgDeclarationComputed)[0]; 80 | var filename = (classView || {}).SELECTED_NAME || "classDiagram"; 81 | var img = new Image(); 82 | var url = window.URL.createObjectURL(new Blob(source.source, { "type" : 'image/svg+xml;charset=utf-8' })); 83 | var canvas = document.createElement("canvas"); 84 | var ctx = canvas.getContext("2d"); 85 | canvas.setAttribute("width", source.width); 86 | canvas.setAttribute("height", source.height); 87 | document.body.appendChild(canvas); 88 | 89 | img.onload = function () { 90 | ctx.drawImage(img, 0, 0); 91 | try { 92 | var a = document.createElement("a"); 93 | a.setAttribute("download", filename + ".png"); 94 | var dataURL = canvas.toDataURL("image/png"); 95 | a.setAttribute("href", dataURL/*url*/); 96 | document.body.appendChild(a); 97 | a.click(); 98 | } catch (e) { 99 | alert("This browser does not allow usage of canvas.toDataURL function. Please," 100 | + " use different browser to download the image."); 101 | } 102 | setTimeout(function () { 103 | a.parentNode.removeChild(a); 104 | document.body.removeChild(emptySvg); 105 | canvas.parentNode.removeChild(canvas); 106 | window.URL.revokeObjectURL(url); 107 | }, 10); 108 | }; 109 | 110 | img.src = url; 111 | 112 | }); 113 | 114 | function setInlineStyles(svg, emptySvgDeclarationComputed) { 115 | 116 | function explicitlySetStyle (element) { 117 | var cSSStyleDeclarationComputed = getComputedStyle(element); 118 | var i, len, key, value; 119 | var computedStyleStr = ""; 120 | for (i=0, len=cSSStyleDeclarationComputed.length; i b.name ? 1 : -1; 283 | }); 284 | 285 | for (i in arr) { 286 | element = arr[i]; 287 | if (rec = append( 288 | rootElement, 289 | element.name, 290 | element.isPackage, 291 | path.join("."), 292 | level 293 | )) { 294 | build(rec, element.val, path.concat([getRealName(element.name)]), level + 1); 295 | } 296 | } 297 | 298 | }; 299 | 300 | build(this.container, treeObject, [], 0); 301 | 302 | if (!doNotChangeRoot) this.treeObject = treeObject; 303 | 304 | }; 305 | 306 | ClassTree.prototype.init = function () { 307 | this.update(); 308 | }; -------------------------------------------------------------------------------- /src/web/js/CacheClassExplorer.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Class diagram visualization tool for InterSystems products. 3 | * @author ZitRo 4 | * @see http://zitros.tk 5 | * @param {HTMLElement} treeViewContainer 6 | * @param {HTMLElement} classViewContainer 7 | * @constructor 8 | */ 9 | var CacheClassExplorer = function (treeViewContainer, classViewContainer) { 10 | 11 | var id = function (e) { return document.getElementById(e); }; 12 | 13 | /** 14 | * This shows if the diagram is in active view. If not, this mean an orthodox diagram and it 15 | * should not interact with UI at all. 16 | * @type {boolean} 17 | */ 18 | this.PRIMARY = !!treeViewContainer; 19 | 20 | this.elements = { 21 | favicon: id("favicon"), 22 | uiBody: id("ui-body"), 23 | className: id("className"), 24 | treeViewContainer: treeViewContainer, 25 | classViewContainer: classViewContainer, 26 | zoomInButton: id("button.zoomIn"), 27 | zoomOutButton: id("button.zoomOut"), 28 | zoomNormalButton: id("button.zoomNormal"), 29 | showSettingsButton: id("button.showSettings"), 30 | helpButton: id("button.showHelp"), 31 | infoButton: id("button.showInfo"), 32 | saveViewButton: id("button.saveView"), 33 | saveViewIcon: id("saveViewIcon"), 34 | methodCodeView: id("methodCodeView"), 35 | closeMethodCodeView: id("closeMethodCodeView"), 36 | methodLabel: id("methodLabel"), 37 | methodCode: id("methodCode"), 38 | classView: id("classView"), 39 | svgContainer: id("svgContainer"), 40 | methodDescription: id("methodDescription"), 41 | methodViewBounds: id("methodViewBounds"), 42 | namespaces: id("namespaces"), 43 | classTreeSearch: id("classTreeSearch"), 44 | searchBlock: id("searchBlock"), 45 | diagramSearch: id("diagramSearch"), 46 | diagramSearchButton: id("button.diagramSearch"), 47 | settingsView: id("settingsView"), 48 | closeSettings: id("closeSettings"), 49 | helpView: id("helpView"), 50 | subLabel: id("subLabel"), 51 | closeHelp: id("closeHelp"), 52 | settingsExtraText: id("settingsExtraText"), 53 | showMappedCheckbox: id("showMappedCheckbox"), 54 | settings: { 55 | showDataTypesOnDiagram: id("setting.showDataTypesOnDiagram"), 56 | showClassIcons: id("setting.showClassIcons"), 57 | showPropertyIcons: id("setting.showPropertyIcons"), 58 | showParameters: id("setting.showParameters"), 59 | showProperties: id("setting.showProperties"), 60 | showMethods: id("setting.showMethods"), 61 | showQueries: id("setting.showQueries"), 62 | showXDatas: id("setting.showXDatas"), 63 | dependencyLevel: id("setting.dependencyLevel") 64 | } 65 | }; 66 | 67 | var getSettingsValue = function (name, defaultVal) { 68 | var item = localStorage.getItem(name); 69 | try { 70 | return item ? JSON.parse(item).data : defaultVal; 71 | } catch (e) { // non-parsable data 72 | return defaultVal; 73 | } 74 | }; 75 | 76 | // note: this.elements is required to be modified with the same name as settings keys 77 | this.settings = { 78 | showDataTypesOnDiagram: getSettingsValue("showDataTypesOnDiagram"), 79 | showClassIcons: getSettingsValue("showClassIcons", true), 80 | showPropertyIcons: getSettingsValue("showPropertyIcons", true), 81 | showParameters: getSettingsValue("showParameters", true), 82 | showProperties: getSettingsValue("showProperties", true), 83 | showMethods: getSettingsValue("showMethods", true), 84 | showQueries: getSettingsValue("showQueries", true), 85 | showXDatas: getSettingsValue("showXDatas", true), 86 | dependencyLevel: getSettingsValue("dependencyLevel", "") 87 | }; 88 | 89 | this.UI = new UI(this); 90 | if (this.PRIMARY) { 91 | this.source = new Source(this); 92 | this.classTree = new ClassTree(this, treeViewContainer); 93 | } 94 | this.classView = new ClassView(this, classViewContainer); 95 | this.NAMESPACE = null; 96 | this.HELP_INITIALIZED = false; 97 | 98 | if (this.PRIMARY) { 99 | this.initSettings(); 100 | this.init(); 101 | } 102 | 103 | }; 104 | 105 | CacheClassExplorer.prototype.initSettings = function () { 106 | 107 | var self = this, 108 | TEXT_CHANGED = "Please, re-render diagram to make changes apply."; 109 | 110 | for (var st in this.elements.settings) { 111 | 112 | var element = this.elements.settings[st]; 113 | 114 | // dropdown ("select") support is not predicted 115 | element[element.type === "checkbox" ? "checked" : "value"] = this.settings[st]; 116 | 117 | element.addEventListener("change", (function (st, element) { return function () { 118 | self.elements.settingsExtraText.innerHTML = TEXT_CHANGED; 119 | localStorage.setItem(st, self.settings[st] = JSON.stringify({ 120 | data: element[element.type === "checkbox" ? "checked" : "value"] 121 | })); 122 | }; })(st, element)); 123 | 124 | } 125 | 126 | }; 127 | 128 | /** 129 | * Render namespaces. 130 | * @param nsData 131 | */ 132 | CacheClassExplorer.prototype.updateNamespaces = function (nsData) { 133 | 134 | var ns, e; 135 | 136 | this.NAMESPACE = nsData["currentNamespace"]; 137 | this.elements.namespaces.textContent = ""; 138 | 139 | for (ns in nsData.namespaces || {}) { 140 | e = document.createElement("option"); 141 | e.setAttribute("value", ns); 142 | e.textContent = ns; 143 | if (ns === nsData.currentNamespace) e.setAttribute("selected", ""); 144 | this.elements.namespaces.appendChild(e); 145 | } 146 | 147 | }; 148 | 149 | /** 150 | * @param {string} namespace 151 | */ 152 | CacheClassExplorer.prototype.setNamespace = function (namespace) { 153 | 154 | this.NAMESPACE = namespace; 155 | this.classTree.setSelectedClassList([]); 156 | 157 | this.classTree.update(); 158 | 159 | this.updateNamespace(); 160 | this.updateURL(); 161 | 162 | }; 163 | 164 | /** 165 | * Updates namespace UI. 166 | */ 167 | CacheClassExplorer.prototype.updateNamespace = function () { 168 | 169 | this.elements.namespaces.value = this.NAMESPACE; 170 | 171 | }; 172 | 173 | /** 174 | * Sets the subLabel on the diagram. 175 | * @param {string} [text] 176 | * @param {string} [style] 177 | */ 178 | CacheClassExplorer.prototype.setSubLabel = function (text, style) { 179 | 180 | if (!text) { 181 | this.elements.subLabel.textContent = ""; 182 | this.elements.subLabel.setAttribute("style", ""); 183 | return; 184 | } 185 | 186 | this.elements.subLabel.innerHTML = text; 187 | if (style) 188 | this.elements.subLabel.setAttribute("style", style); 189 | 190 | }; 191 | 192 | CacheClassExplorer.prototype.updateURL = function () { 193 | 194 | var obj = { 195 | name: this.classTree.SELECTED_NAME 196 | }; 197 | 198 | if (this.classTree.SELECTED_TYPE !== null) 199 | obj.type = this.classTree.SELECTED_TYPE; 200 | if (this.classTree.SELECTED_LEVEL !== null) 201 | obj.level = this.classTree.SELECTED_LEVEL; 202 | 203 | if (this.NAMESPACE) 204 | obj["namespace"] = this.NAMESPACE; 205 | 206 | location.hash = JSON.stringify(obj); 207 | 208 | }; 209 | 210 | CacheClassExplorer.prototype.restoreFromURL = function () { 211 | 212 | var hash = (location.hash || "").substr(1), 213 | obj; 214 | 215 | try { obj = JSON.parse(decodeURIComponent(hash)); } catch (e) { console.error(e); obj = {}; } 216 | 217 | if (obj.level) 218 | this.classTree.SELECTED_LEVEL = obj.level; 219 | if (obj.namespace) { 220 | this.NAMESPACE = obj.namespace; 221 | this.updateNamespace(); 222 | } 223 | 224 | if (obj.type === "class") { // left for older Class Explorer versions support 225 | this.classView.loadClasses([obj.name], true); 226 | } else if (obj.type === "package") { 227 | this.classView.loadPackage(obj.name, true); 228 | } else if (obj.type === "arbitrary") { 229 | try { 230 | this.classView.loadClasses(obj.name.split(","), true); 231 | } catch (e) { 232 | console.error("Unable to parse class list " + obj.name); 233 | } 234 | } else { 235 | this.classView.renderInfoGraphic(); 236 | } 237 | 238 | return obj; 239 | 240 | }; 241 | 242 | CacheClassExplorer.prototype.initHelp = function () { 243 | 244 | if (this.HELP_INITIALIZED) return; 245 | this.HELP_INITIALIZED = true; 246 | 247 | var cont2 = [].slice.call(document.querySelectorAll("#helpView *[name=icon]")); 248 | 249 | for (var i in cont2) { 250 | var ico = lib.image[cont2[i].textContent]; 251 | if (ico) { 252 | cont2[i].innerHTML = "" 253 | } 254 | } 255 | 256 | }; 257 | 258 | CacheClassExplorer.prototype.init = function () { 259 | 260 | var self = this, 261 | restored; 262 | 263 | this.elements.favicon.href = lib.image.binoculars; 264 | 265 | restored = this.restoreFromURL(); 266 | this.classTree.showLoader(); 267 | this.classTree.init(); 268 | this.source.getNamespacesInfo(function (err, data) { 269 | if (restored && restored.namespace) data.currentNamespace = restored.namespace; 270 | if (!err) self.updateNamespaces(data); 271 | }); 272 | 273 | this.elements.infoButton.addEventListener("click", function () { 274 | self.UI.displayMessage( 275 | "Caché Class explorer v" 276 | + "[NOT-BUILT]"/*build.replace:"pkg.version"*/ 277 | + "
for InterSystems Caché" 278 | + "
By Nikita Savchenko" 279 | + "
" 280 | + "Project page / Bug tracker" 282 | + "
Article on " 284 | + "Developer Community

Enjoy!" 285 | ); 286 | }); 287 | this.elements.namespaces.addEventListener("change", function (e) { 288 | var el = e.target || e.srcElement, 289 | ns = el.options[el.selectedIndex].value; 290 | if (ns !== self.NAMESPACE) { 291 | self.setNamespace(ns); 292 | } 293 | }); 294 | this.elements.helpButton.addEventListener("click", function () { 295 | self.initHelp(); 296 | self.elements.helpView.classList.add("active"); 297 | }); 298 | this.elements.closeHelp.addEventListener("click", function () { 299 | self.elements.helpView.classList.remove("active"); 300 | }); 301 | this.elements.showSettingsButton.addEventListener("click", function () { 302 | self.elements.settingsView.classList.add("active"); 303 | }); 304 | this.elements.closeSettings.addEventListener("click", function () { 305 | self.elements.settingsExtraText.textContent = ""; 306 | self.elements.settingsView.classList.remove("active"); 307 | }); 308 | 309 | enableSVGDownload(this.classTree); 310 | 311 | // default icon 312 | this.elements.saveViewIcon.src = lib.image.pin; 313 | this.elements.saveViewButton.addEventListener("click", function () { 314 | self.classView.switchViewSave(); 315 | if (self.classView.viewSaving) { 316 | self.classView.saveView(); 317 | } else { 318 | self.source.resetView( self.NAMESPACE + ":" + self.classView.CURRENT_RENDER_NAME ); 319 | } 320 | }); 321 | 322 | }; -------------------------------------------------------------------------------- /src/cls/ClassExplorer/ClassView.cls: -------------------------------------------------------------------------------- 1 | /// Cache Class Explorer v 2 | /// Class contains methods that return structured classes/packages data. 3 | Class ClassExplorer.ClassView 4 | { 5 | 6 | /// Return list with all namespaces 7 | ClassMethod getAllNamespacesList() As %ZEN.proxyObject 8 | { 9 | set resp = ##class(%ZEN.proxyObject).%New() 10 | set resp.namespaces = ##class(%ZEN.proxyObject).%New() 11 | set resp.currentNamespace = $NAMESPACE 12 | set result = ##class(%ResultSet).%New("%SYS.Namespace:List") 13 | do result.Execute() 14 | while (result.Next()) { 15 | set ns = ##class(%ZEN.proxyObject).%New() 16 | set ns.remote = result.Get("Remote") 17 | set ns.status = result.Get("Status") 18 | do resp.namespaces.%DispatchSetProperty(result.Get("Nsp"), ns) 19 | } 20 | return resp 21 | } 22 | 23 | /// Returns structured class tree with all classes available in current namespace 24 | ClassMethod getClassTree(namespace As %String, showMapped = 0) As %ZEN.proxyObject 25 | { 26 | zn:$GET(namespace)'="" namespace 27 | set resp = ##class(%ZEN.proxyObject).%New() 28 | 29 | set showSystem = (namespace="%SYS") 30 | do $System.OBJ.GetClassList(.classes, "/system="_showSystem_" /percent=" 31 | _showSystem_" /mapped=" _ showMapped) 32 | set objects = ##class(%Library.ArrayOfObjects).%New() 33 | set lastParts = $LB() 34 | 35 | set level = 1 36 | do objects.SetAt(resp, level) 37 | 38 | set name = $order(classes("")) 39 | while (name '= "") { 40 | //if ($EXTRACT(name, 1, 1) = "%") && ($NAMESPACE '= "%SYS") { continue } 41 | set parts = $LISTFROMSTRING(name, ".") 42 | set level = 1 43 | while ((level < $LISTLENGTH(parts)) && ($LISTGET(lastParts, level) = ("/"_$LISTGET(parts, level)))) { 44 | set level = level + 1 45 | } 46 | set resp = objects.GetAt(level) 47 | if (resp="") { 48 | set resp = ##class(%ZEN.proxyObject).%New() 49 | do objects.GetAt(level - 1).%DispatchSetProperty("/" _ $LISTGET(parts, level - 1), resp) 50 | do objects.SetAt(resp, level) 51 | } 52 | while ($LISTLENGTH(parts) > level) { 53 | set level = level + 1 54 | set resp = ##class(%ZEN.proxyObject).%New() 55 | do objects.GetAt(level - 1).%DispatchSetProperty("/" _ $LISTGET(parts, level - 1), resp) 56 | do objects.SetAt(resp, level) 57 | } 58 | if ($LISTLENGTH(parts) = level) { 59 | do resp.%DispatchSetProperty($LISTGET(parts, level), 0) 60 | } 61 | set lastParts = parts 62 | for i=1:1:$LISTLENGTH(lastParts)-1 { 63 | set $LIST(lastParts, i) = "/"_$LISTGET(lastParts, i) 64 | } 65 | set name = $order(classes(name)) 66 | } 67 | 68 | quit objects.GetAt(1) 69 | } 70 | 71 | /// Return structured data about class. 72 | ClassMethod fillClassData(oData As %ZEN.proxyObject, className As %String, level As %String = "", currLevel As %Integer = 0) As %ZEN.proxyObject [ Private ] 73 | { 74 | if ((level'="") && (+currLevel>+level)) { quit "" } 75 | set currLevel = $increment(currLevel) 76 | set classDefinition = ##class(%Dictionary.ClassDefinition).%OpenId(className) 77 | set compiledClassDefinition = ##class(%Dictionary.CompiledClass).%OpenId(className) 78 | if (classDefinition = "") || (oData.classes.%DispatchGetProperty(classDefinition.Name) '= "") quit "" 79 | 80 | set oClass = ##class(%ZEN.proxyObject).%New() 81 | do oData.classes.%DispatchSetProperty(classDefinition.Name, oClass) // prevent from recursive setup 82 | set package = $LISTTOSTRING($LIST($LISTFROMSTRING(classDefinition.Name, "."), 1, *-1),".") 83 | set oProperties = ##class(%ZEN.proxyObject).%New() 84 | set oQueries = ##class(%ZEN.proxyObject).%New() 85 | set oIndices = ##class(%ZEN.proxyObject).%New() 86 | set oXDatas = ##class(%ZEN.proxyObject).%New() 87 | 88 | set oClass.isDataType = classDefinition.ClientDataTypeIsDefined() 89 | set oClass.isOdbcType = classDefinition.OdbcTypeIsDefined() 90 | set oClass.isSoapBindingStyle = classDefinition.SoapBindingStyleIsDefined() 91 | set oClass.isSoapBodyUse = classDefinition.SoapBodyUseIsDefined() 92 | set oClass.isSqlCategory = classDefinition.SqlCategoryIsDefined() 93 | 94 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.ClassDefinition") 95 | for j=1:1:props.Properties.Count() { 96 | set pname = props.Properties.GetAt(j).Name 97 | try { 98 | set:((pname '= "parent") 99 | && ('props.Properties.GetAt(j).Private) 100 | && ('$IsObject($PROPERTY(classDefinition, pname)))) $PROPERTY(oClass, pname) = $PROPERTY(classDefinition, pname) 101 | } catch (e) { 102 | // Skip private properties that InterSystems may add in any future versions 103 | } 104 | } 105 | if (oClass.TimeChanged) { set oClass.TimeChanged = $zdatetime(oClass.TimeChanged) } 106 | if (oClass.TimeCreated) { set oClass.TimeCreated = $zdatetime(oClass.TimeCreated) } 107 | if ((compiledClassDefinition '= "") && (compiledClassDefinition.ClassType '= "")) { 108 | set oClass.ClassType = compiledClassDefinition.ClassType // set class type from all inherited classes 109 | } 110 | 111 | set oClass.Super = "" // do not quit with super at this moment 112 | if (oData.restrictPackage) && ('..inPackage(oData.basePackageName, package)) quit oClass 113 | set oClass.Super = ..correctInheritance(oData, classDefinition, package) // now expand super names 114 | 115 | // prebuild a list of properties that are part of an FK 116 | kill propToFK 117 | for i = 1:1:classDefinition.ForeignKeys.Count() { 118 | set fk = classDefinition.ForeignKeys.GetAt(i) 119 | for j = 1:1:$l(fk.Properties,",") { 120 | set prop = $piece(fk.Properties,",") 121 | set propToFK(prop) = fk 122 | } 123 | } 124 | 125 | set oClass.properties = oProperties 126 | set count = classDefinition.Properties.Count() 127 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.PropertyDefinition") 128 | for i=1:1:count { 129 | set oProp = ##class(%ZEN.proxyObject).%New() 130 | set p = classDefinition.Properties.GetAt(i) 131 | do oProperties.%DispatchSetProperty(p.Name, oProp) 132 | for j=1:1:props.Properties.Count() { 133 | set pname = props.Properties.GetAt(j).Name 134 | try { 135 | set:(pname '= "parent") $PROPERTY(oProp, pname) = $PROPERTY(p, pname) 136 | } catch (e) { 137 | // Skip private properties that InterSystems may add in any future versions 138 | } 139 | } 140 | 141 | set pType = p.Type 142 | 143 | // check if this property has an outgoing FK reference 144 | if $d(propToFK(p.Name), fk) { 145 | // if so, override target type and class relationship traits 146 | set pType = fk.ReferencedClass, 147 | oProp.Relationship = 1, 148 | oProp.Cardinality = "one", 149 | oProp.Inverse = "[foreign key]" 150 | } 151 | 152 | if (..classExists(package _ "." _ pType)) { 153 | set oProp.Type = package _ "." _ pType 154 | do ..fillClassData(oData, package _ "." _ pType, level, currLevel) 155 | } elseif (..classExists(..extendClassFromType(pType))) { 156 | set oProp.Type = ..extendClassFromType(pType) 157 | do ..fillClassData(oData, ..extendClassFromType(pType), level, currLevel) 158 | } else { 159 | set oProp.Type = ..extendClassFromType(pType) 160 | } 161 | } 162 | 163 | set oMethods = ##class(%ZEN.proxyObject).%New() 164 | set oClass.methods = oMethods 165 | set count = classDefinition.Methods.Count() 166 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.MethodDefinition") 167 | for i=1:1:count { 168 | set oMeth = ##class(%ZEN.proxyObject).%New() 169 | set met = classDefinition.Methods.GetAt(i) 170 | do oMethods.%DispatchSetProperty(met.Name, oMeth) 171 | for j=1:1:props.Properties.Count() { 172 | set pname = props.Properties.GetAt(j).Name 173 | try { 174 | set:((pname '= "parent") && (pname '= "Implementation")) $PROPERTY(oMeth, pname) = $PROPERTY(met, pname) 175 | } catch (e) { 176 | // Skip private properties that InterSystems may add in any future versions 177 | } 178 | } 179 | } 180 | 181 | set oParameters = ##class(%ZEN.proxyObject).%New() 182 | set oClass.parameters = oParameters 183 | set count = classDefinition.Parameters.Count() 184 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.ParameterDefinition") 185 | for i=1:1:count { 186 | set oPar = ##class(%ZEN.proxyObject).%New() 187 | set p = classDefinition.Parameters.GetAt(i) 188 | for j=1:1:props.Properties.Count() { 189 | set pname = props.Properties.GetAt(j).Name 190 | try { 191 | set:(pname '= "parent") $PROPERTY(oPar, pname) = $PROPERTY(p, pname) 192 | } catch (e) { 193 | // Skip private properties that InterSystems may add in any future versions 194 | } 195 | } 196 | do oParameters.%DispatchSetProperty(p.Name, oPar) 197 | } 198 | 199 | #dim q as %Dictionary.QueryDefinition 200 | set oClass.queries = oQueries 201 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.QueryDefinition") 202 | for i=1:1:classDefinition.Queries.Count() { 203 | set oProp = ##class(%ZEN.proxyObject).%New() 204 | set q = classDefinition.Queries.GetAt(i) 205 | for j=1:1:props.Properties.Count() { 206 | set pname = props.Properties.GetAt(j).Name 207 | try { 208 | set:(pname '= "parent") $PROPERTY(oProp, pname) = $PROPERTY(q, pname) 209 | } catch (e) { 210 | // Skip private properties that InterSystems may add in any future versions 211 | } 212 | } 213 | do oQueries.%DispatchSetProperty(q.Name, oProp) 214 | } 215 | 216 | #dim xd as %Dictionary.XDataDefinition 217 | set oClass.xdatas = oXDatas 218 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.XDataDefinition") 219 | for i=1:1:classDefinition.XDatas.Count() { 220 | set oProp = ##class(%ZEN.proxyObject).%New() 221 | set xd = classDefinition.XDatas.GetAt(i) 222 | for j=1:1:props.Properties.Count() { 223 | set pname = props.Properties.GetAt(j).Name 224 | try { 225 | set:((pname '= "parent") && (pname '= "Object")) $PROPERTY(oProp, pname) = $PROPERTY(xd, pname) 226 | } catch (e) { 227 | // Skip private properties that InterSystems may add in any future versions 228 | } 229 | } 230 | do oXDatas.%DispatchSetProperty(xd.Name, oProp) 231 | } 232 | 233 | #dim ind as %Dictionary.IndexDefinition 234 | set oClass.indices = oIndices 235 | set props = ##class(%Dictionary.ClassDefinition).%OpenId("%Dictionary.IndexDefinition") 236 | for i=1:1:classDefinition.Indices.Count() { 237 | set oProp = ##class(%ZEN.proxyObject).%New() 238 | set ind = classDefinition.Indices.GetAt(i) 239 | for j=1:1:props.Properties.Count() { 240 | set pname = props.Properties.GetAt(j).Name 241 | try { 242 | set:(pname '= "parent") $PROPERTY(oProp, pname) = $PROPERTY(ind, pname) 243 | } catch (e) { 244 | // Skip private properties that InterSystems may add in any future versions 245 | } 246 | } 247 | do oIndices.%DispatchSetProperty(ind.Name, oProp) 248 | } 249 | 250 | do ..collectInheritance(oData, oClass.Super, level, currLevel) 251 | 252 | quit oClass 253 | } 254 | 255 | /// Return method data. 256 | ClassMethod getMethod(className As %String, methodName As %String, namespace As %String) As %ZEN.proxyObject 257 | { 258 | zn:$GET(namespace)'="" namespace 259 | set oMeth = ##class(%ZEN.proxyObject).%New() 260 | set met = ##class(%Dictionary.MethodDefinition).%OpenId(className _ "||" _ methodName) 261 | if (met = "") { set oMeth.error = 1 quit oMeth } 262 | 263 | set oMeth.description = met.Description 264 | set oMeth.arguments = met.FormalSpec 265 | set oMeth.returns = met.ReturnType 266 | set oMeth.code = "" 267 | do { 268 | set chars = met.Implementation.Read() 269 | set oMeth.code = oMeth.code _ chars 270 | } while (chars) 271 | 272 | quit oMeth 273 | } 274 | 275 | /// Returns if packageName is in basePackageName. 276 | ClassMethod inPackage(basePackageName As %String, packageName As %String) As %Boolean [ Private ] 277 | { 278 | set pack = $LISTFROMSTRING(packageName, ".") 279 | set list = $LISTFROMSTRING(basePackageName, ".") 280 | set OK = 1 281 | for i=1:1:$LISTLENGTH(list) { 282 | if ($LISTGET(list, i) = $LISTGET(pack, i)) { 283 | set OK = 1 284 | } else { 285 | set OK = 0 286 | quit 287 | } 288 | } 289 | quit OK 290 | } 291 | 292 | /// Wrap registered types to class names 293 | ClassMethod extendClassFromType(typeName As %String) As %String [ Private ] 294 | { 295 | return $CASE(typeName, 296 | "%Boolean": "%Library.Boolean", 297 | "%String": "%Library.String", 298 | "%Integer": "%Library.Integer", 299 | "%DataType": "%Library.DataType", 300 | "%Status": "%Library.Status", 301 | "%CacheString": "%Library.CacheString", 302 | "%Persistent": "%Library.Persistent", 303 | :typeName) 304 | } 305 | 306 | /// Return extended inheritance names 307 | ClassMethod correctInheritance(oData As %ZEN.proxyObject, baseClassDefinition As %Dictionary.ClassDefinition, basePack As %String) As %String [ Private ] 308 | { 309 | set superParts = $LISTFROMSTRING(baseClassDefinition.Super, ",") 310 | for i=1:1:$LISTLENGTH(superParts) { 311 | set className = $LISTGET(superParts, i) 312 | 313 | // try to find class with base package, if not successfull - try to add class as it is 314 | if (..classExists(basePack_"."_className)) { 315 | set clsName = basePack_"."_className 316 | } else { 317 | set clsName = ..extendClassFromType(className) 318 | } 319 | 320 | set $LIST(superParts, i) = clsName 321 | } 322 | quit $LISTTOSTRING(superParts) 323 | } 324 | 325 | /// Fill inheritance data 326 | /// Returns new (correct) super 327 | ClassMethod collectInheritance(oData As %ZEN.proxyObject, super As %String, level As %String = "", currLevel As %Integer = 0) As %Status [ Private ] 328 | { 329 | set superParts = $LISTFROMSTRING(super, ",") 330 | for i=1:1:$LISTLENGTH(superParts) { 331 | do ..fillClassData(oData, $LISTGET(superParts, i), level, currLevel) 332 | } 333 | quit $$$OK 334 | } 335 | 336 | /// Setup basic output data object 337 | ClassMethod getBaseOData(packageName As %String, baseNamespace As %String, savedName As %String) As %ZEN.proxyObject [ Private ] 338 | { 339 | set oData = ##class(%ZEN.proxyObject).%New() 340 | set oData.basePackageName = packageName 341 | set oData.restrictPackage = 1 // expand classes only in base package 342 | set oData.classes = ##class(%ZEN.proxyObject).%New() 343 | 344 | set ns = $namespace 345 | zn baseNamespace 346 | set shortName = ..shortenSavedName(ns_":"_savedName) 347 | if $get(^ClassExplorer("savedView", shortName)) '= "" { 348 | set oData.savedView = $get(^ClassExplorer("savedView", shortName)) 349 | } 350 | zn ns 351 | 352 | quit oData 353 | } 354 | 355 | /// Shorten the saved name to avoid SUBSCRIPT errors when saving into ^ClassExplorer 356 | ClassMethod shortenSavedName(savedName as %String) As %String [ Internal ] 357 | { 358 | // this is not watertight, but should be sufficient for this use case 359 | if $l(savedName)>100 { 360 | return $e(savedName, 1, 100) _ $zcrc(savedName, 7) 361 | } 362 | return savedName 363 | } 364 | 365 | /// Returns if class with given name exists. 366 | ClassMethod classExists(className As %String) As %Boolean 367 | { 368 | quit ##class(%Dictionary.ClassDefinition).%OpenId(className) '= "" 369 | } 370 | 371 | /// Returns structured package data 372 | ClassMethod getPackageView(rootPackageName As %String, namespace As %String) As %ZEN.proxyObject 373 | { 374 | set baseNamespace = $namespace 375 | zn:$GET(namespace)'="" namespace 376 | set oData = ..getBaseOData(rootPackageName, baseNamespace, "PACKAGE:"_rootPackageName) 377 | set classes = ##class(%ResultSet).%New("%Dictionary.ClassDefinition:Summary") 378 | do classes.Execute() 379 | set listLen = $LISTLENGTH($LISTFROMSTRING(rootPackageName, ".")) // bottom level of package to extract 380 | 381 | while (classes.Next()) { 382 | set className = classes.Data("Name") 383 | set packageName = $LISTTOSTRING($LIST($LISTFROMSTRING(className, "."), 1, listLen), ".") 384 | if (packageName = rootPackageName) { 385 | do ..fillClassData(oData, className) 386 | } 387 | } 388 | 389 | quit oData 390 | } 391 | 392 | ClassMethod getArbitraryView(classList As %String, namespace As %String, level As %String = "") 393 | { 394 | set baseNamespace = $namespace 395 | zn:$GET(namespace)'="" namespace 396 | set list = $LISTFROMSTRING(classList, ",") 397 | 398 | set basePackages = $LB() 399 | set count = 0 400 | for i=1:1:$LISTLENGTH(list) { 401 | set packName = $PIECE($LISTGET(list, i), ".", 1) 402 | if ($LISTFIND(basePackages, packName) = 0) { 403 | set $LIST(basePackages, count + 1) = packName 404 | set count = count + 1 405 | } 406 | } 407 | 408 | set oData = ..getBaseOData($LISTTOSTRING(basePackages, ",", 1), baseNamespace, "ARBITRARY:"_classList) 409 | 410 | for i=1:1:$LISTLENGTH(list) { 411 | do ..fillClassData(oData, $LISTGET(list, i), level) 412 | } 413 | 414 | quit oData 415 | } 416 | 417 | } 418 | -------------------------------------------------------------------------------- /src/web/jsLib/joint.shapes.uml.js: -------------------------------------------------------------------------------- 1 | /* JointJS v0.9.3 - JavaScript diagramming library 2015-02-03 2 | 3 | 4 | This Source Code Form is subject to the terms of the Mozilla Public 5 | License, v. 2.0. If a copy of the MPL was not distributed with this 6 | file, You can obtain one at http://mozilla.org/MPL/2.0/. 7 | */ 8 | /* 9 | * Modified by ZitRo 10 | */ 11 | joint.shapes.uml = {}; 12 | 13 | joint.shapes.uml.Class = joint.shapes.basic.Generic.extend({ 14 | 15 | markup: [ 16 | '', 17 | '', 18 | '', 19 | '', 20 | 'Parameters', 21 | '', 22 | 'Properties', 23 | '', 24 | 'Methods', 25 | '', 26 | 'Queries', 27 | '', 28 | 'xDatas', 29 | '', 30 | '', 31 | '', 32 | '', 33 | '', 34 | '', 35 | '', 36 | 'Remove class', 37 | '' 38 | ].join(''), 39 | 40 | HEAD_EMPTY_LINES: 0, // controls number of empty lines in header 41 | 42 | defaults: joint.util.deepSupplement({ 43 | 44 | type: 'uml.Class', 45 | 46 | MIN_WIDTH: 100, 47 | size: { width: 0, height: 300 }, 48 | 49 | attrs: { 50 | rect: { 'width': 0 }, 51 | 52 | '.uml-class-name-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': 'rgb(177, 205, 255)' }, 53 | '.uml-class-params-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': 'white' }, 54 | '.uml-class-attrs-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, 55 | '.uml-class-methods-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, 56 | '.uml-class-queries-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, 57 | '.uml-class-xdatas-rect': { 'stroke': 'black', 'stroke-width': 1, 'fill': '#2980b9' }, 58 | 59 | '.uml-class-name-text': { 60 | 'ref': '.uml-class-name-rect', 'ref-y': .5, 'ref-x': .5, 'text-anchor': 'middle', 'y-alignment': 'middle', 'font-weight': 'bold', 61 | 'fill': 'black', 'font-size': 12 62 | }, 63 | '.uml-class-params-text': { 64 | 'ref': '.uml-class-params-rect', 'ref-y': 5, 'ref-x': 5, 65 | 'fill': 'black', 'font-size': 12 66 | }, 67 | '.uml-class-attrs-text': { 68 | 'ref': '.uml-class-attrs-rect', 'ref-y': 5, 'ref-x': 5, 69 | 'fill': 'black', 'font-size': 12 70 | }, 71 | '.uml-class-methods-text': { 72 | 'ref': '.uml-class-methods-rect', 'ref-y': 5, 'ref-x': 5, 73 | 'fill': 'black', 'font-size': 12 74 | }, 75 | '.uml-class-queries-text': { 76 | 'ref': '.uml-class-queries-rect', 'ref-y': 5, 'ref-x': 5, 77 | 'fill': 'black', 'font-size': 12 78 | }, 79 | '.uml-class-xdatas-text': { 80 | 'ref': '.uml-class-xdatas-rect', 'ref-y': 5, 'ref-x': 5, 81 | 'fill': 'black', 'font-size': 12 82 | }, 83 | '.uml-class-attrs-label': { 84 | ref: '.uml-class-attrs-label', fill: "black", 'font-size': 10, 85 | xPos: -56 86 | }, 87 | '.uml-class-methods-label': { 88 | ref: '.uml-class-methods-label', fill: "black", 'font-size': 10 89 | }, 90 | '.uml-class-queries-label': { 91 | ref: '.uml-class-queries-label', fill: "black", 'font-size': 10 92 | }, 93 | '.uml-class-params-label': { 94 | ref: '.uml-class-methods-label', fill: "black", 'font-size': 10 95 | }, 96 | '.uml-class-xdatas-label': { 97 | ref: '.uml-class-xdatas-label', fill: "black", 'font-size': 10 98 | } 99 | }, 100 | 101 | name: [], 102 | params: [], 103 | attributes: [], 104 | methods: [], 105 | queries: [], 106 | xdatas: [], 107 | classSigns: [] 108 | 109 | }, joint.shapes.basic.Generic.prototype.defaults), 110 | 111 | initialize: function () { 112 | 113 | var o, 114 | rects = [ 115 | { type: 'name', text: this.getClassName() }, 116 | { type: 'params', text: (o = this.get('params')||[]) , o: (o.forEach(function(e){e._BLOCK="parameters"}) && o) }, 117 | { type: 'attrs', text: (o = this.get('attributes')||[]), o: (o.forEach(function(e){e._BLOCK="properties"}) && o) }, 118 | { type: 'methods', text: (o = this.get('methods')||[]) , o: (o.forEach(function(e){e._BLOCK="methods"}) && o) }, 119 | { type: 'queries', text: (o = this.get('queries')||[]) , o: (o.forEach(function(e){e._BLOCK="queries"}) && o) }, 120 | { type: 'xdatas', text: (o = this.get('xdatas')||[]) , o: (o.forEach(function(e){e._BLOCK="xdatas"}) && o) } 121 | ], 122 | self = this, 123 | classSigns = this.get('classSigns'), 124 | CLASS_TYPE = this.get('classType'), 125 | SYMBOL_12_WIDTH = this.get('SYMBOL_12_WIDTH') || 6.6, 126 | i, j, blockWidth, left = 3, top = 3, w, positions = [], sign; 127 | 128 | // set color head according to class type 129 | var headColor; 130 | switch (CLASS_TYPE) { 131 | case "persistent": headColor = "rgb(255,219,170)"; break; // light orange 132 | case "serial": headColor = "rgb(252,255,149)"; break; // light yellow 133 | //case "Registered": headColor = "rgb(192,255,170)"; break; // light green 134 | case "datatype": headColor = "rgb(193,250,255)"; break; // light blue 135 | case "stream": headColor = "rgb(246,188,255)"; break; // light magenta 136 | case "view": headColor = "rgb(255,188,188)"; break; // light red 137 | case "index": headColor = "rgb(228,228,228)"; break; // light gray 138 | } 139 | if (headColor) this.attributes.attrs[".uml-class-name-rect"].fill = headColor; 140 | 141 | var subLabelWidth = function (sign) { // object 142 | return sign.text.length * SYMBOL_12_WIDTH + (sign.icon ? 13 : 0) 143 | }; 144 | 145 | // preserve space for sub-labels 146 | w = 0; for (i in classSigns) { 147 | w += subLabelWidth(classSigns[i]); 148 | i = 1; 149 | } 150 | 151 | this.defaults.size.width = Math.max(this.defaults.MIN_WIDTH, Math.min(w, 250)); 152 | _.each(rects, function (rect) { 153 | rect.text.forEach(function (s) { 154 | var t = s.text.length*SYMBOL_12_WIDTH + 8 + (s.icons ? s.icons.length*10 + 2 : 0); 155 | if (t > self.defaults.size.width) { 156 | self.defaults.size.width = t; 157 | } 158 | }); 159 | }); 160 | 161 | blockWidth = this.defaults.size.width; 162 | 163 | if (classSigns.length) this.HEAD_EMPTY_LINES = 1; 164 | 165 | // centering algorithm - first, remember position without centering 166 | j = 0; 167 | for (i in classSigns) { 168 | w = classSigns[i].text.length*SYMBOL_12_WIDTH + (classSigns[i].icon ? 13 : 0); 169 | if (left + w - 3 > blockWidth) { top += 12; left = 3; this.HEAD_EMPTY_LINES++; j++; } 170 | if (!positions[j]) positions[j] = []; 171 | positions[j].push({ top: top, left: left, o: classSigns[i] }); 172 | left += w + 3; 173 | } 174 | 175 | // then draw on position with computed seek by X to center content 176 | for (i = 0; i < positions.length; i++) { // repeat positions and draw signs 177 | w = (blockWidth - (sign = positions[i][positions[i].length - 1]).left - subLabelWidth(sign.o)) / 2; 178 | for (j = 0; j < positions[i].length; j++) { 179 | sign = positions[i][j]; 180 | this.markup += '' + 181 | (sign.o.icon ? '' : '') + '' + sign.o.text + 185 | ''; 186 | } 187 | } 188 | 189 | this.on('change:name change:attributes change:methods', function () { 190 | this.updateRectangles(); 191 | this.trigger('uml-update'); 192 | }, this); 193 | 194 | this.updateRectangles(); 195 | 196 | joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); 197 | 198 | }, 199 | 200 | getClassName: function () { 201 | var n = this.get('name'); 202 | return n instanceof Array ? n : [{ text: n }]; 203 | }, 204 | 205 | updateRectangles: function () { 206 | 207 | var attrs = this.get('attrs'), 208 | self = this, 209 | SYMBOL_12_WIDTH = this.get('SYMBOL_12_WIDTH') || 6.6; 210 | 211 | var rects = [ 212 | { type: 'name', text: this.getClassName() }, 213 | { type: 'params', text: this.get('params') }, 214 | { type: 'attrs', text: this.get('attributes') }, 215 | { type: 'methods', text: this.get('methods') }, 216 | { type: 'queries', text: this.get('queries') }, 217 | { type: 'xdatas', text: this.get('xdatas') } 218 | ]; 219 | 220 | var offsetY = 0; 221 | 222 | var dp = self.get("directProps") || {}, 223 | nameClickHandler = dp.nameClickHandler; 224 | 225 | _.each(rects, function(rect) { 226 | 227 | var lines = _.isArray(rect.text) ? rect.text : [{ text: rect.text }]; 228 | if (rect.type === "name") { 229 | if (self.HEAD_EMPTY_LINES) lines.unshift(""); 230 | for (var i = 0; i < self.HEAD_EMPTY_LINES; i++) lines.unshift({ text: "" }); 231 | } 232 | 233 | var rectHeight = lines.length * 12 + (lines.length ? 10 : 0), 234 | rectText = attrs['.uml-class-' + rect.type + '-text'], 235 | rectRect = attrs['.uml-class-' + rect.type + '-rect'], 236 | rectLabel = attrs['.uml-class-' + rect.type + '-label']; 237 | 238 | rectText.text = lines; 239 | 240 | if (nameClickHandler) { 241 | if (rect.type === "name") { 242 | rectText.clickHandler = nameClickHandler; 243 | } 244 | } 245 | rectRect.transform = 'translate(0,'+ offsetY + ')'; 246 | if (rectLabel) { 247 | if (lines.length) { 248 | rectText.paddingTop = "17px"; rectHeight += 5; 249 | rectLabel.transform = 'translate(' + (2) + ','+ (offsetY + 9) + ')'; 250 | } else { 251 | rectLabel.display = "none"; 252 | } 253 | } 254 | rectRect.height = rectHeight; 255 | offsetY += rectHeight; 256 | 257 | }); 258 | 259 | this.attributes.size.height = offsetY; 260 | this.attributes.size.width = this.defaults.size.width; // max width assign 261 | this.attributes.attrs.rect.width = this.defaults.size.width; 262 | 263 | } 264 | 265 | }); 266 | 267 | joint.shapes.uml.ClassView = joint.dia.ElementView.extend({ 268 | 269 | initialize: function() { 270 | 271 | joint.dia.ElementView.prototype.initialize.apply(this, arguments); 272 | 273 | this.listenTo(this.model, 'uml-update', function() { 274 | this.update(); 275 | this.resize(); 276 | }); 277 | } 278 | }); 279 | 280 | joint.shapes.uml.Abstract = joint.shapes.uml.Class.extend({ 281 | 282 | defaults: joint.util.deepSupplement({ 283 | type: 'uml.Abstract', 284 | attrs: { 285 | '.uml-class-name-rect': { fill : '#e74c3c' }, 286 | '.uml-class-params-rect': { fill : '#c0392b' }, 287 | '.uml-class-attrs-rect': { fill : '#c0392b' }, 288 | '.uml-class-methods-rect': { fill : '#c0392b' } 289 | } 290 | }, joint.shapes.uml.Class.prototype.defaults), 291 | 292 | getClassName: function() { 293 | return ['<>', this.get('name')]; 294 | } 295 | 296 | }); 297 | joint.shapes.uml.AbstractView = joint.shapes.uml.ClassView; 298 | 299 | joint.shapes.uml.Interface = joint.shapes.uml.Class.extend({ 300 | 301 | defaults: joint.util.deepSupplement({ 302 | type: 'uml.Interface', 303 | attrs: { 304 | '.uml-class-name-rect': { fill : '#f1c40f' }, 305 | '.uml-class-attrs-rect': { fill : '#f39c12' }, 306 | '.uml-class-methods-rect': { fill : '#f39c12' } 307 | } 308 | }, joint.shapes.uml.Class.prototype.defaults), 309 | 310 | getClassName: function() { 311 | return ['<>', this.get('name')]; 312 | } 313 | 314 | }); 315 | joint.shapes.uml.InterfaceView = joint.shapes.uml.ClassView; 316 | 317 | joint.shapes.uml.Generalization = joint.dia.Link.extend({ 318 | defaults: { 319 | type: 'uml.Generalization', 320 | attrs: { '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }} 321 | } 322 | }); 323 | 324 | joint.shapes.uml.Implementation = joint.dia.Link.extend({ 325 | defaults: { 326 | type: 'uml.Implementation', 327 | attrs: { 328 | '.marker-target': { d: 'M 20 0 L 0 10 L 20 20 z', fill: 'white' }, 329 | '.connection': { 'stroke-dasharray': '3,3' } 330 | } 331 | } 332 | }); 333 | 334 | joint.shapes.uml.Aggregation = joint.dia.Link.extend({ 335 | defaults: { 336 | type: 'uml.Aggregation', 337 | attrs: { '.marker-target': { d: 'M 20 10 L 10 15 L 0 10 L 10 5 z', fill: 'white' }} 338 | } 339 | }); 340 | 341 | joint.shapes.uml.Composition = joint.dia.Link.extend({ 342 | defaults: { 343 | type: 'uml.Composition', 344 | attrs: { '.marker-target': { d: 'M 20 10 L 10 15 L 0 10 L 10 5 z', fill: 'black' }} 345 | } 346 | }); 347 | 348 | joint.shapes.uml.Association = joint.dia.Link.extend({ 349 | defaults: { 350 | type: 'uml.Association', 351 | attrs: { 352 | '.marker-target': { 353 | d: 'M 15 0 L 0 7.5 L 15 15 M 0 7.5 L 15 7.5', 354 | fill: 'none' 355 | }, 356 | '.connection': { stroke: "gray" } 357 | } 358 | } 359 | }); 360 | 361 | // Statechart 362 | 363 | joint.shapes.uml.State = joint.shapes.basic.Generic.extend({ 364 | 365 | markup: [ 366 | '', 367 | '', 368 | '', 369 | '', 370 | '', 371 | '', 372 | '', 373 | '' 374 | ].join(''), 375 | 376 | defaults: joint.util.deepSupplement({ 377 | 378 | type: 'uml.State', 379 | 380 | attrs: { 381 | '.uml-state-body': { 382 | 'width': 200, 'height': 200, 'rx': 10, 'ry': 10, 383 | 'fill': '#ecf0f1', 'stroke': '#bdc3c7', 'stroke-width': 3 384 | }, 385 | '.uml-state-separator': { 386 | 'stroke': '#bdc3c7', 'stroke-width': 2 387 | }, 388 | '.uml-state-name': { 389 | 'ref': '.uml-state-body', 'ref-x': .5, 'ref-y': 5, 'text-anchor': 'middle', 390 | 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 391 | }, 392 | '.uml-state-events': { 393 | 'ref': '.uml-state-separator', 'ref-x': 5, 'ref-y': 5, 394 | 'fill': '#000000', 'font-family': 'Courier New', 'font-size': 14 395 | } 396 | }, 397 | 398 | name: 'State', 399 | events: [] 400 | 401 | }, joint.shapes.basic.Generic.prototype.defaults), 402 | 403 | initialize: function() { 404 | 405 | this.on({ 406 | 'change:name': this.updateName, 407 | 'change:events': this.updateEvents, 408 | 'change:size': this.updatePath 409 | }, this); 410 | 411 | this.updateName(); 412 | this.updateEvents(); 413 | this.updatePath(); 414 | 415 | joint.shapes.basic.Generic.prototype.initialize.apply(this, arguments); 416 | }, 417 | 418 | updateName: function() { 419 | this.attr('.uml-state-name/text', this.get('name')); 420 | }, 421 | 422 | updateEvents: function() { 423 | this.attr('.uml-state-events/text', this.get('events').join('\n')); 424 | }, 425 | 426 | updatePath: function() { 427 | 428 | var d = 'M 0 20 L ' + this.get('size').width + ' 20'; 429 | 430 | // We are using `silent: true` here because updatePath() is meant to be called 431 | // on resize and there's no need to to update the element twice (`change:size` 432 | // triggers also an update). 433 | this.attr('.uml-state-separator/d', d, { silent: true }); 434 | } 435 | 436 | }); 437 | 438 | joint.shapes.uml.StartState = joint.shapes.basic.Circle.extend({ 439 | 440 | defaults: joint.util.deepSupplement({ 441 | 442 | type: 'uml.StartState', 443 | attrs: { circle: { 'fill': '#34495e', 'stroke': '#2c3e50', 'stroke-width': 2, 'rx': 1 }} 444 | 445 | }, joint.shapes.basic.Circle.prototype.defaults) 446 | 447 | }); 448 | 449 | joint.shapes.uml.EndState = joint.shapes.basic.Generic.extend({ 450 | 451 | markup: '', 452 | 453 | defaults: joint.util.deepSupplement({ 454 | 455 | type: 'uml.EndState', 456 | size: { width: 20, height: 20 }, 457 | attrs: { 458 | 'circle.outer': { 459 | transform: 'translate(10, 10)', 460 | r: 10, 461 | fill: '#ffffff', 462 | stroke: '#2c3e50' 463 | }, 464 | 465 | 'circle.inner': { 466 | transform: 'translate(10, 10)', 467 | r: 6, 468 | fill: '#34495e' 469 | } 470 | } 471 | 472 | }, joint.shapes.basic.Generic.prototype.defaults) 473 | 474 | }); 475 | 476 | joint.shapes.uml.Transition = joint.dia.Link.extend({ 477 | defaults: { 478 | type: 'uml.Transition', 479 | attrs: { 480 | '.marker-target': { d: 'M 10 0 L 0 5 L 10 10 z', fill: '#34495e', stroke: '#2c3e50' }, 481 | '.connection': { stroke: '#2c3e50' } 482 | } 483 | } 484 | }); -------------------------------------------------------------------------------- /src/web/jsLib/backbone-min.js: -------------------------------------------------------------------------------- 1 | (function(t){var e=typeof self=="object"&&self.self==self&&self||typeof global=="object"&&global.global==global&&global;if(typeof define==="function"&&define.amd){define(["underscore","jquery","exports"],function(i,r,s){e.Backbone=t(e,s,i,r)})}else if(typeof exports!=="undefined"){var i=require("underscore"),r;try{r=require("jquery")}catch(s){}t(e,exports,i,r)}else{e.Backbone=t(e,{},e._,e.jQuery||e.Zepto||e.ender||e.$)}})(function(t,e,i,r){var s=t.Backbone;var n=[].slice;e.VERSION="1.2.1";e.$=r;e.noConflict=function(){t.Backbone=s;return this};e.emulateHTTP=false;e.emulateJSON=false;var a=function(t,e,r){switch(t){case 1:return function(){return i[e](this[r])};case 2:return function(t){return i[e](this[r],t)};case 3:return function(t,s){return i[e](this[r],t,s)};case 4:return function(t,s,n){return i[e](this[r],t,s,n)};default:return function(){var t=n.call(arguments);t.unshift(this[r]);return i[e].apply(i,t)}}};var o=function(t,e,r){i.each(e,function(e,s){if(i[s])t.prototype[s]=a(e,s,r)})};var h=e.Events={};var u=/\s+/;var l=function(t,e,r,s,n){var a=0,o;if(r&&typeof r==="object"){if(s!==void 0&&"context"in n&&n.context===void 0)n.context=s;for(o=i.keys(r);a 2 | 3 | 4 | 5 | Cache Class Explorer 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
39 |
40 |
41 |
42 | 46 |
47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 |
55 |
56 |
57 |
58 | Welcome! 59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 69 |
70 |
71 |
72 |
73 |
74 |
75 | 76 |
77 | 78 |
79 |
80 |
81 |
82 | 83 |
84 |
85 |
86 |
87 |
88 |
89 |

90 |
91 |
92 |
93 | 94 |
95 |
96 | 97 |
98 |
99 |
100 |
101 | 102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |

113 | Settings 114 |

115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |
Show data type classes on diagram
Visualize class keywords (turn off for standard UML notation)
Visualize property keywords (turn off for standard UML notation)
Display block with class parameters
Display block with class properties
Display block with class methods
Display block with class queries
Display block with class xDatas
Dependency level of classes (leave blank for full structure)
164 |

165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |

Caché Class Explorer Help

173 |
174 |
175 |

176 | This info graphics below shows all the basics of designations on the diagram. 177 |

178 |
179 |
180 | 181 | 182 | 183 | 184 | 185 | 186 | 189 | 192 | 195 | 198 | 201 | 204 | 207 | 208 | 209 |

Class Types

187 | Registered 188 | 190 | Persistent 191 | 193 | Serial 194 | 196 | DataType 197 | 199 | Index 200 | 202 | View 203 | 205 | Stream 206 |
210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 220 | 221 | 222 | 223 | 226 | 227 | 228 | 229 | 232 | 233 | 234 | 235 | 238 | 239 | 240 |

Connection Types

Class Mention 218 | 219 |
One - to - Many 224 | 225 |
Parent - to - Child 230 | 231 |
Inheritance 236 | 237 |
241 |
242 |
243 |

Icons Description

244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 |
crystalBallAbstract
blueFlagFinal
moleculeCubeCrossNot a Procedure Block
ghostHidden
minusPrivate
plusPublic
keyRedUnique Key
keyGreenPrimary Key or ID Key
keyYellowJust a Key
userClient Method
redFlagNot Inheritable
tableSQL Procedure
earthWEB Method
zedZEN Method
eyeRead Only
261 |
262 |
263 |

264 | Some of the class properties or parameters and all of the methods and SQL procedures 265 | are clickable. You can click them and get additional information such as code of 266 | the method or code of SQL procedure. 267 |

268 |

269 | Elements which have italic font are hoverable. You can hover over them 270 | to get additional information. Non-hoverable elements are usually those which 271 | does not have any keywords or comments defined. 272 |

273 |

274 | All links except inheritance are hoverable too. Hovering over links will 275 | highlight appropriate fields in linked classes. 276 |

277 |
278 |
279 |
280 | 281 | -------------------------------------------------------------------------------- /src/web/js/Lib.js: -------------------------------------------------------------------------------- 1 | var Lib = function () {}, 2 | lib = new Lib(); 3 | 4 | /** 5 | * @param {string} url 6 | * @param {object} data 7 | * @param {function} callback 8 | * @private 9 | */ 10 | Lib.prototype.load = function (url, data, callback) { 11 | 12 | var xhr = new XMLHttpRequest(); 13 | 14 | xhr.open(data ? "POST" : "GET", url); 15 | if (typeof callback === "undefined") callback = function () {}; 16 | 17 | xhr.onreadystatechange = function () { 18 | if (xhr.readyState === 4 && xhr.status === 200) { 19 | var res = {}; 20 | try { 21 | res = JSON.parse(xhr.responseText); 22 | } catch (e) { 23 | var text = xhr.responseText.replace(/\\x([0-9a-fA-F]{2})/g, "\\u00$1"); 24 | try { 25 | res = JSON.parse(text); 26 | } catch (e) { 27 | try { 28 | res = eval(text); 29 | } catch (e) { 30 | console.error("Unable to parse server response:", text); 31 | } 32 | } 33 | } 34 | try { 35 | return callback(null, res); 36 | } catch (e) { 37 | console.error(e, url, "Unable to parse:", { data: xhr.responseText }); 38 | return {}; 39 | } 40 | } else if (xhr.readyState === 4) { 41 | callback(xhr.responseText + ", " + xhr.status + ": " + xhr.statusText); 42 | } 43 | }; 44 | 45 | xhr.send(data ? JSON.stringify(data) : undefined); 46 | 47 | }; 48 | 49 | /** 50 | * Return number of readable properties in object. 51 | * @param object 52 | */ 53 | Lib.prototype.countProperties = function (object) { 54 | 55 | var c = 0, p; 56 | 57 | for (p in object) { 58 | c++; 59 | } 60 | 61 | return c; 62 | 63 | }; 64 | 65 | /** 66 | * extObject properties extends baseObject. 67 | * @param baseObject 68 | * @param extObject 69 | */ 70 | Lib.prototype.extend = function (baseObject, extObject) { 71 | 72 | var i, newObj = {}; 73 | 74 | for (i in baseObject) { if (!baseObject.hasOwnProperty(i)) continue; 75 | newObj[i] = baseObject[i]; 76 | } 77 | 78 | for (i in extObject) { if (!extObject.hasOwnProperty(i)) continue; 79 | newObj[i] = extObject[i]; 80 | } 81 | 82 | return newObj; 83 | 84 | }; 85 | 86 | Lib.prototype.isEmptyObject = function (obj) { 87 | 88 | var empty = true; 89 | 90 | for (var i in obj) { if (!obj.hasOwnProperty(i)) continue; empty = false; break; } 91 | 92 | return empty; 93 | 94 | }; 95 | 96 | /** 97 | * Convert array to associative array. 98 | * @param {Array} array 99 | */ 100 | Lib.prototype.obj = function (array) { 101 | var o = {}, p; 102 | for (p in array) { o[array[p]] = true; } 103 | return o; 104 | }; 105 | 106 | /** 107 | * Make first letter of string uppercase. 108 | * @param {string} string 109 | */ 110 | Lib.prototype.capitalize = function (string) { 111 | return string[0].toUpperCase() + string.substr(1); 112 | }; 113 | 114 | Lib.prototype.keyWords = { 115 | "break": 0, 116 | "catch": 0, 117 | "close": 0, 118 | "continue": 0, 119 | "do": 0, 120 | "d": 0, 121 | "else": 0, 122 | "elseif": 0, 123 | "for": 0, 124 | "goto": 0, 125 | "halt": 0, 126 | "hang": 0, 127 | "h": 0, 128 | "if": 0, 129 | "job": 0, 130 | "j": 0, 131 | "kill": 0, 132 | "k": 0, 133 | "lock": 0, 134 | "l": 0, 135 | "merge": 0, 136 | "new": 0, 137 | "open": 0, 138 | "quit": 0, 139 | "q": 0, 140 | "read": 0, 141 | "r": 0, 142 | "return": 0, 143 | "set": 0, 144 | "s": 0, 145 | "tcommit": 0, 146 | "throw": 0, 147 | "trollback": 0, 148 | "try": 0, 149 | "tstart": 0, 150 | "use": 0, 151 | "view": 0, 152 | "while": 0, 153 | "write": 0, 154 | "w": 0, 155 | "xecute": 0, 156 | "x": 0, 157 | "zkill": 0, 158 | "znspace": 0, 159 | "zn": 0, 160 | "ztrap": 0, 161 | "zwrite": 0, 162 | "zw": 0, 163 | "zzdump": 0, 164 | "zzwrite": 0, 165 | "print": 0, 166 | "zbreak": 0, 167 | "zinsert": 0, 168 | "zload": 0, 169 | "zprint": 0, 170 | "zremove": 0, 171 | "zsave": 0, 172 | "zzprint": 0, 173 | "mv": 0, 174 | "mvcall": 0, 175 | "mvcrt": 0, 176 | "mvdim": 0, 177 | "mvprint": 0, 178 | "zquit": 0, 179 | "zsync": 0, 180 | "ascii": 0 181 | }; 182 | 183 | Lib.prototype.sqlKeyWords = { 184 | "SELECT": 0, 185 | "FROM": 0, 186 | "WHERE": 0, 187 | "JOIN": 0, 188 | "INNER": 0, 189 | "LEFT": 0, 190 | "RIGHT": 0, 191 | "ORDER": 0, 192 | "BY": 0, 193 | "SORT": 0 194 | }; 195 | 196 | /** 197 | * Highlight Caché Object Script code. 198 | * @param {string} code 199 | */ 200 | Lib.prototype.highlightCOS = function (code) { 201 | var self = this; 202 | return this.replaceSpecial(code).replace(/(&[lgtamp]{2,3};)|(\/\/[^\n]*)\n|("[^"]*")|([\$#]{1,3}[a-zA-Z][a-zA-Z0-9]*)|\((%?[a-zA-Z0-9\.]+)\)\.|(%?[a-zA-Z][a-zA-Z0-9]*)\(|([a-zA-Z]+)|(\/\*[^]*?\*\/)|(\^%?[a-zA-Z][a-zA-Z0-9]*)/g, function (part) { 203 | var i = -1, c; 204 | [].slice.call(arguments, 1, arguments.length - 2).every(function (e) { 205 | i++; 206 | return e === undefined; 207 | }); 208 | switch (i) { 209 | case 0: return part; break; // skip some html entities 210 | case 1: c = "comment"; break; 211 | case 2: c = "string"; break; 212 | case 3: c = "vars"; break; 213 | case 4: c = "names"; break; 214 | case 5: c = "functions"; break; 215 | case 6: c = self.keyWords.hasOwnProperty(part.toLowerCase()) ? "keyword" : "word"; break; 216 | case 7: c = "comment"; break; 217 | default: c = "other" 218 | } 219 | return part.replace(arguments[i+1], function (p) { return "" + p + "" }); 220 | }); 221 | }; 222 | 223 | Lib.prototype.replaceSpecial = function (str) { 224 | return str.replace(/[<>&]/g, function (r) { 225 | return r === "<" ? "<" : r === ">" ? ">" : "&"; 226 | }); 227 | }; 228 | 229 | /** 230 | * Highlight SQL code. 231 | * @param {string} code 232 | */ 233 | Lib.prototype.highlightSQL = function (code) { 234 | var self = this; 235 | return this.replaceSpecial(code).replace(/(&[lgtamp]{2,3};)|([a-zA-Z]+)/gi, function (part, a, kw) { 236 | var i = -1, c; 237 | [].slice.call(arguments, 1, arguments.length - 2).every(function (e) { 238 | i++; 239 | return e === undefined; 240 | }); 241 | switch (i) { 242 | case 0: return part; break; 243 | case 1: c = self.sqlKeyWords.hasOwnProperty(kw.toUpperCase()) ? "keyword" : "word"; break; 244 | default: c = "word" 245 | } 246 | return part.replace(arguments[i+1], function (p) { return "" + p + "" }); 247 | }); 248 | }; 249 | 250 | /** 251 | * Highlight XML code. 252 | * @param {string} code 253 | */ 254 | Lib.prototype.highlightXML = function (code) { 255 | 256 | var replaceSpecial = this.replaceSpecial, 257 | level = 0, 258 | regex = new RegExp("|<(\\/?)[\\w](?:.*(?=\\/>)|.*(?=>))(\\/?)>|()" 264 | + "|(<\\?[^]*?\\?>)|(<[^]*?>)", "g"); 265 | 266 | function stringFill (n) { 267 | return new Array(n + 1).join(" ") 268 | } 269 | 270 | return code.replace(regex, function (part, cData, tagS, tagG, comment, special, etc) { 271 | return typeof tagS !== "undefined" ? part.replace(/<\/?([^\s]+)(.*(?=\/>)|.*(?=>))\/?>/ig, function (p, tagName, attrs) { 272 | if (tagS) level--; 273 | var s = stringFill(level) + " <" + tagS + "" 274 | + tagName + "" 275 | + attrs.replace(/(\s*[^=]+)=(\s*(?:'[^']*'|"[^"]*"))/g, function (part, a, b) { 276 | return "" + a 277 | + "=" + b + ""; 278 | }) 279 | + tagG + ">"; 280 | if (!tagS && !tagG) level++; 281 | return s; 282 | }) : comment ? "" + replaceSpecial(comment) + "" 283 | : special ? replaceSpecial(special) 284 | : etc ? replaceSpecial(etc) 285 | : "<![CDATA[" 286 | + replaceSpecial(cData) + "]]>"; 287 | }); 288 | 289 | }; 290 | 291 | Lib.prototype.getSelection = function () { 292 | var html = ""; 293 | if (typeof window.getSelection !== "undefined") { 294 | var sel = window.getSelection(); 295 | if (sel.rangeCount) { 296 | var container = document.createElement("div"); 297 | for (var i = 0, len = sel.rangeCount; i < len; ++i) { 298 | container.appendChild(sel.getRangeAt(i).cloneContents()); 299 | } 300 | html = container.innerHTML; 301 | } 302 | } else if (typeof document.selection !== "undefined") { 303 | if (document.selection.type === "Text") { 304 | html = document.selection.createRange().htmlText; 305 | } 306 | } 307 | return html; 308 | }; 309 | 310 | /** 311 | * Contains graphic base64s for the application. 312 | * 16x16 px 313 | */ 314 | Lib.prototype.image = { 315 | chip: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAK3RFWHRDcmVhdGlvbiBUaW1lAE1pIDE3IERleiAyMDAzIDIyOjM1OjIxICswMTAw2q8EHwAAAAd0SU1FB9MCGRcbLxQhzGIAAAAJcEhZcwAACvAAAArwAUKsNJgAAAAEZ0FNQQAAsY8L/GEFAAACEklEQVR42sWTz6sSURTHj/gjFUVdDAQOhDhgDwRJSXHjSoRCc+XORbt2blpUu/6A1iHu4tFGcCUVvtVDIfwBpk6rRJ/ib1MZdfytM5070KMieC0edGDuDHfu53y/59x7Af53yG74L49GowxFUa9ms9kVz/PpVCr1BefFGxM4HA6j1Wq9YBjmIU3TgDAMh0NxMpn0RqPRh8Vi8bJUKnGKv8HhcPipwWB44XK57isUCkB12Gw2oFQqZXK5nMYEz47H4xtcysl/Bb1e712n0xlF5YTP56MOhwMsl0vYbrdAvsfjMbAsC7FYDERRPKtUKufXDvx+v9VoNF4GAgHaZDLBdDqF9XotgbiY2IdCoQCozFssFgHtW6UmkSESicTtdvu7YDBoUKlUMJ/PJcv7/R5OpxO0220ol8uQSCRAr9efisXiVbVafd7tduuSA5vNFgmFQrJOpyNZJiBRFgQBms2mBK9Wq0OtVuvjm4rH4w8QOxFWSqDT6Q5ESaPRAMdxsNvtJLjRaEA+n4dkMgmoqEin0yzuwtuf8HUJ2LTHZrPZQqyTUKvVEliv10lSEbfsG4KaXC7nQ/vsbweFDFjXe2zQEpvl1Wq1dxCAbDYLaJWUJ8tkMh97vd5rTMrCHyElaLVaAj6fsb7zfr9/D/f6DNV5dPQV5/So/AQFKv98lD0ezyPsw9ztdjODweA71v7pdm/QbcYPCZIkO9gEQq4AAAAASUVORK5CYII=", 316 | moleculeCube: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAE1pIDUgTXJ6IDIwMDMgMjM6MzY6MDEgKzAxMDC/PwNMAAAAB3RJTUUH0wMFFxI6j5FSVwAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAKtSURBVHjaY2CgBvjPwMB4WYlB4z0DAxuKuDFD0Tc3nndnGBnyUMSFGPhgbEYQ8cWBbSmXmlDUtYUvLq1o6p7PzsSo9eTzD61r1y6abPyznT2c2fezrrrShl+MrMcnH23w+yci5H56x7seiy8MZWAD3llyvHsrJyDYzRfEwMjK9OPdzStrS54f8GD7xMB3/TnDv238Yh3v7G2ctTW0rHnf32Ks/bmT4dDGjx/s3zEIMoMM8H355/uvW18czt++s+2HhfWjgrcX/c0iRXkkuP8xv3jGyPKmtsVOnIPt482Hj5ZqL1/+X+ruT9k77xkWrmRg2Ar3V82ESetd/PzDQOw30gyL/gdy//9vwfB/HzPDZ2ldfQmYusSMTLekqJhDMD4LjMHx5dMTATZWaRD79XOGpjPbvpr8/M0gvdPd//fT7RtfwNQJ8/HyfmT4/xbGZ4Ixfv/+/ZSbh0cKxNb8x3BnS31H39bwsF9aauzcL5kY0mHquDg4eNmYmT9iGPDsx7+nf5hYFEHsgKBgvl+PHmh9ZGYXyX66iaPTzaMmOD5eG2wRBw/vby4euAGM0HTA9DFQ5PxdFgHdm+d/n9ni6C7N9uf3bUYmFs3EubNX95mY3RUwMUlSZfp93+/TWZVTLxgYJfecs/diYHgFdsFjSQZO9pcfND+zcjNeUlQVOH3ihAYLG3szhwD/dzsGhpwNZ071X71y2ZDlxoV/IhJ/NZN+nNO4zcDgAw9EuecMX08+/xP3+thFt+dFxYo+Ztz72K+fXXyPXYf/AieDfF9akZ2qqFD5kz8Mf07O6bsp+ZrhrxgDw05cKZs5JiS073+40P99edb/D4sy/A0pKL7i6OoaAvMy4bwhwWD43x2YDgI5/18RYfi7nYFBkNT8xXiIi2Hebn6Gt8DU1kSqZpIAAN9VByIMFaPkAAAAAElFTkSuQmCC", 317 | blueFlag: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAERpIDIgU2VwIDIwMDMgMTY6NTA6MjkgKzAxMDBpQ0TdAAAAB3RJTUUH0wkCDjMC75xHzQAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAHsSURBVHjapZK9ixNBGMafmd3b2b3NRTkwhHQiioWN2Nh5EBBEK4v7E8S/wN7CztomZ6EI2ggpDrFQOdHCRmxEEIsU5mMje0luZ7+zM+vsekkOc2Dk3mLZXd7n9z7vMwOcsMjspdVq3dcouTvwxPru8NyrX9XLbZKmXpYmpibErY11cp1HssryKZ+4B1/6j6/eLHT6DFCv1+9sbV2r7bZf4oLIty9drG7/HCYY7udIUh/d2MKZeg03rhiVt+3PZv9QR2eAIAicKIqxPxpD8hE2N3QI04YTAt+6PjTlVeYSIgP4xPfwN8D3fUfXdZimhSwOIaT6masdSdFEFjsrReBFywAp5UAIAWYyyGmETBwfGlUKb+wtA9R0J45jMGYBWaSsysVU8udBC7VyFXp8DtAXTaQEGIYBKlOIaQ6qFcdEyrEiCNSaIXqbNahUD44FJEmiMmDIVVLdPkco15CFAehonE8Gox13mj198T48C9hflwBq8qB0wEywtIsPzx8+c83mGyIimRPtR7Z3+9Nh68ejmcwBKsDSwZrBUGEU58W7773XD5786ybOQ2w0GkUGeZFB4QIaO73KVZ4Dms0mVycRlIAiSEpP/RegqDRNHdu2VZAmNE2rrALQj364rrvT6XTucc57lmU9WgVw4voNC4zd70vBO2kAAAAASUVORK5CYII=", 318 | ghost: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAERvIDYgTXJ6IDIwMDMgMjM6NTk6MTcgKzAxMDCMVVlHAAAAB3RJTUUH0wMGFxkwngXNbAAAAAlwSFlzAAAK8AAACvABQqw0mAAAAARnQU1BAACxjwv8YQUAAAJkSURBVHjapVNNTxNRFL3TTvno9EuqmNZpymihWKhNkBrTLtQ0Gt1IYsqiEohrkiYuYOEPkISlEFmQuGqicclC2RAjLthha2hjMBSaJii0NLRTnDLTmY53pqGpKaKJL3l5d97cc+45790H8J+DOG1zdnbtXqm0P03T5us8L1RTqewnvd7wfG7uceKvjAsLX5+urqZrctOQJFmen//AR6Nvw2eCY7Hs1ZWV7SrHSQ1wPJ5W1xpSzsy8/zk5+cbVjNE0f+zt7T9hGDPp9wdgfT0BiUQWxscjsLj4GqpVgHD4vp5lD5/9kcBi6QhkMt/A5wtALleGjY0kDA/fht3dQ5BlAJOJAJq2PEKjmlMJurp0vTpdGzCMFxMZBBjB4ehTp6JAkpQiVsvEhNzTQjA29rlNFNnzPl8vJtHg9dLg93uAoi7A4OA1qFTqBAaDCeMKfYIjT4Lu7i1be/tlrVJ1auquume3WyESCYHRSAHLAoiiQmAEnuftLQpEUTtgs1kadvJ5Qa1qMFDqWqlICASwWs/B8XGVaVHAsqXQ0JBTjRWpxaIIJNmmgpQpCDKeg4xKL+J/0dtCYLebH5IkCRwHOCWsKKuVBaFOoCjAXgCdjsRcW/A3C6OjLz14aFeUq9JqASVqMbGjcfIEUfev0RBIUsMbsjmCwehAQ4Ek/bhkwkuOxw9gaekjZLM5cLl6YGTkARQKZWyqL5DJbIPb7YZA4Cb09/cRy8vv7iA0pT4mj+eVjqY3XxQKRyGK6tSixy1JktbQUid2cblUKt5yOpkbOztplqKo7wRBbHLc0XQyGcv/05M9a/wC59cUbcvinwEAAAAASUVORK5CYII=", 319 | moleculeCubeCross: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAE1pIDUgTXJ6IDIwMDMgMjM6MzY6MDEgKzAxMDC/PwNMAAAAB3RJTUUH0wMFFx0vZdSqcwAAAAlwSFlzAAAK8AAACvABQqw0mAAAAARnQU1BAACxjwv8YQUAAALsSURBVHjarVNrSJNhGH2+75vfvuY25/IuKs50UxNBrbwMFbwWqWnQxQwvf0qICBHth4JJZaWE6C+pDAuxKLFBzcRQ0ywrLbw3W1hqbl72remmm5t7c9YEu/zz/Hg5z+Fw4Dy8D8BOAAFgwwIQqQHIbXooFKwksul+DM5v0/nAtXLM8mhjyUaWHz9zrEE59KC88i4TxwJmlvUBY2ODYRJTK/M4kbIcJBQ8WcNs3tT2lqWaHfhJ75/TVeFaKNoMoCMoWuXJs6/kZgBmg+tp2UhzoaIrmVwC7rgCzFI7p2t0jDguUBQQxVFPYKWGNuiWaH7E0GBPWAJS5kyraxPa2I+f5VJ9eNTUBdVg2v6TjmwXWzOhnMUYi6WXo50pUiP7NtUY2NSE3L4YPORqaHgI8GyrV0l1TUt8atoxC190h3so3RahcEAdBCy7BwW7WH25Z/MT8zKzuq0zw0oo7dIMj7Rxt/AFBZT3S3VhBiO4tyWlGb+3SpRW324uh6MBpLLOuJUIhUI4kp4uRgid9jeDvKOiumaprg6PFHuz53A4Y/WxKIpDEoTmrwC34VEvqrQ0boM6TfX1ZRyoulHhqV9jnxK5MK8nJpcczc4OtPiMFJtjZLG3ArDf/wDvdmQZo08E460jJK1WKO0yExiE9KnW7COOllwcl/XwwsLyfHHjZOrSwJ53SsBcX3yIOQQwv7mDaVfYtahZxYBHgnBdzhdEcKFHMmU0XKrViXJzMiyefRSz1p/UP3YIWffP+zoINQCHN+T6zQBPBegGi4s6uhrr4mLjPUA3rYYFQYhMx3Pwma+uEhdOznr7OvKLZ0xgenv7psx1AdadANq2KrwCyMEEdrciEzwYsGIAMBuhs2sevK5cBVuChc4NyMZUo0Nlne3tzb8a/4FmAh9GBXvRSKiD+aWIr0apzghlOaLXfq6IbqxfbwWw/98dbVZwwkHecueTr1Zjyp8EuK+agF6KgfkxmQgbb5E+Ogig3pGr/Rd+Ai1UJxH01MR4AAAAAElFTkSuQmCC", 320 | greenPill: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAK3RFWHRDcmVhdGlvbiBUaW1lAE1pIDE3IFNlcCAyMDAzIDIzOjI5OjEwICswMTAwQo74agAAAAd0SU1FB9MJERUdI/ot6l8AAAAJcEhZcwAACvAAAArwAUKsNJgAAAAEZ0FNQQAAsY8L/GEFAAACa0lEQVR42qWTT0hUQRzHv+/Ne+7uc3dphVSMjYoWsbA/h6g8iNWtKCjqoEmXugoh0aFDHkICLx2ELh0khAjKCIsKPGSY7FL+QYvIlCKSfM99m2/dt7O7789Ms4fCUknoC78fM8x8P/zmNzPAf0r6NejpGW4Nh8m1aLQybhjWhCSFbnR2Hn65IUpX1/P2oaE55vucU8p5Nsv54OBHv7d3/OqGAN3dz97rOucLus/TJue6ISAW5+Mpi9++NXnnSscTdT2vXE7BoBpPmzlYVgk0z6CIg9kFhtfBPqQSNy99PdT34uzEsdi6AF3PTGcyJuycDbfEMW1+wIm3ccwGBrDjgI14k3V00ZxLNifrd64J8H3SPT72iS0t/YCeNtA6eRAtiW2IqVVw86JCSUVzw+56vuykGodrW1YCSDklkw/m9u87J0jekae5fpDIDKJyCAWfig0EnIsl5iFRW6MZeq5NviB9s/oLU78BZY2O3n/V2HAmMZwf2BMN5eA7HMxjKHmOADARHI7noi4UIxmTnqSnvWTxoftZWVmOprkX82Zhu15Bm5woR6DSRSisoBhxEKxUQQgRRQK7gtXknbFwXViGpL+bgstaTSDnvamvjW1VNVkYFWgRBRUBAlmRQSRJhIIRY7642JENrQaU1Ya9GgIjW6rDETUooSJIoKjlEAAi5jLBxPJiOttFq5U1AfcwRU95rd+p/XhTTFWIMKtlsypBJTKYuLssLT36o4mrNMNn/TpuOjY7zhmTPMrgFrl4aD7mLXtWvPvzmAJdHyDEv/AxvllKFbN+vFRgVdT20suF0l3msnYMILOhf/Iv/QS2OyBbr28yxQAAAABJRU5ErkJggg==", 321 | iceCube: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAK3RFWHRDcmVhdGlvbiBUaW1lAE1pIDE5IEZlYiAyMDAzIDAwOjU2OjEzICswMTAwhV8lEQAAAAd0SU1FB9MCEwAKIKGTp3IAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAEZ0FNQQAAsY8L/GEFAAACoUlEQVR42qWTz08TQRTHvzOz3W63lLbQWixIBRU08QdcTAwxil5M0IPRuwfiP6DGq3f9B4gXPegBLx5M8KAnY6JEQtB4QIOGIhhagVbLtrvbnRnfQqwYj0wyO28y8z7v+97bAXY52B/jflHnN1cwmmlTp3tTOKYYhp+t+7FAonIiLsp5Q5RW6qpc9VQpcLDka/Xkzmhs0Qidn6/qiZEOXE/0hkCOL75kjUDjYlwgE2Xp/ZZIL3vBoPA1phZcJHMMqXU2Tq6DW4DFqjduWIxlOUefLdgBUwAmHdjb6r65Cg+Xm/hck+i2GYpNhbhgA+HZFsBpKuNVRUNoYI/JMUSR10lBv8UxYJNNDnsthqH2CGZ+BogEgFQqaAF8qd2UgCU0A91DmRzmHYVKU2ODQO8dCUnwGtkWZ6BM4PjaDX15+FFSNxlZFbogaA2nRVkE0ARRsGmfpD35ImcyXMpGoQLdaAGSgjlRxhAnPZzoaYPjYIzjsG0gFxXYR1NTfbvMcAUelzw0KGgLUHVlszdmoDvKsY/yXqGiFT1FTVYhHHWp4ChKx1eULnCh0wQ14q8CLbUf5jhdlTiTNuFpjdlfElNrATJU1KKrcZzkHU0IMLo3U6PgSnmtIkakdgiDBO0mSR51EgUqgmRhXRTaKP+iJ0kBwxtqZZ3OI9D1FmDVDZwRkl+mqrsU7VrBwpXstrMkNcNEThoM7TQ3qde9dgR331amW4D5jdqLl5/MU1cLMRzp5PhI0fIRvtW6bIT+xgjw9AcpI1XLJY5Hc8Hk7Dtxe8db0Ozkg9pYLpoYM7k639FeP9TfI5BKM1zusTCzGaDkSizMBY3JD/aNrzf5xH+PaefI3FrqFqo2moqZ5zq7omfzKaNv9bs7+3pNjONeYW63L/if8Rs4lyRrD9xpXAAAAABJRU5ErkJggg==", 322 | minus: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABNElEQVR42u2SsUoDQRCG/73EEBMPLCwkhahgI1j6BHYiFvoUFhZB8OmsBMEHCAGxEImEgImBvYu5y+3u+O/tRQtLCy08mJu5ufn+mZs9hR9e6l8ASk7O+tjttDEvvsuJoy37uOCsvzHZqAOD0UzJdVdwegiMXoGVGusiLxuKvBnPWnoKOFpBs0y2WHf7AGUvL3R0tBdjMCRI9VkKTMdAohlngE4IETD2C84Zk3dPOlHzq27SPFhfw/0d8ObBNHQrbAC9N0ur8pkrm+kXnSp9fqzjHROj9wg0V1kgFVxB3lsCWTVBXn2GEYwn74nqbW/K1v4GzHBCVe7AFOVLERvE2FWMfEI+FiuIONzzIoe6aTT6nVa9nWcLTqUIlmdT+mBV7BfqFCwfHGqIuNgpt/QHfqRfF/gAIz+4k570HlcAAAAASUVORK5CYII=", 323 | plus: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAABh0lEQVQ4y52SPUtcURCGn3Pu2V3ZpAhaLAQtQiwDVrGSoK1dKlvxP1j6D2wFK6ukCf6DkCJoE6IogWCzoiFgsHDDsnv3494zMykuCYl774KZ7hzeeZh35nWU1MrBorUWZgj1GgAxy7n9PuJ4q+3ua0MZYJBlbLxc52vnDQDd1HN12SyTlgMAUEfNNwBIrFJVDYjqEAVRSMf+4QBUyMVjQJQpE6wcLJYOmOWOYe5QgywXuv0hz3dnJ7ShRoOt188w54laNGgeOL97BxpQPM3HjlerCS9ay6gVtlTg8P0JIRspX24/46ghAgpE9ajWEQNVEPOYwfnNKfwGAN1enyBixOjwDnIFU080MEDEIUCMxVsNogIGikcihJgZ7euk+AWg2NhSa41veoQa/LiJADyq37vUWAnpYMzZx39383PQYWmTP5YAPn24K7/Cxc71RDznt5/ak+Yc444HBHGFJN0bTWgrE5KLYSaIJZhSWZVBEgPRBKWw8mDA3+fS/5lg/+0hvaxbAKZk+ReMaMs7XA7SqAAAAABJRU5ErkJggg==", 324 | crystalBall: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAK3RFWHRDcmVhdGlvbiBUaW1lAEZyIDE5IERleiAyMDAzIDIwOjIzOjM3ICswMTAwgTNEtwAAAAd0SU1FB9MCGRcHLoVRoakAAAAJcEhZcwAACvAAAArwAUKsNJgAAAAEZ0FNQQAAsY8L/GEFAAACeUlEQVR42qWTy08TURTGv3m3nSm2lVcLKYkkGIVA3MhSNsQtK6uJG1m4UBJZ+l8YdhhYYKJ148ZHWBlcGYm60EIGfPAoFZBiQYZ2Ou/xzERMGogbTnJzMnfu+d3vPC5wSmNO2nw4Odmpadqdqq4PeUCSBTYlQXiVTqenbo2O1v4LeJrP51ZKpenW9nalv7cXqWQSW9vbWFBV7O/srJ9NJkfujo19PhEwPTU1XDk8nO0fGOA702lIkQgYhoFt2zAMAyvFItRCoZyQ5Uv3xse3ghjuKPjRzAz7s1KZzXZ3t6QSCZB0OK4L23FgWBbqBBF4Hg7DyHvlcurN3NzzII49ApRKpUFelnsYloXj++FyAwh5n1T45C3PAycIYCXp2vVcTgzi+COAUa/3tTU3Q6ebtioVRGIxSJIU5uhRYJCCYZpgCRCJx2We58/Rr+V/gFqt5kVkGTwdEChQEEXQIQSKfAIwf/ddSknb3w8u9BoUkBVMXUc8kwkhUjQKjiAsAQIFLtXBrNdBuaCmaYeqqq411ODBxMTHzfXiokJtC+UTQInHoTQ1hV4idVFFQYy+vy8uPlaXl+2GLgTmJ1oKQmv6ZldvHxcAAlA0JofyQWq4RAr5J/nS23fzN3Y3ivqxCfSz6CtexOqH+7f9+aUl/xOV/gutAq15VfWfjQz7L1i8XgWyxwbJvwwRHdk1dGUzKK7D+foD1V+AGXSoAliUrEGN26UIxcG3wTp6Gov4Hr511dwTLSeDM0nw5xkk0gdAVYPVBpTpBehUQ5M852L3xFF2OxFd85Dz4uKQyHMX4LodjmmLVcuvHujY+G1hoWZj7oqFlx00Y6d9yaH9AfjUFDAtnmCxAAAAAElFTkSuQmCC", 325 | user: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACyUlEQVR42nWTa0hTYRjH/+dsZze10i5j81JhLqOijBSVqcSIjKLAvGSB0Lf1QcxSJCFDoRsUZQT5IVJKIx2VlWIfSsxrWSrzEpmXWro5LzHNy25n5/Ruo0Jx5/By4Ln8nuf98z8UVnmURW0lieFrsxUbZLLZBTv/YXSubO6386LpWoJ1ZS21MhBR0v62NGuXJiYsADMOQEAqBqdtKNb1D09NLqkM15N4n4CNeU3qolRVS0q0Am0GG2iaB0XKQ9dLoTdYUFzZlz1+I+meT0Dwhcb7rwvjteMLFFjeBZpk3eNEZA2BiEbJI31re15Mgk/AzsvNt17kq89/NFvhJwAYigZPKliew6ZACa5U9JgbcvYrfAMuNd2uzVef65y0w8HxsDg42Fw82YKDzF+Mmmd6c0dBnG9AiLb+bmlBYvanXxzsDue/tFgkxKLDhYbnPfXDNzVHfQJCtXXyrSq5OUq9DbyN9STdIooDGHzRm9DdNnTY+OD4G58A/9SaCKXC71t6WhSmeAa80wWKKEkREd+/0mPWbDlrrkovWxUQeFp35mDc5ofHkrdjYJrFiMUGxt1MXgFNg5EIMdo3jqHPI9Vj5SknlwGCMnTRB+LDOjNT96KyawZLZLKMob1XgNc3AqEA0gApxgaMGGweuGN6kpHrAQSeepq8Ria6WpiniarutQAcBzEt8DRx1P8VvV8e0nV+GHzXh9mxSY2xKrORitS+rIuN3XKEDZGj3zAPf5G7i1p2RwpeQ7kVFTIi2GcXYWzvbzVUpCVQe3LqepMO7djdOs/AOW8FjZVT/1Joj6BuPWg/Ccx1HROG8hNKskFtl1Il3/dTEghifkAmIYM4z0yKmMkTcx8n6z02B7gFK6xfv5uMj9ODKXlqZRBrc6pEUnEuJWUiIRGFw8WSXcXEAESLJfIH0wIhHOwEWNcwZ3d0k1gLLRMPmHRZP/4A67cYINx3gGMAAAAASUVORK5CYII=", 326 | redFlag: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAKnRFWHRDcmVhdGlvbiBUaW1lAERpIDIgU2VwIDIwMDMgMTY6MDk6MzEgKzAxMDAbRI2sAAAAB3RJTUUH0wkCDgsfmwWX7wAAAAlwSFlzAAALEgAACxIB0t1+/AAAAARnQU1BAACxjwv8YQUAAAHnSURBVHjapZK/axRBFMe/s79md0nOhEQtvErwBwhik0JBTCGW1hZXpdFWFCz9G4QUV9gasBBFEJU0R2wMKFhYCBIPlLvb3MU79/b2bn/c7Ph2wy4LZ+AkDx4MM/P9zHvfecAxg+WLer3+iKnq3bj90z71/tmbW60fWx7DKI5gCRW31TOVm4lIFgKB4bDtf7zmyHupTssB1Wr1/o319dOv3m5jde/1hn39/IYSAPK3D1syaPYUuLgCrF3Ap4c7q3AOMp2SA3zf70zGY3T3HfTCBPq5BSxeOYHKSQXaXgeYSiCiDKbw++Ew1xUVeJ7naJoG2+QYC9qO6bJMgKTc6OHaG8UFQCkdOUIImJxjIg0CCLpcVuYKhskgcGcAjLFOEAQwdAMTZiBJSy4/m7LUQ4UbYrYFRVGcFGBSC5HKIQUBNFKplDrDn0GMwRcXy1YfEY4AhGGYAWLJ4X53oQcSSS9Erz2Vn3f6T8mOF9FW6+wSsDsDoMha4IaONl/B5kv/+dWDXw1iCF3FtzvAh38NUgGgH8gq4LoOVlnEu0uXvz5uNOrZoTh6EgsTpZQpQBqGAZOSA0vzjHIBqNVqHlUxMk0TnL6SPPk/QBrkwb5lWRmAYPY8gLKJ6Ha7m81m84Hrui2CPJkHcOz4C2fdx/hnBd3YAAAAAElFTkSuQmCC", 327 | table: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADC0lEQVR42p2TS2wbRRyHv5ldr2Nnk9ixHbe0QC2BWoGQONGQFAgPicIBBBQkhBAHHleoQNw4cCwPcUOoQryCUtkkbSJLEVCVtiBRDkUUFQmaCrUQKCJOG7vrR7Pr3eHvdaB3ZrWa3dndb/6/b2bVgem5x7bvKL0WhcoYei1ERR5GD8u15mpryZn6b2wgYamfz5x/V3339Q/GLx3F+Hb8QAnAxD3SXwUo+vh/xxKJiM6p24gBh7c+zfLqGknLkjcd0omXaAdvYozamNEn6dyPH3wvYzW5TxIOtNjz66E+oJx7jp9qNVK2IqEHyVovshrs25hNyREwrHfT5iTr0Qra9AAd9v5ViQHezl23un4dHElRW4cTDXhoDLpRv3xbONUVGM9AIQlBKBFG4KvqiaY6duSYN7lrwq3XG3EFNV/xbV3x8FiEvwFw5JyvaSYyhoJj6HQNuVyG6sIXTXX8yyPexJ3jrtfwGBDAhY7hm1WLx7eErJvYmsSCuT80k3nD5pShFRhGs8NUD34eA8xbzRvQgU8maaG15lJgcVOuJ9AmJV7TjsWyb3G9rOKQROi5HcpmcY4fRC3OL3rjd427l+uXxb7mz7biqFTw1LVdrkT9CpKWYWbZYqoQco1AWuIpl8+wMFttqrnKgnfHPZPu2lodtwfoKBb/tnl+m087VLGDtADeP+/wQLHLFonQlAiF/Ciz5fmmOlwue/c9OOU2LtUZSsBZT1OW2V7ZHnBFbEdSRVI4+85Y7BEvJTeiIXKKYzk++agSA8z04E7CdpvRZIIuFr91NLcX5Utti0DNoMg97dncnDWMDMheFWo2n2e1/B7q4w9mvIm7JcLFNYYdzbm2pnLB5tUbfdYlglGKlDa8cTbBE1tDtqWjOEI+n+OzmVnUgU8rzXt3Tw1elK08IoBfmrD/nM3btwR9B1JIOmF4+bTNC6WQHS7UexE25flw/3QMMPlNBeo9iXEFFodWUuy9rtlfhd6fJ/vgnd9dHhnrUEqHtHzIjI5w6uSPqGeefPbR4ubiHP+jLS0tvf4PKUZIoAyrwMEAAAAASUVORK5CYII=", 328 | earth: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADUklEQVR42l1TW2hcVRRd5z7mcedOXtMmmmknpg+VZEIFrfjT0lZqQiemoWXyVxpRKNUKiiD4WUHBHxE/IvVnIuhHDLYiHUhKBEmxUEgKLbEW0kkyebSTmWle87iPmXNOd6a0VA9cOHeffdZae+19GP63XhvJRPv3+o7uCWrfpwq8Fmv1K9nLc9Z52q6Mv9t88/l89nTTl3xoRuu8o2c6zZ5XG3XMbEmoUiBTYQiqQJ0qIewqfpwpjV1btuP/nA0XnwHsHpo3hmPNd49FjLYncBILRYG/1wUadAZbAJ1eib1eQFckvr1dTA/dLnakzreVawBfT+bHvjgU6oYQ4IJhyRJYcoANV0JTUAOI+gQaCditAi8YDBcm18Z/iL3Yw966dP/E5GAkqasqbC5xqyCRLgMGyfbQ5W0XHIq/bW5flnhUAXboQNaqYuDKgxiL/7KQGBnYPVhwBR4Qa5GSc5yhIiSYBAr0f8CgMjU6q5AUijsViRZi+Pyv7DA7+/vKavxIa7NacvB6QAEn1iVL4l8bZJ7EJjEerWeIEECOSCy6XKlyAtDx5Y18ll2YyFj7XtnhS226eCeooHcnQ4kUTG8J3CmJWhkGYwiRebvI0CZNoEqlSIXh3J+PbPbRtYzVvj/kW1x30ElS39/lwQaxBFUFc6UKvlpwoZKsnM1xsEHBh2EdnHMk8hI/3czZrC8xu3roWHvzUq6MAnXg47CKiF9BPQEkcy4SaRemyqgjHFFTxaftHpRdIA0F312dz7I3Lk4ljg90Da5tOMg7EsdDCs616ZgvS3w2U4ZDbdMgYJMXglxNvmlgalPgt1WOGxOzwyz8wcSJw/3RpNkSxPKahfciHsRbPTgzVUKqUEUd1epKCU51bzoC3a0apksKpu+tIXAvHasN0v5PJse6Th7ozhPNNy97cT3HcSllIexntZZVxZMZEFR7lvxgNOKBW3Pj80NHemoALfE/jEC48W7T4Y622EseLOcsXM848NGpS3NQ3f6oEy7TwGkU1TtzaW2r2LH4c2/52WMK9Y+aeoM56u3a06O31INrKoRbqTFzejKc5CO/Dn12cUx17PjKyOnif17j09XU92uv2micQtCMSb8/IEguLKukbFlJpVi+nL0Sv/p8/mODDJQs66BSJwAAAABJRU5ErkJggg==", 329 | zed: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAKPWlDQ1BpY20AAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4BUaaISkgChhBgSQOyIqMCIoiKCFRkUccDREZCxIoqFQbH3AXkIKOPgKDZU3g/eGn2z5r03b/avvfY5Z53vnH0+AEZgsESahaoBZEoV8ogAHzw2Lh4ndwMKVCCBA4BAmC0LifSPAgDg+/Hw7IgAH/gCBODNbUAAAG7YBIbhOPx/UBfK5AoAJAwApovE2UIApBAAMnIVMgUAMgoA7KR0mQIAJQAAWx4bFw+AagEAO2WSTwMAdtIk9wIAtihTKgJAowBAJsoUiQDQDgBYl6MUiwCwYAAoypGIcwGwmwBgkqHMlABg7wCAnSkWZAMQGABgohALUwEI9gDAkEdF8AAIMwEojJSveNJXXCHOUwAA8LJki+WSlFQFbiG0xB1cXbl4oDg3Q6xQ2IQJhOkCuQjnZWXKBNLFAJMzAwCARnZEgA/O9+M5O7g6O9s42jp8taj/GvyLiI2L/5c/r8IBAQCE0/VF+7O8rBoA7hgAtvGLlrQdoGUNgNb9L5rJHgDVQoDmq1/Nw+H78fBUhULmZmeXm5trKxELbYWpX/X5nwl/AV/1s+X78fDf14P7ipMFygwFHhHggwuzMrKUcjxbJhCKcZs/HvHfLvzzd0yLECeL5WKpUIxHS8S5EmkKzsuSiiQKSZYUl0j/k4l/s+wPmLxrAGDVfgb2QltQu8oG7JcuILDogCXsAgDkd9+CqdEQBgAxBoOTdw8AMPmb/x1oGQCg2ZIUHACAFxGFC5XynMkYAQCACDRQBTZogz4YgwXYgCO4gDt4gR/MhlCIgjhYAEJIhUyQQy4shVVQBCWwEbZCFeyGWqiHRjgCLXACzsIFuALX4BY8gF4YgOcwCm9gHEEQMsJEWIg2YoCYItaII8JFZiF+SDASgcQhiUgKIkWUyFJkNVKClCNVyF6kHvkeOY6cRS4hPcg9pA8ZRn5DPqAYykDZqB5qhtqhXNQbDUKj0PloCroIzUcL0Q1oJVqDHkKb0bPoFfQW2os+R8cwwOgYBzPEbDAuxsNCsXgsGZNjy7FirAKrwRqxNqwTu4H1YiPYewKJwCLgBBuCOyGQMJcgJCwiLCeUEqoIBwjNhA7CDUIfYZTwmcgk6hKtiW5EPjGWmELMJRYRK4h1xGPE88RbxAHiGxKJxCGZk1xIgaQ4UhppCamUtJPURDpD6iH1k8bIZLI22ZrsQQ4lC8gKchF5O/kQ+TT5OnmA/I5CpxhQHCn+lHiKlFJAqaAcpJyiXKcMUsapalRTqhs1lCqiLqaWUWupbdSr1AHqOE2dZk7zoEXR0miraJW0Rtp52kPaKzqdbkR3pYfTJfSV9Er6YfpFeh/9PUODYcXgMRIYSsYGxn7GGcY9xismk2nG9GLGMxXMDcx65jnmY+Y7FZaKrQpfRaSyQqVapVnlusoLVaqqqaq36gLVfNUK1aOqV1VH1KhqZmo8NYHacrVqteNqd9TG1FnqDuqh6pnqpeoH1S+pD2mQNcw0/DREGoUa+zTOafSzMJYxi8cSslazalnnWQNsEtuczWensUvY37G72aOaGpozNKM18zSrNU9q9nIwjhmHz8nglHGOcG5zPkzRm+I9RTxl/ZTGKdenvNWaquWlJdYq1mrSuqX1QRvX9tNO196k3aL9SIegY6UTrpOrs0vnvM7IVPZU96nCqcVTj0y9r4vqWulG6C7R3afbpTump68XoCfT2653Tm9En6PvpZ+mv0X/lP6wActgloHEYIvBaYNnuCbujWfglXgHPmqoaxhoqDTca9htOG5kbjTXqMCoyeiRMc2Ya5xsvMW43XjUxMAkxGSpSYPJfVOqKdc01XSbaafpWzNzsxiztWYtZkPmWuZ883zzBvOHFkwLT4tFFjUWNy1JllzLdMudltesUCsnq1Sraqur1qi1s7XEeqd1zzTiNNdp0mk10+7YMGy8bXJsGmz6bDm2wbYFti22L+xM7OLtNtl12n22d7LPsK+1f+Cg4TDbocChzeE3RytHoWO1483pzOn+01dMb53+cob1DPGMXTPuOrGcQpzWOrU7fXJ2cZY7NzoPu5i4JLrscLnDZXPDuKXci65EVx/XFa4nXN+7Obsp3I64/epu457uftB9aKb5TPHM2pn9HkYeAo+9Hr2z8FmJs/bM6vU09BR41ng+8TL2EnnVeQ16W3qneR/yfuFj7yP3OebzlufGW8Y744v5BvgW+3b7afjN9avye+xv5J/i3+A/GuAUsCTgTCAxMChwU+Advh5fyK/nj852mb1sdkcQIygyqCroSbBVsDy4LQQNmR2yOeThHNM50jktoRDKD90c+ijMPGxR2I/hpPCw8OrwpxEOEUsjOiNZkQsjD0a+ifKJKot6MNdirnJue7RqdEJ0ffTbGN+Y8pjeWLvYZbFX4nTiJHGt8eT46Pi6+LF5fvO2zhtIcEooSrg933x+3vxLC3QWZCw4uVB1oWDh0URiYkziwcSPglBBjWAsiZ+0I2lUyBNuEz4XeYm2iIbFHuJy8WCyR3J58lCKR8rmlOFUz9SK1BEJT1IleZkWmLY77W16aPr+9ImMmIymTEpmYuZxqYY0XdqRpZ+Vl9Ujs5YVyXoXuS3aumhUHiSvy0ay52e3KtgKmaJLaaFco+zLmZVTnfMuNzr3aJ56njSva7HV4vWLB/P9879dQlgiXNK+1HDpqqV9y7yX7V2OLE9a3r7CeEXhioGVASsPrKKtSl/1U4F9QXnB69Uxq9sK9QpXFvavCVjTUKRSJC+6s9Z97e51hHWSdd3rp6/fvv5zsaj4col9SUXJx1Jh6eVvHL6p/GZiQ/KG7jLnsl0bSRulG29v8tx0oFy9PL+8f3PI5uYt+JbiLa+3Ltx6qWJGxe5ttG3Kbb2VwZWt2022b9z+sSq16la1T3XTDt0d63e83SnaeX2X167G3Xq7S3Z/2CPZc3dvwN7mGrOain2kfTn7ntZG13Z+y/22vk6nrqTu037p/t4DEQc66l3q6w/qHixrQBuUDcOHEg5d+873u9ZGm8a9TZymksNwWHn42feJ398+EnSk/Sj3aOMPpj/sOMY6VtyMNC9uHm1JbeltjWvtOT77eHube9uxH21/3H/C8ET1Sc2TZadopwpPTZzOPz12RnZm5GzK2f72he0PzsWeu9kR3tF9Puj8xQv+F851eneevuhx8cQlt0vHL3Mvt1xxvtLc5dR17Cenn451O3c3X3W52nrN9Vpbz8yeU9c9r5+94Xvjwk3+zSu35tzquT339t07CXd674ruDt3LuPfyfs798QcrHxIfFj9Se1TxWPdxzc+WPzf1Ovee7PPt63oS+eRBv7D/+T+y//FxoPAp82nFoMFg/ZDj0Ilh/+Frz+Y9G3guez4+UvSL+i87Xli8+OFXr1+7RmNHB17KX078VvpK+9X+1zNet4+FjT1+k/lm/G3xO+13B95z33d+iPkwOJ77kfyx8pPlp7bPQZ8fTmROTPwTA5jz/FxJCTIAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAAAAZiS0dEAP8A/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAjxJREFUOMulk11SGkEUhb9uZprI3zAzBGOMKZyYVOWZLMEsQbfAEnQJugSzghQuIVlC2EFiUCkqpRGcHxigmZk8MFjh2X7t+3Wfc+89IssynnPEV2BbqV+mLHgaiLOMJWAJgQkk/xUnwDTLiIFlmvTGWn+WSX6xt9+i6O2TlraYJMvv/Vn86fcsFsNZLPRiftqyLNz3B6hmk2WWjn2tjzMYG0sgBn4vFkSjEY++/yVM004JkMAWtJvl8lnqOvydL5j6j8RadxK4ApAJMMuyDbgKFAEDbFeprlO3ua2WmNzfEUTReZxllwACkBpIgSiKemGadpwclsBL0/y2W7e9h3dvmVwP1jWna9gFjBTIYBxofVwDCnlPbCnPdiyrzccD7gd/iMKQidYdVspYq5QCiLXulOBK5XBFiKPtmnVivd7lehwwfXhgEsenC+i9AOxcwRSQ1VWjeiq3YoJnKXXxqtGgX9tiMhgSBMHlJE3Pq0B9pZgCHM4AWcplr9fJMc1uy23Yd60d/J99wjC4CpKkUwHKuUITDk3DOFmsp5Dm8Nq3+LDPfX9IFIZEWh8rGFeA5cq/V1bqYp4/JpP897IQR82adbLzZo/bx/DJdwq9Wg5LsB3T7JZsx1tvqExXFryKUheO6zK0K4xuBhu+yWHXMLrbttNeKPW03lIDRdPsCqtu3xQVw+vhk28BKMAU4qik1A9h1Q/vGu5GPgxDyrOZLLRnvg++v0qYLHhOsTAil74E5kAYRRBFm2l8bpz/AaDSFezKL4K+AAAAAElFTkSuQmCC", 330 | eye: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC7UlEQVR42qWTS0iUURTH//eb8ROdZ5o46qRktHBSFCzJlfRCKNqULXoZtOgdZSY9rIULIYLIHliQRBmIUBCREkSlUSBZmjROqTHj6ExOJqYzozPf896uBhLhqg5c7uHA73A4//8h+M8gfxc0z+FiaGotFKmEKbLOJAmQNQNkqZvNxuoT97zrWbSB+rXaTqh2jcOVoCoEXYMuxyFIcbDZOMAf5c1YTGqmCjuRdLx/eqGB6q3JIWBug4FaWDwG6veBBoMgPGdUBwQRMJkhGBJAZAWKokd5ucB00jNCOCwYjEKQGGnGdFcPIq86QFkyaGY+qCUdRjEJiIyBTAzAODMBU2Y2rGl2yCoN6Tp1EjZ2/iRi0as9Nx/wUZMhl5+FVlAG1bYKUDgbnUWK0wrDzyHonz/A3HkZxvHPWOlygoqWKqK69wbeNT5zOjacQeb20+ic4RMzIF/lydQ4Ghtv4FsM2FzXADERcGkM0Te3IbfXoWAZCRLv/R1S6rpLibZluRjmzGBUw2RgFNG3D9Hd14+WR48gxyWUVd9C3q6DWKMFsHm5HSP+ISQ9PSqTKb9Hsue4EueW2T8ex70gw3DvJ7w4UIrIH3IVHrkJ+9b92CIGsNMRw5gtD/G+JzLxNRYFzGVNzjRXMSb4wiueBJGdkoLnx8rxw/N2HjZbUrG6yQNfRMadUhFOsxGh94/heHMhSFjP9lPu1o4reu5uFFUcQk3IhdYvDOlWgh9tzYiHw0jeuA+jmhVltnHczeqFt70BjlAnslc4q4j+eptBIFrA/7IrIxySgdz1uDebB68lH35bITRK4JjqQ2G4C3tENxK+f0SyKGBpdlZImZNxbkS5bVOOyHS3PDllkXwBcOWhQ+A2I7+dRhgSqIYZaoRuWgIiGKOyohVkXBwcWbCy0rLWLkjqNaaqlZRLxSgF4zvhHzcWm/9VyuuMNXMXnkivHZhe9Jik665iSkktZ0o0ynTOzHUwaBTdnK5POzew+DH9a/wCg6d3xpUf4V0AAAAASUVORK5CYII=", 331 | binoculars: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADrElEQVR42m2Sa0zTVxiHfwfaSgthCxTEuZlMdkn2wW26utlEwtQB2rUUicSJ4gRS7cBNXIlmSGCbcYuGbMsYirawoWSJyEAm09VOII6tYKmOS8cYtEBrL7b0Tiv08t9/zZa4ZOfDSc553/Pk5Pe8BI+s3cUljrzc3FSj6T41NjZOLn/bRv6+31tSSvH5m6hJ3e9kfFLnuaW88fi/b8ijgLJy6ZL8QhPL4fSjq7sLkrKSWL29/TK1Z88uWBxeHJO9//DiN3L2/wK2C8XTIoEgs3/gNn4bvYfJidFYXVxQRG3kb0RPZzfC4WWHRjOc9h/Aidr6D9LTMo72DfQl7ywsZI6MaKH+ZRDqX2/H6ufOK6jC4t0ofWsfDPqZaAo3dSHgXxzUaIYKYg3Ha+rChw5K42fn/kRvrwpTf+jAZDJgsVjg9/pwov4kNBo1nlv7DGwOG9gJiejvU+FqdychlYer+C63b1Amq8KaJ7jwBJfQ/FUzrA/siI9jYqD/Jta9vAEejxs/3rgGr9sJl28RPVev04COHFJT++E+p9fbVnmoHHHReDooOzo7rsDpciIaAbTaIWRlb8UDmwVnzzbCarbCanPAYrPjYptCSmSy4/XJKal1ktK3kchmwWRzoUUhh8NqRSgKDA8PIntLDuz0+bz8HDhJHExN6ZH4WAqq35UqyIFSycj42Nj6CEXhYTCIYCCAlasy8NKL67Gw4MDo6F28nv0Gpg163NOqEfAHQOjkCL2tfnK1lVRUvncyLX1VzbHqKlz7QYVbdDgUHazZOB/gcrnapOSku0ajsexNkZhTVCDEqTOfwevxwDCjh9fl+IJUHDl6msfbVF1cJIKitR3jEzpEIhGY5ueWv+/5bsU/E7okEOazdgrz8PGpM/DQZrxuN53H/CekXPLOhChf/MIrG9bRvltgNpsRCYVx32iEUtkb01xSVk5t2ZqLLD4PDQ2N8C76EA2FMDc7rSPSiiOB/Qf2sp99OhNNF1rh93ngcroxO2+AsrcnBpAcrqKyN2dBKMjBp6c/h9vpjFmaM+iDJE8gpj6qrYPda0dzkxwhmhwOLSMajeIn1fUYYP/BCqp4VyFMJgs6Oq8gshxChIrARpshmZnPUw2NX0KlvIlLrS10exQJHDaeWrMWd9Q/xwDbtuVSX7ddwo7tefS39WCtYCF9ZQZYDAbIq/zNEcGO/LiZmUlqaEgNGg0Gkwk2J4HcGVbHADzea5RAVEB1d3XASauNoxXS40ziGXHRvwAVvrukyLa34QAAAABJRU5ErkJggg==", 332 | keyYellow: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACi0lEQVQ4jX2TS0hUURzGv3POfcwdx8GZVHzMCDOZFpHIiPQgXJg2tTCwSClatLBVCwOX0SZatKl2QUQtCiNqkRENtUjTSoyYsjItNUwTlZkcfIyPO/eec1qohDTjt/w4v4//+f78SUNDAzLJ5lLlnDQFCq1mT9ZyeWJRGx+LuZ6oTNxnVCYBQMkEpyypFeQqD88fl42hwCQYG0Fi0i6LDhfV3+6paFky2RFGZZymg7mQyPXol69e8DVWVwHMYaM7WoLn8Wtw+sK4eLQ/xKh8wAXxpg0wTZEdrik8V1DiBFYkIG30/dyF2mOtSPla4fcu4kBwts606Ym0AYpCdlfvyffC5BsO9m//ivabbZjqvQSvm6OieB4KlaG0HRBCUqrCALluWAw1lTOo9t+ACg2gOrigkICSdgLLEgPRgcQEVPbPtCkMQwOjGiCBvl8eCEEG05coFW9ydoiDCwDrT4gEqARxWOgeLMDHCc8nXRWP//vCsslKaytjkfry94HV3zNweIoBBohVisScga7Pftx7F5xhVJ6hRE5uClgxyd6qstVnTQeH8wwHgSP1DYiP41bnzu8fRotm55Na1tyy1qOr/AolMr5W78bqLHkoUMQ6TtbEXU6dIzePAFLg5ZttePQ2r01VZERhEoZmb94YANi2PJ3v1e6eqrP0bMOG368hPm2Ip71BM9Lv6zN0EaFEIp0o57LF6VTbmw/n6C6HhbJgNqDZ6Boo/HKnc4c/ZdOmTDAAUCGxaGiASlMIVeSDUIKFmBMqlSUuw+aUyD8ZaQAK52I0x60vhMPlbvAxwOK43lE6Ev2RddbQbHsrGAAUSjGsqurrF6/i+2KxZGppyaWNTStuLuiUwtZOdiv9BTLFAAB6Bi65AAAAAElFTkSuQmCC", 333 | keyRed: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACeklEQVQ4jX2TO0zTURTGv3vv/9F/H4Q2LZRHa4o8fASGGuJjMBEluDAQI0Tj4ICTkUQZ3YyJxsHEyRijgwZjdFBjbHQCnDAGMQqioDYgMUBttdACbf/3HheNoK1nPDnfL/m+cw7r7OxEqbKJdMlYd0QWeryrK00pzZiJu9wPdKVuC6IMAGilxHkiI6hrd08K6orOzkHEp5HK2o2j/pr2683NvVkhDgqiBC8mlkTwm+a5i5tqu1oFIKSN4WAYTy5cgrOjHWdfj0cF0R3JmK8oICeVp6O66kTQ5QQUAbaNkYataOs7g3zfKYSWs9iTTB/IcX6oqAWNs+2tlRU+ZNIAA6Bp2D35FgP9p1HzOY59UqElncVQwBstCmCM5XUh/jSEwN7UPFqvXoFOBmCUQXIGArSiFgpSjY8mU7Pg6yCMwzIMCMMAAIx4PVCMvSseoq75Mh8mJUgB/PcIAURgBRvDwQBeecvGTKXu/2NhhYv6tu+LsfaxF5G1hXk4wrUAAMU4Ug4Lg+EQbtXVzQuiY5xobgNglbGdO+y1x90zUwGLMTgmJoBPX3BtW9P7ly3VybRmuH4YxnNTyvOcKAGsO6Qc0f6IJh4eTibcTiXhFwwoKDzzeXHP5+/XQTGNCJZtb9wYANhERytM4+YRVTA90kbIMJAwLfUoUpeLVdeOWKRinKhYXOCSqNep6wM93nLTbRfQ6PEAysZgoOrNjc0NoTzn3aXEAMAVsGwB0At5RCsrwBjDkumEDgq7C7bkRN9KqgFoUqmP5Q5zqWNLUxlm4oCSuByqnx41Xcct+ZfhYgAOTOmaPvR0IbFrMZ3JZw23EWdameT8q/brZf9XPwERTf+roi2jYwAAAABJRU5ErkJggg==", 334 | keyGreen: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACbElEQVQ4jX2TS0iUURzFz31933wz44yOjviYERTTIjCYkLJFkCW2cSGRUrRoYW1cuHAV7aJFm2odUYvCiFpUREOt1FZGWNEblUwTmxwdfI7OzHfvv8UgIc147upy7/n97zlwWVdXF4qJXFLQrAf1ud5cWbqZp6wZNe1/Qsrch6B1AJDFzCZLlqdKPgz2U/ef2BzmxSR0ym0qH6/pKL3d0ocNcRKCkrzgZE1wKuwrLdci3b5WwAgXwdE6XHxxHW3eTqQvf4hB0ANoFioIcDOmpKGz+kKwygsNgoaLmrF96G8fQG92ACa6BhxZOoEMP1UwgpBsf6S1MpTFCvI5JWbbPuHS0CAWa39AHNNgLSugkXCscAeMZYUSoO0tBNJHE4i33kRAWQjCBjQHCLJgBJ0znxPjqVkJ8Y8JDtuxoISV72msDDDsa0GA1DI0tf5NGxgwbF+h/GI5sNEqsHdl72Gbx/9FYGnRuNm+EJ/oeFPvbiUQ9tTmDwyHTDnwDEeh7jUkSNA5cJrbCdhkh9jBreeLPRNh7jD89HzBMmbgubX3e83bmiWxYvn4svWabH0VnJL5grcfmKHjdr14unk66YdXQ1UwMBjQq3KwR+FBpShOkkCOuzMuABiXzjqV1l3vmZy9UeLCG7WQTjom8Kwh48QjY+SYODihkDhp6rO8aqi2t9Q2/hxCTSUwcOEbrv7ov7MniizvKWbOAwzWuAO4KotIrBKMMchVL0hRHfldDU6LRd0ApNFmyim1Vw90Ngd+YRqARvBG4yQb9503juvuZgYAyTgmpFIjMy+Th38vrGe3NvwWm5YBpvk8ZP7L7qa/51LzXlKvOSsAAAAASUVORK5CYII=", 335 | minusSimple: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAL0lEQVQ4jWP8//8/IwMFgIkSzaMGUMkAFhiDkZHxHyka////z0QVFzCOJqThYAAAIOcKHde8N5QAAAAASUVORK5CYII=", 336 | plusSimple: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAARUlEQVQ4jdWRMQ4AIAjErP9/M+eiiyEyYCTeSjiagiRaIj2zHBYABlgdwR8FrDdGsvZI6ncJ3OGkWte81EusLzhKfEIwANMfFhcD7xBqAAAAAElFTkSuQmCC", 337 | pin: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAADBmlDQ1BpY2MAAHjaY2BgnuDo4uTKJMDAUFBUUuQe5BgZERmlwH6egY2BmYGBgYGBITG5uMAxIMCHgYGBIS8/L5UBFTAyMHy7xsDIwMDAcFnX0cXJlYE0wJpcUFTCwMBwgIGBwSgltTiZgYHhCwMDQ3p5SUEJAwNjDAMDg0hSdkEJAwNjAQMDg0h2SJAzAwNjCwMDE09JakUJAwMDg3N+QWVRZnpGiYKhpaWlgmNKflKqQnBlcUlqbrGCZ15yflFBflFiSWoKAwMD1A4GBgYGXpf8EgX3xMw8BSMDVQYqg4jIKAUICxE+CDEESC4tKoMHJQODAIMCgwGDA0MAQyJDPcMChqMMbxjFGV0YSxlXMN5jEmMKYprAdIFZmDmSeSHzGxZLlg6WW6x6rK2s99gs2aaxfWMPZ9/NocTRxfGFM5HzApcj1xZuTe4FPFI8U3mFeCfxCfNN45fhXyygI7BD0FXwilCq0A/hXhEVkb2i4aJfxCaJG4lfkaiQlJM8JpUvLS19QqZMVl32llyfvIv8H4WtioVKekpvldeqFKiaqP5UO6jepRGqqaT5QeuA9iSdVF0rPUG9V/pHDBYY1hrFGNuayJsym740u2C+02KJ5QSrOutcmzjbQDtXe2sHY0cdJzVnJRcFV3k3BXdlD3VPXS8Tbxsfd99gvwT//ID6wIlBS4N3hVwMfRnOFCEXaRUVEV0RMzN2T9yDBLZE3aSw5IaUNak30zkyLDIzs+ZmX8xlz7PPryjYVPiuWLskq3RV2ZsK/cqSql01jLVedVPrHzbqNdU0n22VaytsP9op3VXUfbpXta+x/+5Em0mzJ/+dGj/t8AyNmf2zvs9JmHt6vvmCpYtEFrcu+bYsc/m9lSGrTq9xWbtvveWGbZtMNm/ZarJt+w6rnft3u+45uy9s/4ODOYd+Hmk/Jn58xUnrU+fOJJ/9dX7SRe1LR68kXv13fc5Nm1t379TfU75/4mHeY7En+59lvhB5efB1/lv5dxc+NH0y/fzq64Lv4T8Ffp360/rP8f9/AA0ADzTzG2NJAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAACYktHRAD/h4/MvwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+ABCw0JBgCIkrwAAADNSURBVCjPdZA9DgFRFIVPMCFRSFQTBQ293z1oRWMJLECoLUHsAdEoRERjA0QvEhGNaBUa8inGzDwznFu83HvO/XlHKBQ5ZmTcTD8EHWCH7WQRhfGQVNZCtqQfEywmONhih1dYjPGxJetTcUTU63Yxj0lKqa2G0jooqrp3yVQbobUQBe4EMSZmfrPKNUBbQR9KHD166XabPux18La/9DRc+SiHwIozACcS/gTn6QIjRJErsCPyLWgCfdxzb/RM60SFCy2jlCf5LRhQQ//jDXleGJr4KuKfAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE2LTAxLTExVDEzOjA5OjA2KzAxOjAw8Rgr6AAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNi0wMS0xMVQxMzowOTowNiswMTowMIBFk1QAAAA3dEVYdGljYzpjb3B5cmlnaHQAQ29weXJpZ2h0IDE5OTkgQWRvYmUgU3lzdGVtcyBJbmNvcnBvcmF0ZWQxbP9tAAAAHHRFWHRpY2M6ZGVzY3JpcHRpb24ARG90IEdhaW4gMjAlk5c01gAAAB10RVh0aWNjOm1hbnVmYWN0dXJlcgBEb3QgR2FpbiAyMCWy7qr9AAAAFnRFWHRpY2M6bW9kZWwARG90IEdhaW4gMjAlMnX1qwAAAABJRU5ErkJggg==", 338 | pinActive: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAALGPC/xhBQAAACBjSFJNAAB6JQAAgIMAAPn/AACA6QAAdTAAAOpgAAA6mAAAF2+SX8VGAAAAe1BMVEX/////zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv/zBv////a7dsHAAAAJ3RSTlMAQPLg8wreGfQY6utB+GxtU/taZ7ywq+3urK+9aL6trpeZf4BeYC49Q3tiAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+ABCw0HIEwGOs8AAABlSURBVBjTXc1HEoAwCEBRYu+99879b2hGo0b+KrzJAACPKSpPY/Ckq8gz9BfAtBAtG6QcRFeewaVAf3g+ov8dgQCvwmeO4hviRECa5UVZ1U3bSVvaHv4NI4FpJrCsBLadwHGIxwkpXgeBl9dpZwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNi0wMS0xMVQxMzowNzozMyswMTowMDNmMx8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTYtMDEtMTFUMTM6MDc6MzIrMDE6MDDkTIAXAAAAAElFTkSuQmCC" 339 | }; --------------------------------------------------------------------------------