From d245065045941bd382b02edd47c0dc2b5676596c Mon Sep 17 00:00:00 2001 From: bbm Date: Tue, 27 Sep 2022 17:23:11 -0400 Subject: [PATCH 001/221] adding basic socketio webserver for gui --- refl1d/webserver.py | 67 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 refl1d/webserver.py diff --git a/refl1d/webserver.py b/refl1d/webserver.py new file mode 100644 index 00000000..7dee2fe2 --- /dev/null +++ b/refl1d/webserver.py @@ -0,0 +1,67 @@ +# from .main import setup_bumps + +from aiohttp import web +import socketio +from pathlib import Path +import json +from copy import deepcopy + +from bumps.fitters import DreamFit, LevenbergMarquardtFit, SimplexFit, DEFit, MPFit, BFGSFit + +FITTERS = (DreamFit, LevenbergMarquardtFit, SimplexFit, DEFit, MPFit, BFGSFit) +FITTER_DEFAULTS = {} +for fitter in FITTERS: + FITTER_DEFAULTS[fitter.id] = { + "name": fitter.name, + "settings": dict(fitter.settings) + } + +# set up mutable state of fitters: +fitter_settings = deepcopy(FITTER_DEFAULTS) +active_fitter = "amoeba" + +sio = socketio.AsyncServer(cors_allowed_origins="*") +app = web.Application() +static_dir_path = Path(__file__).parent / 'webview' +sio.attach(app) + +async def index(request): + """Serve the client-side application.""" + # redirect to static built site: + return web.HTTPFound('/static/index.html') + +@sio.event +async def connect(sid, environ): + print("connect ", sid) + sio.enter_room(sid, "situation") + await sio.emit("fitter-defaults", FITTER_DEFAULTS, to=sid) + await sio.emit("fitter-settings", fitter_settings, to=sid) + await sio.emit("fitter-active", active_fitter, to=sid) + +@sio.on("fitter-active") +async def new_fitter_active(sid, data): + # using global state here: + global active_fitter + active_fitter = data + await sio.emit("fitter-active", data, room="situation") + +@sio.on("fitter-settings") +async def new_fitter_settings(sid, data): + global fitter_settings + fitter_settings.update(data) + await sio.emit("fitter-settings", fitter_settings, room="situation") + +# async def get_sessions(sid): +# return web.Response(text=json.dumps(list(sessions)), content_type='application/json') + +@sio.event +def disconnect(sid): + print('disconnect ', sid) + +app.router.add_static('/static', static_dir_path) +# app.router.add_static('/static', os.path.join(currdir, 'webview')) +app.router.add_get('/', index) + + +if __name__ == '__main__': + web.run_app(app) From 4242336fe742138662c6c99f17112361fc44d725 Mon Sep 17 00:00:00 2001 From: bbm Date: Tue, 27 Sep 2022 17:26:44 -0400 Subject: [PATCH 002/221] initial commit of web gui for refl1d --- refl1d-web-gui/README.md | 29 +++ refl1d-web-gui/index.html | 13 ++ refl1d-web-gui/package.json | 18 ++ refl1d-web-gui/src/App.vue | 175 +++++++++++++++ .../src/assets/refl1d-icon_256x256x32.png | Bin 0 -> 54618 bytes refl1d-web-gui/src/components/FitOptions.vue | 210 ++++++++++++++++++ refl1d-web-gui/src/fitter_defaults.ts | 62 ++++++ refl1d-web-gui/src/main.js | 6 + refl1d-web-gui/vite.config.js | 15 ++ 9 files changed, 528 insertions(+) create mode 100644 refl1d-web-gui/README.md create mode 100644 refl1d-web-gui/index.html create mode 100644 refl1d-web-gui/package.json create mode 100644 refl1d-web-gui/src/App.vue create mode 100644 refl1d-web-gui/src/assets/refl1d-icon_256x256x32.png create mode 100644 refl1d-web-gui/src/components/FitOptions.vue create mode 100644 refl1d-web-gui/src/fitter_defaults.ts create mode 100644 refl1d-web-gui/src/main.js create mode 100644 refl1d-web-gui/vite.config.js diff --git a/refl1d-web-gui/README.md b/refl1d-web-gui/README.md new file mode 100644 index 00000000..e8949d5e --- /dev/null +++ b/refl1d-web-gui/README.md @@ -0,0 +1,29 @@ +# refl1d-web-gui + +This template should help get you started developing with Vue 3 in Vite. + +## Recommended IDE Setup + +[VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). + +## Customize configuration + +See [Vite Configuration Reference](https://vitejs.dev/config/). + +## Project Setup + +```sh +npm install +``` + +### Compile and Hot-Reload for Development + +```sh +npm run dev +``` + +### Compile and Minify for Production + +```sh +npm run build +``` diff --git a/refl1d-web-gui/index.html b/refl1d-web-gui/index.html new file mode 100644 index 00000000..030a6ff5 --- /dev/null +++ b/refl1d-web-gui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/refl1d-web-gui/package.json b/refl1d-web-gui/package.json new file mode 100644 index 00000000..0d0651c3 --- /dev/null +++ b/refl1d-web-gui/package.json @@ -0,0 +1,18 @@ +{ + "name": "refl1d-web-gui", + "version": "0.0.0", + "scripts": { + "dev": "vite", + "build": "vite build --outDir ../refl1d/webview --emptyOutDir", + "preview": "vite preview --port 4173" + }, + "dependencies": { + "bootstrap": "^5.2.1", + "socket.io-client": "^4.5.2", + "vue": "^3.2.38" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^3.0.3", + "vite": "^3.0.9" + } +} diff --git a/refl1d-web-gui/src/App.vue b/refl1d-web-gui/src/App.vue new file mode 100644 index 00000000..4abda4f5 --- /dev/null +++ b/refl1d-web-gui/src/App.vue @@ -0,0 +1,175 @@ + + + + + diff --git a/refl1d-web-gui/src/assets/refl1d-icon_256x256x32.png b/refl1d-web-gui/src/assets/refl1d-icon_256x256x32.png new file mode 100644 index 0000000000000000000000000000000000000000..96a6895ba0b90a8ba58952009e45abe7232671fa GIT binary patch literal 54618 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 P00000NkvXXu0mjf6(N^N literal 0 HcmV?d00001 diff --git a/refl1d-web-gui/src/components/FitOptions.vue b/refl1d-web-gui/src/components/FitOptions.vue new file mode 100644 index 00000000..e226b4c2 --- /dev/null +++ b/refl1d-web-gui/src/components/FitOptions.vue @@ -0,0 +1,210 @@ + + + + + \ No newline at end of file diff --git a/refl1d-web-gui/src/fitter_defaults.ts b/refl1d-web-gui/src/fitter_defaults.ts new file mode 100644 index 00000000..2f2d638a --- /dev/null +++ b/refl1d-web-gui/src/fitter_defaults.ts @@ -0,0 +1,62 @@ +export const FITTERS = { + dream: { + name: "DREAM", + settings: { + "samples": 10000, + "burn": 100, + "pop": 10, + "init": "eps", + "thin": 1, + "alpha": 0.01, + "outliers": "none", + "trim": false, + "steps": 0 + } + }, + lm: { + name: "Levenberg-Marquardt", + settings: { + "steps": 200, + "ftol": 1.5e-08, + "xtol": 1.5e-08 + } + }, + amoeba: { + name: "Nelder-Mead Simplex", + settings: { + "steps": 1000, + "starts": 1, + "radius": 0.15, + "xtol": 1e-06, + "ftol": 1e-08 + } + }, + de: { + name: "Differential Evolution", + settings: { + "steps": 1000, + "pop": 10, + "CR": 0.9, + "F": 2.0, + "ftol": 1e-08, + "xtol": 1e-06 + } + }, + mp: { + name: "MPFit", + settings: { + "steps": 200, + "ftol": 1e-10, + "xtol": 1e-10 + } + }, + newton: { + name: "Quasi-Newton BFGS", + settings: { + "steps": 3000, + "starts": 1, + "ftol": 1e-06, + "xtol": 1e-12 + } + }, + } \ No newline at end of file diff --git a/refl1d-web-gui/src/main.js b/refl1d-web-gui/src/main.js new file mode 100644 index 00000000..e629d700 --- /dev/null +++ b/refl1d-web-gui/src/main.js @@ -0,0 +1,6 @@ +import { createApp } from 'vue' +import 'bootstrap/dist/css/bootstrap.min.css'; +// import './style.css' +import App from './App.vue' + +createApp(App).mount('#app') diff --git a/refl1d-web-gui/vite.config.js b/refl1d-web-gui/vite.config.js new file mode 100644 index 00000000..40c640c2 --- /dev/null +++ b/refl1d-web-gui/vite.config.js @@ -0,0 +1,15 @@ +import { fileURLToPath, URL } from 'node:url' + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [vue()], + base: '', + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) From f17d0bfb580d5f3c1b166c240bed7544f8b0ee58 Mon Sep 17 00:00:00 2001 From: bbm Date: Tue, 27 Sep 2022 17:30:19 -0400 Subject: [PATCH 003/221] update title and favicon --- refl1d-web-gui/index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refl1d-web-gui/index.html b/refl1d-web-gui/index.html index 030a6ff5..1c749da0 100644 --- a/refl1d-web-gui/index.html +++ b/refl1d-web-gui/index.html @@ -2,9 +2,9 @@ - + - Vite App + Refl1D
From 419c34c7a4479575c118277bd4dc06018a5dd9c4 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 30 Sep 2022 07:06:00 -0400 Subject: [PATCH 004/221] adding mathjax --- refl1d-web-gui/index.html | 3 +++ 1 file changed, 3 insertions(+) diff --git a/refl1d-web-gui/index.html b/refl1d-web-gui/index.html index 1c749da0..c58c76fe 100644 --- a/refl1d-web-gui/index.html +++ b/refl1d-web-gui/index.html @@ -4,6 +4,9 @@ + Refl1D From 99fb4d1e25acffbed753e0cde478a3a49643cc16 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 30 Sep 2022 07:06:22 -0400 Subject: [PATCH 005/221] adding plotting libraries --- refl1d-web-gui/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/refl1d-web-gui/package.json b/refl1d-web-gui/package.json index 0d0651c3..0beb3186 100644 --- a/refl1d-web-gui/package.json +++ b/refl1d-web-gui/package.json @@ -8,6 +8,8 @@ }, "dependencies": { "bootstrap": "^5.2.1", + "mpld3": "^0.5.8", + "plotly.js": "^2.14.0", "socket.io-client": "^4.5.2", "vue": "^3.2.38" }, From 6d16a2beb8789bd397c486163b961698b635d2c3 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 30 Sep 2022 07:07:29 -0400 Subject: [PATCH 006/221] move component-specific listener registration on socket to components, add fileBrowser --- refl1d-web-gui/src/App.vue | 119 ++++++++++++++++++++----------------- 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/refl1d-web-gui/src/App.vue b/refl1d-web-gui/src/App.vue index 4abda4f5..1c81adc8 100644 --- a/refl1d-web-gui/src/App.vue +++ b/refl1d-web-gui/src/App.vue @@ -1,11 +1,15 @@ - \ No newline at end of file diff --git a/refl1d-web-gui/src/components/ModelView.vue b/refl1d-web-gui/src/components/ModelView.vue new file mode 100644 index 00000000..c5741b57 --- /dev/null +++ b/refl1d-web-gui/src/components/ModelView.vue @@ -0,0 +1,58 @@ + + + + + \ No newline at end of file diff --git a/refl1d-web-gui/src/components/PanelTabContainer.vue b/refl1d-web-gui/src/components/PanelTabContainer.vue new file mode 100644 index 00000000..c94085a7 --- /dev/null +++ b/refl1d-web-gui/src/components/PanelTabContainer.vue @@ -0,0 +1,66 @@ + + + + + \ No newline at end of file diff --git a/refl1d-web-gui/src/components/SummaryView.vue b/refl1d-web-gui/src/components/SummaryView.vue new file mode 100644 index 00000000..1190b097 --- /dev/null +++ b/refl1d-web-gui/src/components/SummaryView.vue @@ -0,0 +1,78 @@ + + + + + \ No newline at end of file From 2a3bc2fd1525b39aa74f2bc9e2730a6be193dfc0 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 30 Sep 2022 07:14:47 -0400 Subject: [PATCH 011/221] fix missing oversampling argument --- refl1d/probe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/refl1d/probe.py b/refl1d/probe.py index 4bdb363b..d0c4b390 100644 --- a/refl1d/probe.py +++ b/refl1d/probe.py @@ -1647,7 +1647,7 @@ def __init__(self, xs: Optional[Tuple]=None, mp: optional_xs=None, pm: optional_xs=None, pp: optional_xs=None, - name=None, Aguide=BASE_GUIDE_ANGLE, H=0): + name=None, Aguide=BASE_GUIDE_ANGLE, H=0, oversampling=None): if any([mm, mp, pm, pp]): if xs is not None: warnings.warn("a cross-section is directly specified - xs argument will be ignored") From 8b8191760fcbf11475f9de2ed6d42ac341a1f5c1 Mon Sep 17 00:00:00 2001 From: bbm Date: Fri, 30 Sep 2022 07:15:31 -0400 Subject: [PATCH 012/221] add methods to webserver to support parameter view, profile view and data view --- refl1d/webserver.py | 215 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 191 insertions(+), 24 deletions(-) diff --git a/refl1d/webserver.py b/refl1d/webserver.py index 7dee2fe2..0f613dca 100644 --- a/refl1d/webserver.py +++ b/refl1d/webserver.py @@ -1,12 +1,18 @@ # from .main import setup_bumps +from typing import Any, Dict, List +from datetime import datetime from aiohttp import web +import numpy as np +import asyncio import socketio -from pathlib import Path +from pathlib import Path, PurePath import json from copy import deepcopy from bumps.fitters import DreamFit, LevenbergMarquardtFit, SimplexFit, DEFit, MPFit, BFGSFit +from bumps.serialize import to_dict +import refl1d.fitproblem, refl1d.probe FITTERS = (DreamFit, LevenbergMarquardtFit, SimplexFit, DEFit, MPFit, BFGSFit) FITTER_DEFAULTS = {} @@ -17,14 +23,45 @@ } # set up mutable state of fitters: -fitter_settings = deepcopy(FITTER_DEFAULTS) -active_fitter = "amoeba" +# fitter_settings = deepcopy(FITTER_DEFAULTS) +# active_fitter = "amoeba" sio = socketio.AsyncServer(cors_allowed_origins="*") app = web.Application() static_dir_path = Path(__file__).parent / 'webview' sio.attach(app) +# class TopicManager: +# topics: Dict[str, Any] = {} + +# def subscribe(self, sid: str, topic: str): +# sio.enter_room(sid, topic) + +# def unsubscribe(self, sid: str, topic: str): +# sio.leave_room(sid, topic) + +# def get(self, topic: str): +# return self.topics.get(topic, None) + +# async def publish(self, topic: str, message): +# timestamp = datetime.now().timestamp() +# self.topics[topic] = {"message": message, "timestamp": timestamp} +# await sio.emit("message-available", timestamp, room=topic) + +# topicManager = TopicManager() + +# use context storage: within the aiohttp app, see +# class TopicData(TypedDict): +# message: Any +# timestamp: float + +# topic_buffers: Dict[str, TopicData] = {} + +topics: Dict[str, Dict] = {} +app["topics"] = topics +app["problem"] = {"fitProblem": None, "filepath": None} +problem = None + async def index(request): """Serve the client-side application.""" # redirect to static built site: @@ -33,35 +70,165 @@ async def index(request): @sio.event async def connect(sid, environ): print("connect ", sid) - sio.enter_room(sid, "situation") - await sio.emit("fitter-defaults", FITTER_DEFAULTS, to=sid) - await sio.emit("fitter-settings", fitter_settings, to=sid) - await sio.emit("fitter-active", active_fitter, to=sid) - -@sio.on("fitter-active") -async def new_fitter_active(sid, data): - # using global state here: - global active_fitter - active_fitter = data - await sio.emit("fitter-active", data, room="situation") - -@sio.on("fitter-settings") -async def new_fitter_settings(sid, data): - global fitter_settings - fitter_settings.update(data) - await sio.emit("fitter-settings", fitter_settings, room="situation") - -# async def get_sessions(sid): -# return web.Response(text=json.dumps(list(sessions)), content_type='application/json') + +@sio.event +async def load_model_file(sid: str, pathlist: List[str], filename: str): + from bumps.cli import load_model + path = Path(*pathlist, filename) + app["problem"]["fitProblem"] = load_model(str(path)) + await sio.emit("plot_update_ready", True) + +def get_single_probe_data(theory, probe, substrate=None, surface=None, label=''): + fresnel_calculator = probe.fresnel(substrate, surface) + Q, FQ = probe.apply_beam(probe.calc_Q, fresnel_calculator(probe.calc_Q)) + Q, R = theory + assert isinstance(FQ, np.ndarray) + if len(Q) != len(probe.Q): + # Saving interpolated data + output = dict(Q = Q, theory = R, fresnel=np.interp(Q, probe.Q, FQ)) + elif getattr(probe, 'R', None) is not None: + output = dict(Q = probe.Q, dQ = probe.dQ, R = probe.R, dR = probe.dR, theory = R, fresnel = FQ) + else: + output = dict(Q = probe.Q, dQ = probe.dQ, theory = R, fresnel = FQ) + output['label'] = label + return output + +def get_probe_data(theory, probe, substrate=None, surface=None): + if isinstance(probe, refl1d.probe.PolarizedNeutronProbe): + output = [] + for xsi, xsi_th, suffix in zip(probe.xs, theory, ('--', '-+', '+-', '++')): + if xsi is not None: + output.append(get_single_probe_data(xsi_th, xsi, substrate, surface, suffix)) + return output + else: + return [get_single_probe_data(theory, probe, substrate, surface)] + +@sio.event +def get_plot_data(sid: str, view: str = 'linear'): + fitProblem: refl1d.fitproblem.FitProblem = app["problem"]["fitProblem"] + result = [] + for model in fitProblem.models: + theory = model.reflectivity() + probe = model.probe + result.append(get_probe_data(theory, probe, model._substrate, model._surface)) + # fresnel_calculator = probe.fresnel(model._substrate, model._surface) + # Q, FQ = probe.apply_beam(probe.calc_Q, fresnel_calculator(probe.calc_Q)) + # Q, R = theory + # assert isinstance(FQ, np.ndarray) + # if len(Q) != len(probe.Q): + # # Saving interpolated data + # output = dict(Q = Q, R = R, fresnel=np.interp(Q, probe.Q, FQ)) + # elif getattr(probe, 'R', None) is not None: + # output = dict(Q = probe.Q, dQ = probe.dQ, R = probe.R, dR = probe.dR, fresnel = FQ) + # else: + # output = dict(Q = probe.Q, dQ = probe.dQ, R = R, fresnel = FQ) + # result.append(output) + return to_dict(result) + +@sio.event +def get_profile_plot(sid: str): + import mpld3 + import matplotlib.pyplot as plt + fitProblem: refl1d.fitproblem.FitProblem = app["problem"]["fitProblem"] + for model in iter(fitProblem.models): + model.plot_profile() + return mpld3.fig_to_dict(plt.gcf()) + +@sio.event +def get_parameters(sid: str): + fitProblem: refl1d.fitproblem.FitProblem = app["problem"]["fitProblem"] + if fitProblem is None: + return [] + raw_parameters = fitProblem._parameters + parameters = [] + for rp in raw_parameters: + p = {} + low, high = (v for v in rp.prior.limits) + p['min'] = VALUE_FORMAT.format(nice(low)) + p['max'] = VALUE_FORMAT.format(nice(high)) + p['value_str'] = VALUE_FORMAT.format(nice(rp.value)) + p['value01'] = rp.prior.get01(rp.value) + p['name'] = rp.name + parameters.append(p) + return to_dict(parameters) + +@sio.event +async def set_parameter01(sid: str, parameter_name: str, parameter_value01: float): + fitProblem: refl1d.fitproblem.FitProblem = app["problem"]["fitProblem"] + if fitProblem is None: + return + parameter_matches = [p for p in fitProblem._parameters if p.name == parameter_name] + if len(parameter_matches) < 1: + return + parameter = parameter_matches[0] + new_value = parameter.prior.put01(parameter_value01) + nice_new_value = nice(new_value, digits=VALUE_PRECISION) + parameter.value = nice_new_value + await sio.emit("plot_update_ready", True) + return new_value + +@sio.event +async def subscribe(sid: str, topic: str): + sio.enter_room(sid, topic) + last_timestamp = topics.get(topic, {}).get("timestamp", None) + if last_timestamp is not None: + await sio.emit(topic, last_timestamp, to=sid) + +@sio.event +def unsubscribe(sid: str, topic: str): + sio.leave_room(sid, topic) + +@sio.event +async def publish(sid: str, topic: str, message): + timestamp = datetime.now().timestamp() + topics[topic] = {"message": message, "timestamp": timestamp} + await sio.emit(topic, timestamp, room=topic) + +@sio.event +def get_last_message(sid: str, topic: str): + # this is a GET request in disguise - + # emitter must handle the response in a callback, + # as no separate response event is emitted. + return topics.get(topic, {}).get("message", None) + +@sio.event +def get_dirlisting(sid: str, pathlist: List[str]): + # GET request + # TODO: use psutil to get disk listing as well? + subfolders = [] + files = [] + for p in Path(*pathlist).iterdir(): + if p.is_dir(): + subfolders.append(p.name) + else: + files.append(p.resolve().name) + return dict(subfolders=subfolders, files=files) + +app.on_startup.append(lambda App: publish('', 'fitter_defaults', FITTER_DEFAULTS)) +app.on_startup.append(lambda App: publish('', 'fitter_settings', deepcopy(FITTER_DEFAULTS))) +app.on_startup.append(lambda App: publish('', 'fitter_active', 'amoeba')) @sio.event def disconnect(sid): print('disconnect ', sid) app.router.add_static('/static', static_dir_path) -# app.router.add_static('/static', os.path.join(currdir, 'webview')) app.router.add_get('/', index) +VALUE_PRECISION = 6 +VALUE_FORMAT = "{{:.{:d}g}}".format(VALUE_PRECISION) + +def nice(v, digits=4): + """Fix v to a value with a given number of digits of precision""" + from math import log10, floor + if v == 0. or not np.isfinite(v): + return v + else: + sign = v/abs(v) + place = floor(log10(abs(v))) + scale = 10**(place-(digits-1)) + return sign*floor(abs(v)/scale+0.5)*scale + if __name__ == '__main__': web.run_app(app) From 5fbca59d0efd6a07b320171fe14bbf9fba15ec84 Mon Sep 17 00:00:00 2001 From: Brian Benjamin Maranville Date: Fri, 30 Sep 2022 16:31:49 -0400 Subject: [PATCH 013/221] fix bug in assigning visible status --- refl1d-web-gui/src/components/PanelTabContainer.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/refl1d-web-gui/src/components/PanelTabContainer.vue b/refl1d-web-gui/src/components/PanelTabContainer.vue index c94085a7..da38a4e5 100644 --- a/refl1d-web-gui/src/components/PanelTabContainer.vue +++ b/refl1d-web-gui/src/components/PanelTabContainer.vue @@ -22,11 +22,11 @@ function activateTab(index) { panelContainers.value.forEach((el, i) => { if (i === index) { el.classList.add('active'); - panelVisible.value[index] = true; + panelVisible.value[i] = true; } else { el.classList.remove('active'); - panelVisible.value[index] = false; + panelVisible.value[i] = false; } }); } From c5ce0daf80a66fad3a6fccfda93d238f5bed930f Mon Sep 17 00:00:00 2001 From: Brian Benjamin Maranville Date: Fri, 30 Sep 2022 16:32:41 -0400 Subject: [PATCH 014/221] prevent re-render on active slider, and send parameter updates to server --- refl1d-web-gui/src/components/SummaryView.vue | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/refl1d-web-gui/src/components/SummaryView.vue b/refl1d-web-gui/src/components/SummaryView.vue index 1190b097..2aa2a9bd 100644 --- a/refl1d-web-gui/src/components/SummaryView.vue +++ b/refl1d-web-gui/src/components/SummaryView.vue @@ -1,22 +1,37 @@