diff --git a/app/index.html b/app/index.html index 0995112..0b46ce4 100644 --- a/app/index.html +++ b/app/index.html @@ -8,14 +8,21 @@ margin: 0; padding: 0; } + @keyframes example { + 0% {background-color: inherit;} + 25% {background-color: #3f51b5;} + 50% {background-color: #3f51b5;} + 100% {background-color: inherit;} + } +
- - + diff --git a/app/package-lock.json b/app/package-lock.json index ca11af8..2bf0fc8 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -650,6 +650,42 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA==" }, + "body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha1-5LoM5BCkaTYyM2dgnstOZVMSUGk=", + "dev": true, + "requires": { + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" + }, + "dependencies": { + "bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "dev": true, + "requires": { + "bytes": "1", + "string_decoder": "0.10" + } + }, + "string_decoder": { + "version": "0.10.31", + "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", + "dev": true + } + } + }, "body-parser": { "version": "1.18.3", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", @@ -1084,6 +1120,12 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, + "continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha1-vXJ6f67XfnH/OYWskzUakSczrQ8=", + "dev": true + }, "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", @@ -1204,36 +1246,6 @@ "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" }, - "cytoscape": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.3.1.tgz", - "integrity": "sha512-3IIh63Oz/dtiO1ibPb9CWHBPo90JZg/74EI+vDL/hamaP01bQ7Yx1r9oiS4AyTedvlUCio5j7Bu3NtRWxJHryw==", - "requires": { - "heap": "^0.2.6", - "lodash.debounce": "^4.0.8" - } - }, - "cytoscape-cola": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cytoscape-cola/-/cytoscape-cola-2.3.0.tgz", - "integrity": "sha512-xblxlCH8JXGLdH6XMUBJY3xBUJuL1rLy8bLMGvqkvoPHSbBfV+/klMWoqwervVKWOmFHPwUdihtxy8stG4RM5g==", - "requires": { - "webcola": "^3.3.6" - } - }, - "cytoscape-dagre": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/cytoscape-dagre/-/cytoscape-dagre-2.2.2.tgz", - "integrity": "sha512-zsg36qNwua/L2stJSWkcbSDcvW3E6VZf6KRe6aLnQJxuXuz89tMqI5EVYVKEcNBgzTEzFMFv0PE3T0nD4m6VDw==", - "requires": { - "dagre": "^0.8.2" - } - }, - "cytoscape-expand-collapse": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cytoscape-expand-collapse/-/cytoscape-expand-collapse-3.1.2.tgz", - "integrity": "sha512-yClfo0z2IqEkhlOipUh85k1kJGSgxaSKs4e30KO1mDIXPKOnvL/iQhVXmNt9+KmuIGg9YtNt09P0j3F4BN3WsA==" - }, "d": { "version": "1.0.0", "resolved": "http://registry.npmjs.org/d/-/d-1.0.0.tgz", @@ -1242,39 +1254,6 @@ "es5-ext": "^0.10.9" } }, - "d3-dispatch": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-1.0.5.tgz", - "integrity": "sha512-vwKx+lAqB1UuCeklr6Jh1bvC4SZgbSqbkGBLClItFBIYH4vqDJCA7qfoy14lXmJdnBOdxndAMxjCbImJYW7e6g==" - }, - "d3-drag": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/d3-drag/-/d3-drag-1.2.3.tgz", - "integrity": "sha512-8S3HWCAg+ilzjJsNtWW1Mutl74Nmzhb9yU6igspilaJzeZVFktmY6oO9xOh5TDk+BM2KrNFjttZNoJJmDnkjkg==", - "requires": { - "d3-dispatch": "1", - "d3-selection": "1" - } - }, - "d3-selection": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.3.2.tgz", - "integrity": "sha512-OoXdv1nZ7h2aKMVg3kaUFbLLK5jXUFAMLD/Tu5JA96mjf8f2a9ZUESGY+C36t8R1WFeWk/e55hy54Ml2I62CRQ==" - }, - "d3-timer": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-1.0.9.tgz", - "integrity": "sha512-rT34J5HnQUHhcLvhSB9GjCkN0Ddd5Y8nCwDBG2u6wQEeYxT/Lf51fTFFkldeib/sE/J0clIe0pnCfs6g/lRbyg==" - }, - "dagre": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/dagre/-/dagre-0.8.4.tgz", - "integrity": "sha512-Dj0csFDrWYKdavwROb9FccHfTC4fJbyF/oJdL9LNZJ8WUvl968P6PAKEriGqfbdArVJEmmfA+UyumgWEwcHU6A==", - "requires": { - "graphlib": "^2.1.7", - "lodash": "^4.17.4" - } - }, "date-now": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", @@ -1500,11 +1479,6 @@ "minimalistic-crypto-utils": "^1.0.0" } }, - "emitter-component": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz", - "integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY=" - }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -1589,6 +1563,16 @@ "prr": "~1.0.1" } }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "dev": true, + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, "es5-ext": { "version": "0.10.46", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.46.tgz", @@ -2044,21 +2028,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", "optional": true, "requires": { "delegates": "^1.0.0", @@ -2067,11 +2055,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2079,29 +2069,35 @@ }, "chownr": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "optional": true, "requires": { "ms": "2.0.0" @@ -2109,22 +2105,26 @@ }, "deep-extend": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -2132,12 +2132,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -2152,7 +2154,8 @@ }, "glob": { "version": "7.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -2165,12 +2168,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.21", - "bundled": true, + "resolved": false, + "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -2178,7 +2183,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -2186,7 +2192,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -2195,39 +2202,46 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -2235,7 +2249,8 @@ }, "minizlib": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -2243,19 +2258,22 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "optional": true }, "needle": { "version": "2.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", "optional": true, "requires": { "debug": "^2.1.2", @@ -2265,7 +2283,8 @@ }, "node-pre-gyp": { "version": "0.10.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -2282,7 +2301,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -2291,12 +2311,14 @@ }, "npm-bundled": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", "optional": true }, "npm-packlist": { "version": "1.1.10", - "bundled": true, + "resolved": false, + "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -2305,7 +2327,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -2316,33 +2339,39 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -2351,17 +2380,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.7", - "bundled": true, + "resolved": false, + "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -2372,14 +2404,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -2393,7 +2427,8 @@ }, "rimraf": { "version": "2.6.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "optional": true, "requires": { "glob": "^7.0.5" @@ -2401,36 +2436,43 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true + "resolved": false, + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.5.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2439,7 +2481,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -2447,19 +2490,22 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", "optional": true, "requires": { "chownr": "^1.0.1", @@ -2473,12 +2519,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", "optional": true, "requires": { "string-width": "^1.0.2" @@ -2486,11 +2534,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" } } }, @@ -2578,19 +2628,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==" }, - "graphlib": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/graphlib/-/graphlib-2.1.7.tgz", - "integrity": "sha512-TyI9jIy2J4j0qgPmOOrHTCtpPqJGN/aurBwc6ZT+bRii+di1I+Wv3obRhVrmBEXet+qkMaEX67dXrwsd3QQM6w==", - "requires": { - "lodash": "^4.17.5" - } - }, - "hammerjs": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/hammerjs/-/hammerjs-2.0.8.tgz", - "integrity": "sha1-BO93hiz/K7edMPdpIJWTAiK/YPE=" - }, "handle-thing": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-2.0.0.tgz", @@ -2673,11 +2710,6 @@ "minimalistic-assert": "^1.0.1" } }, - "heap": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", - "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" - }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -3139,11 +3171,6 @@ "css-vendor": "^0.3.8" } }, - "keycharm": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/keycharm/-/keycharm-0.2.0.tgz", - "integrity": "sha1-+m6i5DuQpoAohD0n8gddNajD5vk=" - }, "keycode": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz", @@ -3167,6 +3194,12 @@ "invert-kv": "^2.0.0" } }, + "livereload-js": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", + "dev": true + }, "loader-runner": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.1.tgz", @@ -3957,14 +3990,6 @@ "object-assign": "^4.1.1" } }, - "propagating-hammerjs": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/propagating-hammerjs/-/propagating-hammerjs-1.4.6.tgz", - "integrity": "sha1-/tAOmwB2f/1C0U9bUxvEk+tnLjc=", - "requires": { - "hammerjs": "^2.0.6" - } - }, "proxy-addr": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", @@ -4107,17 +4132,6 @@ "pure-color": "^1.2.0" } }, - "react-cytoscape": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/react-cytoscape/-/react-cytoscape-1.0.6.tgz", - "integrity": "sha512-bCBLyctKVDuxe9W2b/TaBGwk+Q1/AX33k+ocD94r0Pq4KQGAJNS91m3Z/czSOkDHcK1VmbImxnH2Q75J5lxeiQ==", - "requires": { - "cytoscape": "^3.2.5", - "cytoscape-cola": "^2.0.0", - "cytoscape-dagre": "^2.1.0", - "fsevents": "*" - } - }, "react-dom": { "version": "16.3.3", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.3.3.tgz", @@ -4320,6 +4334,12 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, + "safe-json-parse": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "integrity": "sha1-PnZyPjjf3aE8mx0poeB//uSzC1c=", + "dev": true + }, "safe-regex": { "version": "1.1.0", "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", @@ -4873,6 +4893,12 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -4992,6 +5018,37 @@ "setimmediate": "^1.0.4" } }, + "tiny-lr": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", + "integrity": "sha512-44yhA3tsaRoMOjQQ+5v5mVdqef+kH6Qze9jTpqtVufgYjYt08zyZAwNwwVBj3i1rJMnR52IxOW0LK0vBzgAkuA==", + "dev": true, + "requires": { + "body": "^5.1.0", + "debug": "^3.1.0", + "faye-websocket": "~0.10.0", + "livereload-js": "^2.3.0", + "object-assign": "^4.1.0", + "qs": "^6.4.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "to-array": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/to-array/-/to-array-0.1.4.tgz", @@ -5244,18 +5301,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, - "vis": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/vis/-/vis-4.21.0.tgz", - "integrity": "sha1-3XFji/9/ZJXQC8n0DCU1JhM97Ws=", - "requires": { - "emitter-component": "^1.1.1", - "hammerjs": "^2.0.8", - "keycharm": "^0.2.0", - "moment": "^2.18.1", - "propagating-hammerjs": "^1.4.6" - } - }, "vm-browserify": { "version": "0.0.4", "resolved": "http://registry.npmjs.org/vm-browserify/-/vm-browserify-0.0.4.tgz", @@ -5290,16 +5335,6 @@ "minimalistic-assert": "^1.0.0" } }, - "webcola": { - "version": "3.3.8", - "resolved": "https://registry.npmjs.org/webcola/-/webcola-3.3.8.tgz", - "integrity": "sha512-WVDTdHS1SaqYCUJGPdbOhqj44mchDyTC78tozUdqJllwYeJ2554+BWkJfc5kNphT8foip2StCMw1FWsIvGmv9w==", - "requires": { - "d3-dispatch": "^1.0.3", - "d3-drag": "^1.0.4", - "d3-timer": "^1.0.5" - } - }, "webpack": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.28.2.tgz", @@ -5481,6 +5516,16 @@ } } }, + "webpack-livereload-plugin": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-livereload-plugin/-/webpack-livereload-plugin-2.2.0.tgz", + "integrity": "sha512-sx9xA5mHoNOUgLQI0PmXT3KV9ecsVmUaTgr+fsoL69qAOHw/7VzkL1+ZMDQ8n0dPbWounswK6cBRSgMod7Nhgg==", + "dev": true, + "requires": { + "portfinder": "^1.0.17", + "tiny-lr": "^1.1.1" + } + }, "webpack-log": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/webpack-log/-/webpack-log-2.0.0.tgz", diff --git a/app/package.json b/app/package.json index e0bd397..95f840c 100644 --- a/app/package.json +++ b/app/package.json @@ -10,31 +10,27 @@ "author": "", "license": "ISC", "devDependencies": { - "@material-ui/icons": "^3.0.1" + "@material-ui/icons": "^3.0.1", + "webpack-livereload-plugin": "^2.2.0" }, "dependencies": { - "@types/node": "^10.12.18", - "awesome-typescript-loader": "^5.2.1", - "source-map-loader": "^0.2.4", - "typescript": "^3.2.2", - "webpack": "^4.28.2", - "webpack-cli": "^3.1.2", - "webpack-dev-server": "^3.1.14", "@material-ui/core": "^3.7.1", + "@types/node": "^10.12.18", "@types/react": "^16.7.18", "@types/react-dom": "^16.0.11", "@types/socket.io-client": "^1.4.32", "@types/vis": "^4.21.9", - "cytoscape": "^3.3.1", - "cytoscape-dagre": "^2.2.2", - "cytoscape-expand-collapse": "^3.1.2", + "awesome-typescript-loader": "^5.2.1", "jquery": "^3.3.1", "lodash.throttle": "^4.1.1", "react": "^16.3.2", - "react-cytoscape": "^1.0.6", "react-dom": "^16.3.3", "react-json-view": "^1.19.1", "socket.io-client": "^2.2.0", - "vis": "^4.21.0" + "source-map-loader": "^0.2.4", + "typescript": "^3.2.2", + "webpack": "^4.28.2", + "webpack-cli": "^3.1.2", + "webpack-dev-server": "^3.1.14" } } diff --git a/app/src/App.tsx b/app/src/App.tsx index 34ae632..1c42ac1 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -3,7 +3,7 @@ import * as q from '../../backend/src/Model' import { Tree } from './components/Tree' import TitleBar from './components/TitleBar' -import { Sidebar } from './components/Sidebar' +import Sidebar from './components/Sidebar/Sidebar' import { withTheme, Theme } from '@material-ui/core/styles' @@ -12,6 +12,7 @@ class State { } interface Props { + name: string theme: Theme } @@ -21,34 +22,47 @@ class App extends React.Component { this.state = { selectedNode: undefined, } + } - console.log('asd', this.props) - this.theme = this.props.theme - this.styles = { - primaryText: { - backgroundColor: this.theme.palette.background.default, - padding: `${this.theme.spacing.unit}px ${this.theme.spacing.unit * 2}px`, - color: this.theme.palette.text.primary, + private getStyles(): {[s: string]: React.CSSProperties} { + const { theme } = this.props + return { + left: { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + height: 'calc(100vh - 64px)', + float: 'left', + width: '60vw', + overflowY: 'scroll', + overflowX: 'hidden', + display: 'block', }, - primaryColor: { - backgroundColor: this.theme.palette.background.default, - // padding: `${this.theme.spacing.unit}px ${this.theme.spacing.unit * 2}px`, - color: this.theme.palette.common.white, + right: { + height: 'calc(100vh - 64px)', + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + float: 'right', width: '40vw', + overflowY: 'scroll', + overflowX: 'hidden', + display: 'block', }, } } - private theme: Theme - private styles: any - public render() { - return
+ return
- { - this.setState({ selectedNode: node }) - console.log('did select', node) - }} /> -
+
+
+ { + this.setState({ selectedNode: node }) + }} /> +
+
+ +
+
+
} } diff --git a/app/src/components/Sidebar.tsx b/app/src/components/Sidebar.tsx deleted file mode 100644 index a2d6163..0000000 --- a/app/src/components/Sidebar.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import * as React from 'react' -import * as q from '../../../backend/src/Model' -import Drawer from '@material-ui/core/Drawer' -import TextField from '@material-ui/core/TextField' -import Paper from '@material-ui/core/Paper' -import { ValueRenderer } from './ValueRenderer' - -interface Props { - node?: q.TreeNode | undefined -} - -interface State { - node?: q.TreeNode | undefined -} - -export class Sidebar extends React.Component { - private updateNode: (node?: q.TreeNode | undefined) => void - constructor(props: any) { - super(props) - this.state = {} - this.updateNode = (node) => { - if (!node) { - this.setState(this.state) - } else { - this.setState({ node }) - } - } - } - - public componentWillReceiveProps(nextProps: Props) { - this.props.node && this.props.node.removeListener('update', this.updateNode) - nextProps.node && nextProps.node.on('update', this.updateNode) - nextProps.node && this.updateNode(nextProps.node) - } - - private open() { - return this.props.node !== undefined - } - - public render() { - return - {this.renderNode()} - - } - - private renderNode() { - const style: React.CSSProperties = { display: 'block', width: '40vw' } - const topicStyle: React.CSSProperties = { width: '100%' } - - if (!this.state.node) { - return null - } - - return
- - - - -
- } -} diff --git a/app/src/components/Sidebar/NodeStats.tsx b/app/src/components/Sidebar/NodeStats.tsx new file mode 100644 index 0000000..477d29a --- /dev/null +++ b/app/src/components/Sidebar/NodeStats.tsx @@ -0,0 +1,41 @@ +import * as React from 'react' +import * as q from '../../../../backend/src/Model' +// import Drawer from '@material-ui/core/Drawer' +import { Typography } from '@material-ui/core' +import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' + +interface Props { + node: q.TreeNode, + classes: any, + theme: Theme +} + +interface State { + node?: q.TreeNode | undefined +} + +class NodeStats extends React.Component { + constructor(props: any) { + super(props) + } + + public static styles: StyleRulesCallback = (theme: Theme) => { + return { + } + } + + public render() { + const leafes = this.props.node.leafes() + const leafMessages = leafes + .map(leaf => leaf.messages) + .reduce((a, b) => a + b) + + return +

