diff --git a/.gitignore b/.gitignore index b53f82c..d438ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ __pycache__ .idea log client/favicon_package_v0 -client/favicon.ico \ No newline at end of file +client/favicon.ico +client/favicon192.png_old \ No newline at end of file diff --git a/client/SEAWebClient.html b/client/SEAWebClient.html index 6089df5..7eaf6ef 100644 --- a/client/SEAWebClient.html +++ b/client/SEAWebClient.html @@ -10,6 +10,14 @@ SEAWebClient + + + + + + + + diff --git a/client/components/control/control.css b/client/components/control/control.css new file mode 100644 index 0000000..702b3c7 --- /dev/null +++ b/client/components/control/control.css @@ -0,0 +1,53 @@ +.control-global{ + width: 30px; + height: 30px; + border: 1px solid dimgray; + box-sizing: border-box; + transition: border 0.25s; + background-color: white; +} + +.animated:hover{ + border: 3px solid dimgray; + cursor: pointer; +} + +.control-global:hover~.tooltiptext{ + visibility: visible; + opacity: 1; + transition-delay: 2s; +} + +.control-global-active{ + display: block; +} + +.control-global-inactive{ + display: none; +} + +.tooltip { + position: relative; + display: inline-block; +} + +.tooltiptext { + visibility:hidden; + opacity: 0; + background-color:gray; + color: #fff; + text-align: center; + padding: 2px 0; + border-radius: 6px; + + /* Position the tooltip text - see examples below! */ + width: 130px; + height: 20px; + left: 100%; + top:3px; + + position: absolute; + z-index: 1; + + transition: opacity 0.25s; +} \ No newline at end of file diff --git a/client/components/control/control.js b/client/components/control/control.js new file mode 100644 index 0000000..0f189c2 --- /dev/null +++ b/client/components/control/control.js @@ -0,0 +1,62 @@ +class Control extends HTMLElement{ + + constructor(imgSrcMain, imgSrcAlt, tooltipDescription, callbackMain, callbackAlt = undefined){ + super(); + this.imgSrcMain = imgSrcMain; + this.imgSrcAlt = imgSrcAlt; + this.tooltipDescription = tooltipDescription; + this.callbackMain = callbackMain; + this.callbackAlt = callbackAlt; + } + + changeToMain(){ + this.permute("control-global-alt", "control-global-main"); + } + + changeToAlt(){ + this.permute("control-global-main", "control-global-alt"); + } + + permute(from, to){ + let fromElm = this.getElementsByClassName(from)[0]; + let toElm = this.getElementsByClassName(to)[0]; + + fromElm.classList.remove("control-global-active"); + toElm.classList.remove("control-global-inactive"); + + fromElm.classList.add("control-global-inactive"); + toElm.classList.add("control-global-active"); + + this.getAnimations() + } + + connectedCallback(){ + this.render(); + this.getElementsByClassName("control-global-active")[0].onclick = this.callbackMain; + if (this.callbackAlt !== undefined){ + let altElm = this.getElementsByClassName("control-global-inactive")[0]; + altElm.onclick = this.callbackAlt; + altElm.classList.add("animated"); + } + } + + render(){ + this.innerHTML = ` + +
+ + + ${this.tooltipDescription} +
+ `; + + } +} + +customElements.define("sea-control", Control) + +/* +connectedCallback : called when first added +disconnectedCallback : called when removed +attributeChangedCallback : called when attribute changes. attribute to be added in observedAttributes +*/ \ No newline at end of file diff --git a/client/components/divider/divider.css b/client/components/divider/divider.css new file mode 100644 index 0000000..22989d8 --- /dev/null +++ b/client/components/divider/divider.css @@ -0,0 +1,13 @@ +.divider-container{ + width: 10px; + height: 100%; + display: flex; + align-items: center; + justify-content: center; +} + +.divider{ + width: 2px; + height: 80%; + background-color:white; +} \ No newline at end of file diff --git a/client/components/divider/divider.js b/client/components/divider/divider.js new file mode 100644 index 0000000..9acce8b --- /dev/null +++ b/client/components/divider/divider.js @@ -0,0 +1,19 @@ +class VerticalDivider extends HTMLElement{ + constructor(){ + super(); + } + + connectedCallback(){ + this.render() + } + + render(){ + this.innerHTML = ` + +
+
+
` + } +} + +customElements.define("sea-vertical-divider", VerticalDivider) diff --git a/client/components/states_indicator/dates/dates.css b/client/components/states_indicator/dates/dates.css new file mode 100644 index 0000000..c5ba240 --- /dev/null +++ b/client/components/states_indicator/dates/dates.css @@ -0,0 +1,12 @@ +.dates{ + height: 30px; + + /* For text alignement */ + text-align: center; + vertical-align: middle; + line-height: 30px; + + cursor: default; + color: white; + +} \ No newline at end of file diff --git a/client/components/states_indicator/dates/dates.js b/client/components/states_indicator/dates/dates.js new file mode 100644 index 0000000..543d59e --- /dev/null +++ b/client/components/states_indicator/dates/dates.js @@ -0,0 +1,35 @@ +class DatesIndicator extends HTMLElement{ + constructor(oldestTimestamp, newestTimestamp){ + super(); + this.oldestDate = this.timestampToString(oldestTimestamp); + this.newestDate = this.timestampToString(newestTimestamp); + } + + timestampToString(timestamp){ + let date = new Date(timestamp); + return date.toUTCString(); + } + + update(oldestTimestamp, newestTimestamp){ + this.oldestDate = this.timestampToString(oldestTimestamp); + this.newestDate = this.timestampToString(newestTimestamp); + this.render() + } + + connectedCallback(){ + this.render(); + } + + render(){ + this.innerHTML = ` + +
+ ${this.oldestDate} + -> + ${this.newestDate} +
+ ` + } +} + +customElements.define("sea-dates-indicator", DatesIndicator) \ No newline at end of file diff --git a/client/components/states_indicator/live/live.css b/client/components/states_indicator/live/live.css new file mode 100644 index 0000000..7818f98 --- /dev/null +++ b/client/components/states_indicator/live/live.css @@ -0,0 +1,20 @@ +.live{ + width: 40px; + height: 30px; + /* For text alignement */ + text-align: center; + vertical-align: middle; + line-height: 30px; + + cursor: default; + font-weight: bold; + margin-right: 10px; +} + +.enabled{ + color: lime; +} + +.disabled{ + color: red; +} \ No newline at end of file diff --git a/client/components/states_indicator/live/live.js b/client/components/states_indicator/live/live.js new file mode 100644 index 0000000..94cf95c --- /dev/null +++ b/client/components/states_indicator/live/live.js @@ -0,0 +1,32 @@ +class LiveStateIndicator extends HTMLElement{ + constructor(){ + super(); + } + + changeToDisable(){ + let liveElm = this.getElementsByClassName("live")[0]; + liveElm.classList.remove("enabled"); + liveElm.classList.add("disabled"); + } + + changeToEnable(){ + let liveElm = this.getElementsByClassName("live")[0]; + liveElm.classList.remove("disabled"); + liveElm.classList.add("enabled"); + } + + connectedCallback(){ + this.render(); + } + + render(){ + this.innerHTML = ` + +
+ LIVE +
+ ` + } +} + +customElements.define("sea-live-state-indicator", LiveStateIndicator) \ No newline at end of file diff --git a/client/cssFiles/SEAWebClientGraphics.css b/client/cssFiles/SEAWebClientGraphics.css index 1dd1a81..2760d91 100644 --- a/client/cssFiles/SEAWebClientGraphics.css +++ b/client/cssFiles/SEAWebClientGraphics.css @@ -19,6 +19,18 @@ cursor: pointer; } +#control_bar{ + height: 100%; + background-color: dimgray; + border-top-left-radius: 12px; + border-top-right-radius: 12px; + padding-left: 12px; + padding-right: 12px; + display: flex; + column-gap: 5px; + margin-left: 100px; +} + /*********************/ .graphs-container{ height: 100%; diff --git a/client/jsFiles/SEAWebClientGraphics.js b/client/jsFiles/SEAWebClientGraphics.js index 5f9df6f..e6fbc89 100644 --- a/client/jsFiles/SEAWebClientGraphics.js +++ b/client/jsFiles/SEAWebClientGraphics.js @@ -193,6 +193,93 @@ function strFormat(val, significant_digits=13) { return evalue; } +let dummyCallback = function(){ + console.log("Dummy callback called"); +} + +// Defining keys for global controls + +let jumpKey = "jump-control"; +let goToNowKey = "go-to-now-control"; + +let zoomInKey = "zoom-in-control"; +let zoomOutKey = "zoom-out-control"; +let shiftOlderKey = "shift-older-control"; +let shiftNewerKey = "shift-newer-control"; + + +let xyKey = "xy-control"; +let cursorKey = "cursor-control"; +let legendsKey = "legends-control"; + +let globalControls = (function (){ + let controlsMap = {}; + + function loadControls(panel){ + let controlBar = document.createElement("div"); + controlBar.id = "control_bar" + panel.appendChild(controlBar); + + let jumpControl = new Control("res/jump.png", "res/jump_blocked.png", "Jump", dummyCallback); + let goToNowControl = new Control("res/go_to_now.png", "res/go_to_now_blocked.png", "Go to now", dummyCallback); + + let zoomInControl = new Control("res/zoom_in.png", "res/zoom_in_blocked.png", "Zoom in", dummyCallback); + let zoomOutControl = new Control("res/zoom_out.png", "res/zoom_out_blocked.png", "Zoon out", dummyCallback); + let shiftOlderControl = new Control("res/shift_older.png", "res/shift_older_blocked.png", "Shift to older", dummyCallback); + let shiftNewerControl = new Control("res/shift_newer.png", "res/shift_newer_blocked.png", "Shift to newer", dummyCallback); + + + let xyControl = new Control("res/y_direction.png", "res/x_direction.png", "Time<->Y zoom", dummyCallback, dummyCallback); + let cursorControl = new Control("res/remove_cursor.png", "res/remove_cursor_blocked.png", "Remove cursor",dummyCallback); + let legendsControl = new Control("res/display_legends.png", "res/remove_legends.png", "Legends", dummyCallback, dummyCallback); + + let now = Date.now(); + let old = now - 1000*3600; + let dates = new DatesIndicator(old, now); + let liveState = new LiveStateIndicator(); + + controlBar.appendChild(jumpControl) + controlBar.appendChild(goToNowControl) + goToNowControl.changeToAlt(); + controlBar.appendChild(new VerticalDivider()); + controlBar.appendChild(zoomInControl); + controlBar.appendChild(zoomOutControl); + controlBar.appendChild(shiftOlderControl); + controlBar.appendChild(shiftNewerControl); + controlBar.appendChild(new VerticalDivider()); + controlBar.appendChild(xyControl); + controlBar.appendChild(cursorControl); + cursorControl.changeToAlt(); + controlBar.appendChild(legendsControl); + legendsControl.changeToAlt(); + + panel.appendChild(dates) + panel.appendChild(liveState); + liveState.changeToDisable(); + liveState.style.marginLeft = "auto"; //sticks element to the right + dates.style.marginLeft = "auto"; + + controlsMap[jumpKey] = jumpControl; + controlsMap[goToNowKey] = goToNowControl; + controlsMap[zoomInKey] = zoomInControl; + controlsMap[zoomOutKey] = zoomOutControl; + controlsMap[shiftOlderKey] = shiftOlderControl; + controlsMap[shiftNewerKey] = shiftNewerControl; + controlsMap[xyKey] = xyControl; + controlsMap[cursorKey] = cursorControl; + controlsMap[legendsKey] = legendsControl; + } + + function getControlsMap(){ + return controlsMap; + } + + return { + loadControls: loadControls, + getControlsMap: getControlsMap, + } +})(); + let graphs = (function (){ let dataset_to_graph_map = {}; // a dictionnary mapping a variable name to a two values array, containing its graph index and its position inside the graph let blocks, liveMode=true, top_vars=[], bottom_vars=[]; @@ -837,6 +924,7 @@ let graphs = (function (){ container.parentNode.querySelector('.panel span').appendChild(zoomMode); } + // The cross to display "main" panel at the location of the graphs let gotoMainElm = document.createElement('div'); gotoMainElm.innerHTML = "×"; let currentSwiper = swiper[f]; diff --git a/client/res/display_legends.png b/client/res/display_legends.png new file mode 100644 index 0000000..6496116 Binary files /dev/null and b/client/res/display_legends.png differ diff --git a/client/res/go_to_now.png b/client/res/go_to_now.png new file mode 100644 index 0000000..fe7940a Binary files /dev/null and b/client/res/go_to_now.png differ diff --git a/client/res/go_to_now_blocked.png b/client/res/go_to_now_blocked.png new file mode 100644 index 0000000..c9714af Binary files /dev/null and b/client/res/go_to_now_blocked.png differ diff --git a/client/res/jump.png b/client/res/jump.png new file mode 100644 index 0000000..8e05956 Binary files /dev/null and b/client/res/jump.png differ diff --git a/client/res/jump_blocked.png b/client/res/jump_blocked.png new file mode 100644 index 0000000..eb37329 Binary files /dev/null and b/client/res/jump_blocked.png differ diff --git a/client/res/remove_cursor.png b/client/res/remove_cursor.png new file mode 100644 index 0000000..953bba4 Binary files /dev/null and b/client/res/remove_cursor.png differ diff --git a/client/res/remove_cursor_blocked.png b/client/res/remove_cursor_blocked.png new file mode 100644 index 0000000..d5d1242 Binary files /dev/null and b/client/res/remove_cursor_blocked.png differ diff --git a/client/res/remove_legends.png b/client/res/remove_legends.png new file mode 100644 index 0000000..94e7b4f Binary files /dev/null and b/client/res/remove_legends.png differ diff --git a/client/res/shift_newer.png b/client/res/shift_newer.png new file mode 100644 index 0000000..a6500c6 Binary files /dev/null and b/client/res/shift_newer.png differ diff --git a/client/res/shift_newer_blocked.png b/client/res/shift_newer_blocked.png new file mode 100644 index 0000000..1a28e34 Binary files /dev/null and b/client/res/shift_newer_blocked.png differ diff --git a/client/res/shift_older.png b/client/res/shift_older.png new file mode 100644 index 0000000..28af88b Binary files /dev/null and b/client/res/shift_older.png differ diff --git a/client/res/shift_older_blocked.png b/client/res/shift_older_blocked.png new file mode 100644 index 0000000..8cde5e4 Binary files /dev/null and b/client/res/shift_older_blocked.png differ diff --git a/client/res/x_direction.png b/client/res/x_direction.png new file mode 100644 index 0000000..f25141f Binary files /dev/null and b/client/res/x_direction.png differ diff --git a/client/res/y_direction.png b/client/res/y_direction.png new file mode 100644 index 0000000..9cf2784 Binary files /dev/null and b/client/res/y_direction.png differ diff --git a/client/res/zoom_in.png b/client/res/zoom_in.png new file mode 100644 index 0000000..228f626 Binary files /dev/null and b/client/res/zoom_in.png differ diff --git a/client/res/zoom_in_blocked.png b/client/res/zoom_in_blocked.png new file mode 100644 index 0000000..2648b1f Binary files /dev/null and b/client/res/zoom_in_blocked.png differ diff --git a/client/res/zoom_out.png b/client/res/zoom_out.png new file mode 100644 index 0000000..fa083f7 Binary files /dev/null and b/client/res/zoom_out.png differ diff --git a/client/res/zoom_out_blocked.png b/client/res/zoom_out_blocked.png new file mode 100644 index 0000000..1ac73cd Binary files /dev/null and b/client/res/zoom_out_blocked.png differ diff --git a/seaweb.py b/seaweb.py index 051340d..7bfcd98 100755 --- a/seaweb.py +++ b/seaweb.py @@ -38,6 +38,8 @@ def guess_mimetype(filename): mimetype = 'text/css' elif filename.endswith('.ico'): mimetype = 'image/x-icon' + elif filename.endswith(".png"): + mimetype = "image/png" else: mimetype = 'text/html' return mimetype @@ -147,11 +149,16 @@ def subdir_test_file(file): resp = flask.send_file("client/test/"+file, mimetype=guess_mimetype(file)) return resp +@app.route('/components/control/') +@app.route('/components/divider/') +@app.route('/components/states_indicator/dates/') +@app.route('/components/states_indicator/live/') +@app.route('/res/') @app.route('/jsFiles/') @app.route('/cssFiles/') @app.route('/externalFiles/') def subdir_file(file): - subdir = flask.request.path.split('/')[1] + subdir = "/".join(flask.request.path.split("/")[1:-1]) resp = flask.send_file("client/" + subdir+"/"+file, mimetype=guess_mimetype(file)) #resp.headers['Content-Security-Policy'] = "sandbox; script-src 'unsafe-inline';" return resp