Skip to content

Commit 62b2404

Browse files
authored
Merge pull request #35 from flamapy/develop
Develop
2 parents 3f84b01 + b767bb6 commit 62b2404

16 files changed

+683
-105
lines changed

package-lock.json

Lines changed: 79 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"@monaco-editor/react": "^4.6.0",
1414
"dom-to-svg": "^0.12.2",
1515
"file-saver": "^2.0.5",
16+
"jszip": "^3.10.1",
1617
"react": "^18.3.1",
1718
"react-d3-tree": "^3.6.2",
1819
"react-dom": "^18.3.1",

public/assets/flamapy.conf.zip

7.58 MB
Binary file not shown.

public/flamapy/flamapy.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class Flamapy {
2929
await micropip.install("flamapy/uvlparser-2.0.1-py3-none-any.whl", deps=False)
3030
await micropip.install("flamapy/afmparser-1.0.3-py3-none-any.whl", deps=False)
3131
await micropip.install("flamapy/antlr4_python3_runtime-4.13.1-py3-none-any.whl", deps=False)
32+
await micropip.install("flamapy/flamapy_configurator-2.0.1-py3-none-any.whl", deps=False)
3233
`);
3334
await pyodideInstance.runPythonAsync(await pythonFile.text());
3435
pyodideInstance.FS.mkdir("export");
@@ -122,4 +123,23 @@ class Flamapy {
122123
}
123124
}
124125
}
126+
127+
async startConfigurator() {
128+
const result = await this.pyodide.runPythonAsync(`
129+
start_configurator()`);
130+
return JSON.parse(result);
131+
}
132+
133+
async answerQuestion(answer) {
134+
this.pyodide.globals.set("answer", answer);
135+
const result = await this.pyodide.runPythonAsync(`
136+
answer_question(answer)`);
137+
return JSON.parse(result);
138+
}
139+
140+
async undoAnswer() {
141+
const result = await this.pyodide.runPythonAsync(`
142+
undo_answer()`);
143+
return JSON.parse(result);
144+
}
125145
}
7.38 KB
Binary file not shown.

public/flamapy/flamapy_ide.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,12 @@
88
from flamapy.core.discover import DiscoverMetamodels
99
from flamapy.metamodels.fm_metamodel.transformations import GlencoeReader, AFMReader, FeatureIDEReader, JSONReader, XMLReader, UVLReader, GlencoeWriter
1010
from flamapy.metamodels.configuration_metamodel.models import Configuration
11+
from flamapy.metamodels.configurator_metamodel.transformation import FmToConfigurator
1112
from collections import defaultdict
1213

1314
fm = None
15+
configurator = None
16+
1417

1518
# Custom error listener
1619
class CustomErrorListener(ErrorListener):
@@ -211,4 +214,30 @@ def execute_configurator_operation(name: str, conf):
211214
result = operation.get_result()
212215
if type(result) is list:
213216
return [str(conf) for conf in result]
214-
return result
217+
return result
218+
219+
def start_configurator():
220+
global configurator
221+
configurator = FmToConfigurator(fm.fm_model).transform()
222+
configurator.start()
223+
224+
return json.dumps(configurator.get_current_status())
225+
226+
def answer_question(answer):
227+
valid = configurator.answer_question(answer)
228+
229+
result = dict()
230+
result['valid'] = valid
231+
if valid:
232+
if configurator.next_question():
233+
result['nextQuestion'] = configurator.get_current_status()
234+
else:
235+
result['configuration'] = configurator._get_configuration()
236+
else:
237+
result['contradiction'] = {'msg': 'The selected choice is incompatible with the model definition. Please choose another option.'}
238+
239+
return json.dumps(result)
240+
241+
def undo_answer():
242+
configurator.previous_question()
243+
return json.dumps(configurator.get_current_status())

public/webworker.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@ self.onmessage = async (event) => {
3333
results = await self.flamapy.getFeatures();
3434
} else if (action === "executeActionWithConf") {
3535
results = await self.flamapy.executeActionWithConf(data);
36+
} else if (action === "startConfigurator") {
37+
results = await self.flamapy.startConfigurator();
38+
} else if (action === "answerQuestion") {
39+
results = await self.flamapy.answerQuestion(data);
40+
} else if (action === "undoAnswer") {
41+
results = await self.flamapy.undoAnswer();
3642
}
3743

3844
self.postMessage({ results, action });

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
flamapy==2.0.1
22
./public/flamapy/flamapy_fw-2.0.2.dev0-py3-none-any.whl
33
./public/flamapy/flamapy_fm-2.0.2.dev0-py3-none-any.whl
4+
./public/flamapy/flamapy_configurator-2.0.1-py3-none-any.whl
45
flamapy_sat==2.0.1
56
flamapy_bdd==2.0.1
67
pytest==8.3.3

src/components/Configuration.jsx

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
function Configuration({ configuration }) {
2+
const categorizedConfig = {
3+
selected: [],
4+
deselected: [],
5+
undecided: [],
6+
};
7+
8+
Object.entries(configuration).forEach(([feature, status]) => {
9+
if (status === true) {
10+
categorizedConfig.selected.push(feature);
11+
} else if (status === false) {
12+
categorizedConfig.deselected.push(feature);
13+
} else {
14+
categorizedConfig.undecided.push(feature);
15+
}
16+
});
17+
18+
return (
19+
<div className="bg-white w-full rounded-xl p-4 text-xl shadow-md overflow-auto">
20+
<h2 className="text-2xl font-bold text-gray-800 mb-4">
21+
Feature Configuration
22+
</h2>
23+
<div className="text-lg">
24+
<div className="mb-2">
25+
<h3 className="font-semibold text-green-600">Selected Features:</h3>
26+
<ul className="list-disc list-inside text-black">
27+
{categorizedConfig.selected.length > 0 ? (
28+
categorizedConfig.selected.map((feature, index) => (
29+
<li key={index}>{feature}</li>
30+
))
31+
) : (
32+
<p className="text-gray-500">None</p>
33+
)}
34+
</ul>
35+
</div>
36+
37+
<div className="mb-2">
38+
<h3 className="font-semibold text-red-600">Deselected Features:</h3>
39+
<ul className="list-disc list-inside text-black">
40+
{categorizedConfig.deselected.length > 0 ? (
41+
categorizedConfig.deselected.map((feature, index) => (
42+
<li key={index}>{feature}</li>
43+
))
44+
) : (
45+
<p className="text-gray-500">None</p>
46+
)}
47+
</ul>
48+
</div>
49+
50+
<div>
51+
<h3 className="font-semibold text-yellow-600">Undecided Features:</h3>
52+
<ul className="list-disc list-inside text-black">
53+
{categorizedConfig.undecided.length > 0 ? (
54+
categorizedConfig.undecided.map((feature, index) => (
55+
<li key={index}>{feature}</li>
56+
))
57+
) : (
58+
<p className="text-gray-500">None</p>
59+
)}
60+
</ul>
61+
</div>
62+
</div>
63+
</div>
64+
);
65+
}
66+
67+
export default Configuration;

src/components/CustomButton.jsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/* eslint-disable react/prop-types */
2+
function CustomButton({ active = true, onClick, children }) {
3+
const baseClasses =
4+
"w-max text-white py-2 px-4 m-2 rounded shadow-lg transition-colors duration-200";
5+
const activeClasses = "bg-[#356C99] hover:bg-[#0D486C]";
6+
const disabledClasses = "bg-gray-400 cursor-not-allowed";
7+
8+
return (
9+
<button
10+
className={`${baseClasses} ${active ? activeClasses : disabledClasses}`}
11+
onClick={active ? onClick : undefined}
12+
disabled={!active}
13+
>
14+
{children}
15+
</button>
16+
);
17+
}
18+
19+
export default CustomButton;

0 commit comments

Comments
 (0)