Messages: #{this.props.node.messages}

+

Subtopics: {leafes.length}

+

Messages Subtopics: #{leafMessages}

+
+ } +} + +export default withStyles(NodeStats.styles, { withTheme: true })(NodeStats) diff --git a/app/src/components/Sidebar/Sidebar.tsx b/app/src/components/Sidebar/Sidebar.tsx new file mode 100644 index 0000000..b055e24 --- /dev/null +++ b/app/src/components/Sidebar/Sidebar.tsx @@ -0,0 +1,105 @@ +import * as React from 'react' +import * as q from '../../../../backend/src/Model' +// import Drawer from '@material-ui/core/Drawer' +import ExpansionPanel from '@material-ui/core/ExpansionPanel' +import ExpansionPanelSummary from '@material-ui/core/ExpansionPanelSummary' +import ExpansionPanelDetails from '@material-ui/core/ExpansionPanelDetails' +import ExpandMore from '@material-ui/icons/ExpandMore' +import ValueRenderer from './ValueRenderer' +import NodeStats from './NodeStats' +import Topic from './Topic' +import { Typography } from '@material-ui/core' +import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' + +interface Props { + node?: q.TreeNode | undefined, + classes: any, + theme: Theme +} + +interface State { + node?: q.TreeNode | undefined +} + +class Sidebar extends React.Component { + private updateNode: (node?: q.TreeNode | undefined) => void + constructor(props: any) { + super(props) + this.state = {} + this.updateNode = (node) => { + if (!node) { + this.setState(this.state) + } else { + this.setState({ node }) + } + } + } + + public static styles: StyleRulesCallback = (theme: Theme) => { + return { + drawer: { + display: 'block', + height: '100%', + }, + valuePaper: { + margin: `${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px ${theme.spacing.unit}px`, + }, + heading: { + fontSize: theme.typography.pxToRem(15), + fontWeight: theme.typography.fontWeightRegular, + }, + } + } + + public componentWillReceiveProps(nextProps: Props) { + this.props.node && this.props.node.removeListener('update', this.updateNode) + nextProps.node && nextProps.node.on('update', this.updateNode) + nextProps.node && this.updateNode(nextProps.node) + } + + private open(): boolean { + return true + } + + public render() { + return
+ {this.renderNode()} +
+ } + + private renderNode() { + const { classes } = this.props + if (!this.state.node) { + return null + } + + return
+ + }> + Topic + + + + + + + }> + Value + + + + + + + }> + Stats + + + + + +
+ } +} + +export default withStyles(Sidebar.styles, { withTheme: true })(Sidebar) diff --git a/app/src/components/Sidebar/Topic.tsx b/app/src/components/Sidebar/Topic.tsx new file mode 100644 index 0000000..d636b24 --- /dev/null +++ b/app/src/components/Sidebar/Topic.tsx @@ -0,0 +1,52 @@ +import * as React from 'react' +import * as q from '../../../../backend/src/Model' +import { withStyles, Theme, StyleRulesCallback } from '@material-ui/core/styles' +import Button from '@material-ui/core/Button' + +interface Props { + classes: any + theme: Theme + node: q.TreeNode + selected?: q.TreeNode +} + +class Topic extends React.Component { + public static styles: StyleRulesCallback = (theme: Theme) => ({ + button: { + textTransform: 'none', + padding: '3px 5px 3px 5px', + minWidth: '30px', + }, + }) + + public render() { + const { node } = this.props + let i = 0 + const breadCrumps = node.branch() + .map(node => node.sourceEdge) + .filter(edge => Boolean(edge)) + .map(edge => + [], + ) + + if (breadCrumps.length === 0) { + return null + } + + const joinedBreadCrumps = breadCrumps.reduce((prev, current) => + prev.concat([/]).concat(current), + ) + + return {joinedBreadCrumps} + } +} + +export default withStyles(Topic.styles, { withTheme: true })(Topic) diff --git a/app/src/components/ValueRenderer.tsx b/app/src/components/Sidebar/ValueRenderer.tsx similarity index 71% rename from app/src/components/ValueRenderer.tsx rename to app/src/components/Sidebar/ValueRenderer.tsx index 8ed2f6d..59d6a17 100644 --- a/app/src/components/ValueRenderer.tsx +++ b/app/src/components/Sidebar/ValueRenderer.tsx @@ -1,16 +1,18 @@ import * as React from 'react' -import * as q from '../../../backend/src/Model' +import * as q from '../../../../backend/src/Model' import { default as ReactJson } from 'react-json-view' +import { withTheme, Theme } from '@material-ui/core/styles' interface Props { node?: q.TreeNode | undefined + theme: Theme } interface State { node?: q.TreeNode | undefined } -export class ValueRenderer extends React.Component { +class ValueRenderer extends React.Component { private updateNode: (node?: q.TreeNode | undefined) => void constructor(props: any) { super(props) @@ -30,6 +32,10 @@ export class ValueRenderer extends React.Component { nextProps.node && this.updateNode(nextProps.node) } + private style = (theme: Theme) => { + + } + public render() { const node = this.props.node if (!node || !node.message) { @@ -48,7 +54,14 @@ export class ValueRenderer extends React.Component { } else if (typeof json === 'number') { return this.renderRawValue(node.message.value) } else { - return + const theme = this.props.theme.palette.type === 'dark' ? 'monokai' : 'bright:inverted' + return { + console.log(val) + }} /> } } @@ -62,6 +75,8 @@ export class ValueRenderer extends React.Component { padding: '12px 5px 12px 5px', } - return
{value}
+ return
{value}
} } + +export default withTheme()(ValueRenderer) diff --git a/app/src/components/Sidebar/index.ts b/app/src/components/Sidebar/index.ts new file mode 100644 index 0000000..fa784d0 --- /dev/null +++ b/app/src/components/Sidebar/index.ts @@ -0,0 +1,3 @@ +import Sidebar from './Sidebar' + +export { Sidebar } diff --git a/app/src/components/Tree.tsx b/app/src/components/Tree.tsx index 810ff87..e25e020 100644 --- a/app/src/components/Tree.tsx +++ b/app/src/components/Tree.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import * as io from 'socket.io-client' import * as q from '../../../backend/src/Model' -import { TreeNode } from './TreeNode' +import TreeNode from './TreeNode' import List from '@material-ui/core/List' const throttle = require('lodash.throttle') @@ -18,10 +18,14 @@ class TreeState { export interface TreeNodeProps { didSelectNode?: (node: q.TreeNode) => void } +declare const performance: any export class Tree extends React.Component { private socket: SocketIOClient.Socket - private renderDuration: number = 300 + private renderDuration: number = 200 + private updateTimer?: any + private lastUpdate: number = 0 + private perf:number = 0 constructor(props: any) { super(props) @@ -30,21 +34,36 @@ export class Tree extends React.Component { this.socket = io('http://localhost:3000') } - public componentDidMount() { - let updateState = throttle((state: any) => { - this.setState(state) - updateState.cancel() - updateState = throttle(() => { - this.setState(state) - }, Math.max(this.renderDuration * 5, 300), { trailing: true }) - }, 1000) + public time(): number { + const time = performance.now() - this.perf + this.perf = performance.now() + return time + } + + public throttledStateUpdate(state: any) { + if (this.updateTimer) { + return + } + + const updateInterval = Math.max(this.renderDuration * 5, 200) + const timeUntilNextUpdate = updateInterval - (performance.now() - this.lastUpdate) + + this.updateTimer = setTimeout(() => { + this.lastUpdate = performance.now() + this.updateTimer && clearTimeout(this.updateTimer) + this.updateTimer = undefined + this.setState(state) + }, Math.max(0, timeUntilNextUpdate)) + } + + public componentDidMount() { this.socket.on('message', (msg: any) => { const edges = msg.topic.split('/') const node = q.TreeNodeFactory.fromEdgesAndValue(edges, Buffer.from(msg.payload, 'base64').toString()) this.state.tree.updateWithNode(node.firstNode()) - updateState({ msg, tree: this.state.tree }) + this.throttledStateUpdate({ msg, tree: this.state.tree }) }) } @@ -56,10 +75,14 @@ export class Tree extends React.Component { return
this.renderDuration = ms} + key="rootNode" + performanceCallback={(ms: number) => { + this.renderDuration = ms + }} />
diff --git a/app/src/components/TreeNode.tsx b/app/src/components/TreeNode.tsx index 00d35f5..a8f59f1 100644 --- a/app/src/components/TreeNode.tsx +++ b/app/src/components/TreeNode.tsx @@ -1,15 +1,21 @@ import * as React from 'react' import * as q from '../../../backend/src/Model' -import List from '@material-ui/core/List' -import ListItem from '@material-ui/core/ListItem' -import Collapse from '@material-ui/core/Collapse' +import { List, ListItem, Collapse, Typography } from '@material-ui/core' +import { withTheme, Theme } from '@material-ui/core/styles' +const throttle = require('lodash.throttle') +import Slide from '@material-ui/core/Slide' +import { isElementInViewport } from './helper/isElementInViewport' +const collapseLimit = 3 +declare var performance: any export interface TreeNodeProps { + isRoot?: boolean treeNode: q.TreeNode name?: string | undefined collapsed?: boolean | undefined performanceCallback?: ((ms: number) => void) | undefined didSelectNode?: (node: q.TreeNode) => void + theme: Theme } interface TreeNodeState { @@ -19,28 +25,59 @@ interface TreeNodeState { edgeCount: number } -const collapseLimit = 0 -declare var performance: any - -export class TreeNode extends React.Component { +class TreeNode extends React.Component { private dirty: boolean = true private willUpdateTime: number = performance.now() + private titleRef = React.createRef() + private markAsDirty = () => { + this.dirty = true + if (!this.props.isRoot) { + this.indicateUpdate() + } + } - constructor(props: TreeNodeProps, state: TreeNodeState) { - super(props, state) + private indicateUpdate = throttle(() => { + const title: any = this.titleRef.current + if (title && isElementInViewport(title)) { + title.style.animation = 'example 0.5s' + setTimeout(() => { + title.style.animation = '' + }, 500) + } + }, 500) + constructor(props: TreeNodeProps) { + super(props) const edgeCount = Object.keys(props.treeNode.edges).length const collapsed = edgeCount > collapseLimit - - this.props.treeNode.on('update', () => { - this.dirty = true - }) - this.state = { collapsed, edgeCount, collapsedOverride: props.collapsed, title: props.name } } + public componentDidMount() { + this.props.treeNode.on('update', this.markAsDirty) + } + + public componentWillUnmount() { + this.props.treeNode.removeListener('update', this.markAsDirty) + } + + private getStyles() { + const { theme } = this.props + return { + collapsedSubnodes: { + color: theme.palette.text.secondary, + }, + container: { + display: 'block', + }, + } + } + public setState(state: any) { - this.dirty = true + this.dirty = this.state.collapsed !== state.collapsed + || this.state.collapsedOverride !== state.collapsedOverride + || this.state.edgeCount !== state.edgeCount + super.setState(state) } @@ -49,7 +86,6 @@ export class TreeNode extends React.Component { } public componentDidUpdate() { - this.dirty = false if (this.props.performanceCallback) { const renderTime = performance.now() - this.willUpdateTime this.props.performanceCallback(renderTime) @@ -73,18 +109,25 @@ export class TreeNode extends React.Component { private renderNodes() { const edges = Object.values(this.props.treeNode.edges) const listItemStyle = { - padding: '3px 8px 3px 8px', + padding: '3px 8px 0px 8px', } + const listStyle = { - padding: '3px 8px 3px 16px', + padding: '3px 8px 0px 16px', } if (edges.length > 0) { const listItems = edges .map(edge => edge.target) - .map(node => - - ) + .map(node => ( + + + + )) return {listItems} @@ -100,13 +143,10 @@ export class TreeNode extends React.Component { } const name = this.state.title || (this.props.treeNode.sourceEdge && this.props.treeNode.sourceEdge.name) - return this.toggle()}>{name} - } - - private getStyle(): React.CSSProperties { - return { - display: 'block', - } + return { + this.toggle() + this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode) + }>{name} } public componentWillReceiveProps() { @@ -128,7 +168,7 @@ export class TreeNode extends React.Component { ? this.props.didSelectNode && this.props.didSelectNode(this.props.treeNode)} - > = {this.props.treeNode.message.toString()} + > = {this.props.treeNode.message.value.toString()} : null } @@ -137,23 +177,33 @@ export class TreeNode extends React.Component { } private renderTitleLine() { - const style = { + const style: React.CSSProperties = { lineHeight: '1em', + whiteSpace: 'nowrap', + width: '15em', } - return
{this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()}
+ return
+ + {this.renderExpander()} {this.renderSourceEdge()} {this.renderCollapsedSubnodes()} {this.renderValue()} + +
} public render() { + this.dirty = false + const nodeStyle: React.CSSProperties = { display: 'block', } - return
- {this.renderTitleLine()} -
- {this.clear()} -
- {this.collapsed() ? null : this.renderNodes()} + return
+ { this.renderTitleLine() } +
+ { this.clear() } +
+ { this.collapsed() ? null : this.renderNodes() }
@@ -178,10 +228,8 @@ export class TreeNode extends React.Component { return null } - const style = { - color: '#333', - } - return ({this.props.treeNode.leafes().length} nodes) + const messages = this.props.treeNode.leafes().map(leaf => leaf.messages).reduce((a, b) => a + b) + return ({this.props.treeNode.leafes().length} nodes, {messages} messages) } private subnodesStyle(): React.CSSProperties { @@ -190,3 +238,5 @@ export class TreeNode extends React.Component { } } } + +export default withTheme()(TreeNode) diff --git a/app/src/components/helper/isElementInViewport.ts b/app/src/components/helper/isElementInViewport.ts new file mode 100644 index 0000000..5def19a --- /dev/null +++ b/app/src/components/helper/isElementInViewport.ts @@ -0,0 +1,12 @@ +declare const window: any +declare const document: any + +export function isElementInViewport(el: any) { + const rect = el.getBoundingClientRect() + return ( + rect.top >= 0 && + rect.left >= 0 && + rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ + rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ + ) +} diff --git a/app/webpack.config.js b/app/webpack.config.js index 8d577c1..450109e 100644 --- a/app/webpack.config.js +++ b/app/webpack.config.js @@ -1,3 +1,5 @@ +var LiveReloadPlugin = require('webpack-livereload-plugin'); + module.exports = { entry: "./src/index.tsx", output: { @@ -30,6 +32,10 @@ module.exports = { ] }, + plugins: [ + new LiveReloadPlugin({}) + ], + // When importing a module whose path matches one of the following, just // assume a corresponding global variable exists and use that instead. // This is important because it allows us to avoid bundling all of our diff --git a/backend/src/index.ts b/backend/src/index.ts index 95f1979..6561da6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,10 +1,10 @@ +import * as socketIO from 'socket.io' +const http = require('http') + import { TopicProperties, Tree, TreeNodeFactory } from './Model' import { MqttSource, DataSource } from './DataSource' -import * as socketIO from 'socket.io' - -const http = require('http') -const options = { url: 'mqtt://nodered' } +const options = { url: 'mqtt://test.mosquitto.org' } const dataSource = new MqttSource() const a: any[] = [] @@ -23,7 +23,9 @@ server.listen(3000) const state = dataSource.connect(options) dataSource.onMessage((topic: string, payload: Buffer) => { let buffer = payload - a.push({ topic, payload: buffer.toString('base64') }) + if (a.length < 30) { + a.push({ topic, payload: buffer.toString('base64') }) + } if (buffer.length > 10000) { buffer = buffer.slice(0, 10000) } diff --git a/electron.js b/electron.js index 68cbedc..e1a4d18 100644 --- a/electron.js +++ b/electron.js @@ -7,7 +7,13 @@ let mainWindow function createWindow () { // Create the browser window. - mainWindow = new BrowserWindow({width: 1024, height: 600}) + mainWindow = new BrowserWindow({ + width: 1024, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) // and load the index.html of the app. mainWindow.loadFile('app/index.html') diff --git a/icon.xcf b/icon.xcf new file mode 100644 index 0000000..be4682b Binary files /dev/null and b/icon.xcf differ diff --git a/tslint.json b/tslint.json index 1f942fb..283120c 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ "max-line-length": [true, 200], "member-access": true, "no-else-after-return": false, - "align": false + "align": false, + "variable-name": false } }