Skip to content

Commit 66f1350

Browse files
committed
Enable configuration of visibility of visual elements (edit button, bias tag)
1 parent dfc6507 commit 66f1350

4 files changed

Lines changed: 101 additions & 9 deletions

File tree

client/app.js

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ const store = createStore({
105105
twoWayTable: false,
106106
},
107107

108+
visualElements: {
109+
editExperimentButton: true,
110+
biasTag: true,
111+
},
112+
108113
running: {
109114
cancel: false,
110115
auto: false,
@@ -161,7 +166,7 @@ const runner = createRunner(store.getState().running, {
161166
onTick: () => {
162167
const state = store.getState();
163168
render(els, state);
164-
applySectionVisibility(state);
169+
applyVisibility(state);
165170
activityLogger.maybeLogStatus(state);
166171
},
167172
onDone: updateControls,
@@ -249,7 +254,7 @@ function resetSimulation({ reason = 'unknown', logSettings = false } = {}) {
249254
else resetSingleSimulation();
250255

251256
render(els, store.getState());
252-
applySectionVisibility(store.getState());
257+
applyVisibility(store.getState());
253258
syncUiFromState();
254259

255260
const currentState = store.getState();
@@ -313,6 +318,46 @@ function applySectionVisibility(state) {
313318
}
314319
}
315320

321+
function applyVisualElementVisibility(state) {
322+
const visualElements = state.visualElements || {};
323+
324+
const showEditExperimentButton = visualElements.editExperimentButton !== false;
325+
if (els.openSettings) {
326+
els.openSettings.style.display = showEditExperimentButton ? '' : 'none';
327+
els.openSettings.hidden = !showEditExperimentButton;
328+
}
329+
330+
const showBiasTag = visualElements.biasTag !== false;
331+
if (els.biasSummary) {
332+
els.biasSummary.style.display = showBiasTag ? '' : 'none';
333+
els.biasSummary.hidden = !showBiasTag;
334+
}
335+
336+
const tagsContainer = els.biasSummary?.parentElement || els.relationshipSummary?.parentElement;
337+
if (tagsContainer) {
338+
const biasVisible = Boolean(
339+
showBiasTag
340+
&& els.biasSummary
341+
&& !els.biasSummary.hidden
342+
&& els.biasSummary.style.display !== 'none',
343+
);
344+
const relationshipVisible = Boolean(
345+
els.relationshipSummary
346+
&& !els.relationshipSummary.hidden
347+
&& els.relationshipSummary.style.display !== 'none',
348+
);
349+
350+
const anyVisible = biasVisible || relationshipVisible;
351+
tagsContainer.style.display = anyVisible ? '' : 'none';
352+
tagsContainer.hidden = !anyVisible;
353+
}
354+
}
355+
356+
function applyVisibility(state) {
357+
applySectionVisibility(state);
358+
applyVisualElementVisibility(state);
359+
}
360+
316361
function updateControls() {
317362
const state = store.getState();
318363
const autoRunning = state.running.auto;
@@ -669,7 +714,7 @@ function renderEventOptions() {
669714
});
670715
const state = store.getState();
671716
render(els, state);
672-
applySectionVisibility(state);
717+
applyVisibility(state);
673718
activityLogger.logSettingsChange(state);
674719
});
675720

@@ -955,7 +1000,7 @@ function initEventListeners() {
9551000
});
9561001
const currentState = store.getState();
9571002
render(els, currentState);
958-
applySectionVisibility(currentState);
1003+
applyVisibility(currentState);
9591004
});
9601005

9611006
els.heatmap.addEventListener('click', (event) => {
@@ -984,13 +1029,13 @@ function initEventListeners() {
9841029
});
9851030
const updatedState = store.getState();
9861031
render(els, updatedState);
987-
applySectionVisibility(updatedState);
1032+
applyVisibility(updatedState);
9881033
});
9891034

9901035
window.addEventListener('resize', () => {
9911036
const state = store.getState();
9921037
render(els, state);
993-
applySectionVisibility(state);
1038+
applyVisibility(state);
9941039
});
9951040
}
9961041

@@ -1025,6 +1070,9 @@ async function init() {
10251070
if (config.sections) {
10261071
draft.sections = { ...draft.sections, ...config.sections };
10271072
}
1073+
if (config.visualElements) {
1074+
draft.visualElements = { ...draft.visualElements, ...config.visualElements };
1075+
}
10281076
draft.rng = createRngFromSeed(draft.seedText);
10291077
});
10301078
syncUiFromState();
@@ -1066,12 +1114,13 @@ async function init() {
10661114

10671115
initEventListeners();
10681116
updateControls();
1069-
applySectionVisibility(store.getState());
1117+
applyVisibility(store.getState());
10701118
activityLogger.logAppStart(store.getState());
10711119

10721120
// Defer initial render until after layout is complete
10731121
requestAnimationFrame(() => {
10741122
render(els, store.getState());
1123+
applyVisibility(store.getState());
10751124
});
10761125
}
10771126

client/config.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
"outcomes": ["Pass", "Fail"],
88
"probabilities": [0.7, 0.3]
99
},
10+
"visualElements": {
11+
"editExperimentButton": true,
12+
"biasTag": true
13+
},
1014
"sections": {
1115
"barChart": true,
1216
"convergence": true,
@@ -15,4 +19,3 @@
1519
"twoWayTable": false
1620
}
1721
}
18-

client/src/shell/config.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const VALID_DEVICES = ['coin', 'die', 'spinner', 'custom'];
66
const MAX_CUSTOM_OUTCOMES = 50;
77
const VALID_SECTIONS_SINGLE = ['barChart', 'convergence', 'frequencyTable'];
88
const VALID_SECTIONS_TWO = ['jointDistribution', 'twoWayTable'];
9+
const VALID_VISUAL_ELEMENTS = ['editExperimentButton', 'biasTag'];
910

1011
/**
1112
* Validates configuration object structure
@@ -106,7 +107,7 @@ function validateCustomDeviceSettings(settings, label) {
106107
};
107108
}
108109

109-
function validateConfig(config) {
110+
export function validateConfig(config) {
110111
const validated = {};
111112
const rawConfig = config && typeof config === 'object' ? config : {};
112113

@@ -173,6 +174,16 @@ function validateConfig(config) {
173174
}
174175
// If sections is missing entirely, don't add it (will use store defaults)
175176

177+
// Validate visual elements toggles
178+
if (rawConfig.visualElements && typeof rawConfig.visualElements === 'object' && !Array.isArray(rawConfig.visualElements)) {
179+
const validatedVisualElements = {};
180+
for (const key of VALID_VISUAL_ELEMENTS) {
181+
const value = rawConfig.visualElements[key];
182+
validatedVisualElements[key] = typeof value === 'boolean' ? value : true;
183+
}
184+
validated.visualElements = validatedVisualElements;
185+
}
186+
176187
return validated;
177188
}
178189

@@ -194,6 +205,10 @@ export async function loadConfig() {
194205
return {
195206
mode: 'single',
196207
device: 'coin',
208+
visualElements: {
209+
editExperimentButton: true,
210+
biasTag: true,
211+
},
197212
sections: {
198213
barChart: false,
199214
convergence: false,

tests/shell/config.test.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { describe, it, expect } from 'vitest';
2+
import { validateConfig } from '../../client/src/shell/config.js';
3+
4+
describe('validateConfig (visualElements)', () => {
5+
it('omits visualElements when not provided', () => {
6+
const validated = validateConfig({ mode: 'single', device: 'coin' });
7+
expect(validated.visualElements).toBeUndefined();
8+
});
9+
10+
it('defaults visualElements keys to true when provided as empty object', () => {
11+
const validated = validateConfig({ mode: 'single', device: 'coin', visualElements: {} });
12+
expect(validated.visualElements).toEqual({ editExperimentButton: true, biasTag: true });
13+
});
14+
15+
it('supports hiding biasTag via visualElements', () => {
16+
const validated = validateConfig({ mode: 'single', device: 'coin', visualElements: { biasTag: false } });
17+
expect(validated.visualElements).toEqual({ editExperimentButton: true, biasTag: false });
18+
});
19+
20+
it('ignores visualElements when not an object', () => {
21+
const validated = validateConfig({ mode: 'single', device: 'coin', visualElements: 'nope' });
22+
expect(validated.visualElements).toBeUndefined();
23+
});
24+
});
25+

0 commit comments

Comments
 (0)