Compare commits
No commits in common. "master" and "1.0.0" have entirely different histories.
67 changed files with 188 additions and 1460 deletions
|
@ -9,7 +9,7 @@ This run with the [PyQt6](https://www.riverbankcomputing.com/software/pyqt/) and
|
||||||
|
|
||||||
To install the projet, you will need to install :
|
To install the projet, you will need to install :
|
||||||
|
|
||||||
- Python >= 3.11 (preferably in a virtual environment with `python3 -m venv ./.venv/`)
|
- Python >= 3.11 (preferably in a virtual environment)
|
||||||
- Python dependencies (`python3 -m pip install -r ./requirements.txt`)
|
- Python dependencies (`python3 -m pip install -r ./requirements.txt`)
|
||||||
|
|
||||||
Or download one of the build in the [releases page](https://github.com/Faraphel/M1-Recherche/releases).
|
Or download one of the build in the [releases page](https://github.com/Faraphel/M1-Recherche/releases).
|
||||||
|
|
Binary file not shown.
|
@ -1,43 +1,38 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="en">
|
<TS version="2.1" language="en">
|
||||||
<context>
|
<context>
|
||||||
<name>LanguageSelection</name>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="69" />
|
|
||||||
<source>SELECT YOUR LANGUAGE</source>
|
|
||||||
<translation>Select your language</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="70" />
|
|
||||||
<source>START</source>
|
|
||||||
<translation>Start</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
|
||||||
<name>SurveyEngine</name>
|
<name>SurveyEngine</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyEngine.py" line="177" />
|
<location filename="../../source/widget/SurveyEngine.py" line="177"/>
|
||||||
<source>WARNING</source>
|
<source>WARNING</source>
|
||||||
<translation>Warning</translation>
|
<translation>Warning</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SurveyNavigation</name>
|
<name>SurveyNavigation</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="19" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="19"/>
|
||||||
<source>ABANDON</source>
|
<source>ABANDON</source>
|
||||||
<translation>Abandon</translation>
|
<translation>Abandon</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="29" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="29"/>
|
||||||
<source>SKIP</source>
|
<source>SKIP</source>
|
||||||
<translation>Skip</translation>
|
<translation>Skip</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="38" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="38"/>
|
||||||
<source>NEXT</source>
|
<source>NEXT</source>
|
||||||
<translation>Next</translation>
|
<translation>Next</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>SurveyWindow</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyWindow.py" line="16"/>
|
||||||
|
<source>SURVEY</source>
|
||||||
|
<translation>Survey</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
Binary file not shown.
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!DOCTYPE TS>
|
|
||||||
<TS version="2.1" language="es">
|
|
||||||
<context>
|
|
||||||
<name>LanguageSelection</name>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="69" />
|
|
||||||
<source>SELECT YOUR LANGUAGE</source>
|
|
||||||
<translation>Seleccione su idioma</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="70" />
|
|
||||||
<source>START</source>
|
|
||||||
<translation>Comenzar</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
|
||||||
<name>SurveyEngine</name>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\SurveyEngine.py" line="177" />
|
|
||||||
<source>WARNING</source>
|
|
||||||
<translation>Atención</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
|
||||||
<name>SurveyNavigation</name>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="19" />
|
|
||||||
<source>ABANDON</source>
|
|
||||||
<translation>Abandonar</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="29" />
|
|
||||||
<source>SKIP</source>
|
|
||||||
<translation>Pasar</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="38" />
|
|
||||||
<source>NEXT</source>
|
|
||||||
<translation>Siguiente</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
</TS>
|
|
Binary file not shown.
|
@ -1,43 +1,38 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!DOCTYPE TS>
|
<!DOCTYPE TS>
|
||||||
<TS version="2.1" language="fr_FR">
|
<TS version="2.1" language="fr_FR">
|
||||||
<context>
|
<context>
|
||||||
<name>LanguageSelection</name>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="69" />
|
|
||||||
<source>SELECT YOUR LANGUAGE</source>
|
|
||||||
<translation>Sélectionner votre langue</translation>
|
|
||||||
</message>
|
|
||||||
<message>
|
|
||||||
<location filename="..\..\source\ui\LanguageSelection.py" line="70" />
|
|
||||||
<source>START</source>
|
|
||||||
<translation>Démarrer</translation>
|
|
||||||
</message>
|
|
||||||
</context>
|
|
||||||
<context>
|
|
||||||
<name>SurveyEngine</name>
|
<name>SurveyEngine</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyEngine.py" line="177" />
|
<location filename="../../source/widget/SurveyEngine.py" line="177"/>
|
||||||
<source>WARNING</source>
|
<source>WARNING</source>
|
||||||
<translation>Attention</translation>
|
<translation>Avertissement</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
<context>
|
<context>
|
||||||
<name>SurveyNavigation</name>
|
<name>SurveyNavigation</name>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="19" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="19"/>
|
||||||
<source>ABANDON</source>
|
<source>ABANDON</source>
|
||||||
<translation>Abandonner</translation>
|
<translation>Abandonner</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="29" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="29"/>
|
||||||
<source>SKIP</source>
|
<source>SKIP</source>
|
||||||
<translation>Passer</translation>
|
<translation>Passer</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<location filename="..\..\source\ui\SurveyNavigation.py" line="38" />
|
<location filename="../../source/widget/SurveyNavigation.py" line="38"/>
|
||||||
<source>NEXT</source>
|
<source>NEXT</source>
|
||||||
<translation>Suivant</translation>
|
<translation>Suivant</translation>
|
||||||
</message>
|
</message>
|
||||||
</context>
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>SurveyWindow</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyWindow.py" line="16"/>
|
||||||
|
<source>SURVEY</source>
|
||||||
|
<translation>Sondage</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
</TS>
|
</TS>
|
||||||
|
|
BIN
assets/language/sp.qm
Normal file
BIN
assets/language/sp.qm
Normal file
Binary file not shown.
38
assets/language/sp.ts
Normal file
38
assets/language/sp.ts
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="es">
|
||||||
|
<context>
|
||||||
|
<name>SurveyEngine</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyEngine.py" line="177"/>
|
||||||
|
<source>WARNING</source>
|
||||||
|
<translation>Atención</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>SurveyNavigation</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyNavigation.py" line="19"/>
|
||||||
|
<source>ABANDON</source>
|
||||||
|
<translation>Abandonar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyNavigation.py" line="29"/>
|
||||||
|
<source>SKIP</source>
|
||||||
|
<translation>Pasar</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyNavigation.py" line="38"/>
|
||||||
|
<source>NEXT</source>
|
||||||
|
<translation>Siguiente</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>SurveyWindow</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../../source/widget/SurveyWindow.py" line="16"/>
|
||||||
|
<source>SURVEY</source>
|
||||||
|
<translation>Encuesta</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
17
main.py
17
main.py
|
@ -1,14 +1,29 @@
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from PyQt6.QtCore import QTranslator, QLocale
|
||||||
from PyQt6.QtWidgets import QApplication
|
from PyQt6.QtWidgets import QApplication
|
||||||
|
|
||||||
from source.ui import SurveyWindow
|
from source import translate
|
||||||
|
from source import assets_path
|
||||||
|
from source.widget import SurveyWindow
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
# create the application
|
# create the application
|
||||||
application = QApplication(sys.argv)
|
application = QApplication(sys.argv)
|
||||||
|
|
||||||
|
# get the user language
|
||||||
|
local = QLocale()
|
||||||
|
language_code: str = local.languageToCode(local.language(), QLocale.LanguageCodeType.ISO639)
|
||||||
|
|
||||||
|
# apply the language on the translator
|
||||||
|
translate.set_language(language_code)
|
||||||
|
|
||||||
|
# load the translator to support multiple languages
|
||||||
|
translator = QTranslator()
|
||||||
|
application.installTranslator(translator)
|
||||||
|
translator.load(str(assets_path / f"language/{language_code}.qm"))
|
||||||
|
|
||||||
# create the window
|
# create the window
|
||||||
window = SurveyWindow("./surveys.json")
|
window = SurveyWindow("./surveys.json")
|
||||||
window.show()
|
window.show()
|
||||||
|
|
|
@ -4,6 +4,4 @@ PyQt6-WebEngine
|
||||||
nextcord~=2.6.0
|
nextcord~=2.6.0
|
||||||
requests~=2.31.0
|
requests~=2.31.0
|
||||||
|
|
||||||
cx_freeze
|
cx_freeze
|
||||||
matplotlib~=3.8.2
|
|
||||||
numpy~=1.26.3
|
|
21
setup.py
21
setup.py
|
@ -22,25 +22,16 @@ setup(
|
||||||
"build_exe": {
|
"build_exe": {
|
||||||
"include_msvcr": True,
|
"include_msvcr": True,
|
||||||
"include_files": [
|
"include_files": [
|
||||||
("./tools/", "./tools/"),
|
|
||||||
("./assets/", "./assets/"),
|
("./assets/", "./assets/"),
|
||||||
("./README.md", "./README.md"),
|
("./README.md", "./README.md"),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
executables=[
|
executables=[Executable(
|
||||||
Executable(
|
"main.py",
|
||||||
"main.py",
|
base=base,
|
||||||
base=base,
|
target_name=__appname__,
|
||||||
target_name=__appname__,
|
icon=__icon_ico__
|
||||||
icon=__icon_ico__
|
)]
|
||||||
),
|
|
||||||
Executable(
|
|
||||||
"tools/web_replay/main.py",
|
|
||||||
base=base,
|
|
||||||
target_name="tools/web_replay/main",
|
|
||||||
icon=__icon_ico__
|
|
||||||
),
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ from pathlib import Path
|
||||||
assets_path = Path("./assets/")
|
assets_path = Path("./assets/")
|
||||||
|
|
||||||
__appname__ = "Survey Engine"
|
__appname__ = "Survey Engine"
|
||||||
__version__ = (1,0,2)
|
__version__ = (1,0,0)
|
||||||
__str_version__ = ".".join(map(str, __version__))
|
__str_version__ = ".".join(map(str, __version__))
|
||||||
__author__ = "Faraphel"
|
__author__ = "Faraphel"
|
||||||
__mail__ = "rc60650@hotmail.com"
|
__mail__ = "rc60650@hotmail.com"
|
||||||
|
|
|
@ -5,7 +5,6 @@ from typing import Optional
|
||||||
import nextcord
|
import nextcord
|
||||||
import requests
|
import requests
|
||||||
from PyQt6.QtCore import pyqtSignal
|
from PyQt6.QtCore import pyqtSignal
|
||||||
from PyQt6.QtWidgets import QApplication
|
|
||||||
|
|
||||||
result_path = Path("./results/")
|
result_path = Path("./results/")
|
||||||
|
|
||||||
|
@ -60,7 +59,6 @@ def upload_discord(
|
||||||
|
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
if signal_warning is not None:
|
if signal_warning is not None:
|
||||||
application = QApplication.instance()
|
signal_warning.emit("COULD NOT UPLOAD THE DATA") # NOQA: emit exist
|
||||||
signal_warning.emit(application.tr("COULD NOT UPLOAD THE DATA")) # NOQA: emit exist
|
|
||||||
else:
|
else:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSpinBox
|
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QSpinBox
|
||||||
|
|
||||||
from source import translate, ui
|
from source import translate, widget
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
|
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class IntegerQuestion(BaseSurvey):
|
||||||
self._layout.addWidget(self.entry_response)
|
self._layout.addWidget(self.entry_response)
|
||||||
|
|
||||||
# navigation
|
# navigation
|
||||||
self.navigation = ui.SurveyNavigation(signals=signals)
|
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||||
self._layout.addWidget(self.navigation)
|
self._layout.addWidget(self.navigation)
|
||||||
self.navigation.show_forward()
|
self.navigation.show_forward()
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel
|
from PyQt6.QtWidgets import QVBoxLayout, QLabel
|
||||||
|
|
||||||
from source import translate, ui
|
from source import translate, widget
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ class Text(BaseSurvey):
|
||||||
self.label_description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
self.label_description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
|
|
||||||
# navigation
|
# navigation
|
||||||
self.navigation = ui.SurveyNavigation(signals=signals)
|
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||||
self._layout.addWidget(self.navigation)
|
self._layout.addWidget(self.navigation)
|
||||||
|
|
||||||
self.navigation.show_forward() # always show forward
|
self.navigation.show_forward() # always show forward
|
||||||
|
|
|
@ -4,7 +4,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit
|
from PyQt6.QtWidgets import QVBoxLayout, QLabel, QTextEdit
|
||||||
|
|
||||||
from source import translate, ui
|
from source import translate, widget
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ class TextQuestion(BaseSurvey):
|
||||||
self._layout.addWidget(self.entry_response)
|
self._layout.addWidget(self.entry_response)
|
||||||
|
|
||||||
# navigation
|
# navigation
|
||||||
self.navigation = ui.SurveyNavigation(signals=signals)
|
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||||
self._layout.addWidget(self.navigation)
|
self._layout.addWidget(self.navigation)
|
||||||
self.navigation.show_forward()
|
self.navigation.show_forward()
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from PyQt6.QtCore import Qt, QTimer, pyqtSignal, QUrl, QEvent, QObject, QPointF
|
||||||
from PyQt6.QtGui import QFont, QMouseEvent, QResizeEvent, QKeyEvent
|
from PyQt6.QtGui import QFont, QMouseEvent, QResizeEvent, QKeyEvent
|
||||||
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSizePolicy
|
from PyQt6.QtWidgets import QLabel, QVBoxLayout, QSizePolicy
|
||||||
|
|
||||||
from source import translate, ui
|
from source import translate, widget
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,20 +14,13 @@ class WebMission(BaseSurvey):
|
||||||
self,
|
self,
|
||||||
title: translate.Translatable,
|
title: translate.Translatable,
|
||||||
url: str,
|
url: str,
|
||||||
|
check_condition: Optional[str] = None,
|
||||||
initial_js: Optional[str] = None,
|
|
||||||
start_check_js: Optional[str] = None,
|
|
||||||
check_js: Optional[str] = None,
|
|
||||||
|
|
||||||
skip_time: Optional[float] = None,
|
skip_time: Optional[float] = None,
|
||||||
signals: dict[str, pyqtSignal] = None
|
signals: dict[str, pyqtSignal] = None
|
||||||
):
|
):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.initial_js = initial_js if initial_js is not None else ""
|
self.check_condition = check_condition
|
||||||
self.start_check_js = start_check_js if start_check_js is not None else "true"
|
|
||||||
self.check_js = check_js if check_js is not None else "true"
|
|
||||||
|
|
||||||
self.default_url = url
|
self.default_url = url
|
||||||
self.skip_time = skip_time
|
self.skip_time = skip_time
|
||||||
self.signals = signals if signals is not None else {}
|
self.signals = signals if signals is not None else {}
|
||||||
|
@ -54,38 +47,48 @@ class WebMission(BaseSurvey):
|
||||||
self.label_title.setFont(font_title)
|
self.label_title.setFont(font_title)
|
||||||
|
|
||||||
# web page
|
# web page
|
||||||
self.browser = ui.Browser()
|
self.browser = widget.Browser()
|
||||||
self._layout.addWidget(self.browser)
|
self._layout.addWidget(self.browser)
|
||||||
|
|
||||||
self.browser.web.focusProxy().installEventFilter(self) # capture the event in eventFilter
|
self.browser.web.focusProxy().installEventFilter(self) # capture the event in eventFilter
|
||||||
self.browser.web.urlChanged.connect(self._on_url_changed) # NOQA: connect exist
|
self.browser.web.urlChanged.connect(self._on_url_changed) # NOQA: connect exist
|
||||||
|
|
||||||
self.browser.web.setUrl(QUrl(self.default_url))
|
self.browser.web.setUrl(QUrl(self.default_url))
|
||||||
self.browser.web.loadFinished.connect(self._initialise_js) # NOQA: connect exist
|
|
||||||
|
|
||||||
self.browser.web.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
self.browser.web.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
|
|
||||||
|
# setup the timer for the check
|
||||||
|
self.timer_check = None
|
||||||
|
if self.check_condition is not None:
|
||||||
|
self.timer_check = QTimer()
|
||||||
|
self.timer_check.setInterval(100)
|
||||||
|
self.timer_check.timeout.connect(self.check) # NOQA: connect exist
|
||||||
|
|
||||||
|
# setup the timer for skipping the mission
|
||||||
|
self.timer_skip = None
|
||||||
|
if self.skip_time is not None:
|
||||||
|
self.timer_skip = QTimer()
|
||||||
|
self.timer_skip.setInterval(self.skip_time * 1000)
|
||||||
|
self.timer_skip.timeout.connect(self._on_time_skip) # NOQA: connect exist
|
||||||
|
|
||||||
# setup the events
|
# setup the events
|
||||||
self.browser.web.page().scrollPositionChanged.connect(self._on_scroll_position_changed)
|
self.browser.web.page().scrollPositionChanged.connect(self._on_scroll_position_changed)
|
||||||
|
|
||||||
# navigation
|
# navigation
|
||||||
self.navigation = ui.SurveyNavigation(signals=signals)
|
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||||
self._layout.addWidget(self.navigation)
|
self._layout.addWidget(self.navigation)
|
||||||
|
|
||||||
# initialize the start time
|
# initialize the start time
|
||||||
self.start_time = time.time()
|
self.start_time = time.time()
|
||||||
|
|
||||||
# initialise the timers
|
# check timer
|
||||||
self.timer_start_check: Optional[QTimer] = None
|
if self.timer_check is not None:
|
||||||
self.timer_check: Optional[QTimer] = None
|
# enable the timer
|
||||||
self.timer_skip: Optional[QTimer] = None
|
self.timer_check.start()
|
||||||
|
else:
|
||||||
|
self._success() # call directly the success method
|
||||||
|
|
||||||
# skip timer
|
# skip timer
|
||||||
if self.skip_time is not None:
|
if self.timer_skip is not None:
|
||||||
self.timer_skip = QTimer()
|
# enable the timer for skipping the question
|
||||||
self.timer_skip.setInterval(self.skip_time * 1000)
|
|
||||||
self.timer_skip.timeout.connect(self._allow_time_skip) # NOQA: connect exist
|
|
||||||
|
|
||||||
self.timer_skip.start()
|
self.timer_skip.start()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -93,11 +96,7 @@ class WebMission(BaseSurvey):
|
||||||
return cls(
|
return cls(
|
||||||
title=data["title"],
|
title=data["title"],
|
||||||
url=data.get("url"),
|
url=data.get("url"),
|
||||||
|
check_condition=data.get("check"),
|
||||||
initial_js=data.get("initial_js"),
|
|
||||||
start_check_js=data.get("start_check_js"),
|
|
||||||
check_js=data.get("check_js"),
|
|
||||||
|
|
||||||
skip_time=data.get("skip_time"),
|
skip_time=data.get("skip_time"),
|
||||||
|
|
||||||
signals=signals
|
signals=signals
|
||||||
|
@ -206,80 +205,30 @@ class WebMission(BaseSurvey):
|
||||||
url=self.browser.web.url().toString()
|
url=self.browser.web.url().toString()
|
||||||
)
|
)
|
||||||
|
|
||||||
def _allow_time_skip(self):
|
def _on_time_skip(self):
|
||||||
# when the timer to allow skip have run out
|
# when the timer to allow skip have run out
|
||||||
if "skip" in self.signals:
|
if "skip" in self.signals:
|
||||||
self.navigation.show_skip()
|
self.navigation.show_skip()
|
||||||
|
|
||||||
# condition
|
# condition
|
||||||
|
|
||||||
def _preprocess_check(self, check: str) -> str:
|
def check(self) -> None:
|
||||||
"""
|
|
||||||
Preprocess a check
|
|
||||||
"""
|
|
||||||
|
|
||||||
check = check.replace("#LANGUAGE_CODE#", translate.get_language())
|
|
||||||
check = check.replace("#START_TIME#", str(self.start_time))
|
|
||||||
|
|
||||||
return check
|
|
||||||
|
|
||||||
def _initialise_js(self, ok: bool):
|
|
||||||
# prevent the event from being call a second time
|
|
||||||
self.browser.web.loadFinished.disconnect(self._initialise_js)
|
|
||||||
|
|
||||||
def callback(result: bool):
|
|
||||||
self.timer_start_check = QTimer()
|
|
||||||
self.timer_start_check.setInterval(100)
|
|
||||||
self.timer_start_check.timeout.connect(self._start_check) # NOQA: connect exist
|
|
||||||
self.timer_start_check.start()
|
|
||||||
|
|
||||||
# run the initial command
|
|
||||||
self.browser.web.page().runJavaScript(
|
|
||||||
self._preprocess_check(self.initial_js),
|
|
||||||
resultCallback=callback
|
|
||||||
)
|
|
||||||
|
|
||||||
def _start_check(self) -> None:
|
|
||||||
"""
|
|
||||||
Check if the real checking condition should start
|
|
||||||
"""
|
|
||||||
|
|
||||||
# if the check evaluated to True, enable the normal check
|
|
||||||
def callback(result: bool):
|
|
||||||
if result:
|
|
||||||
# stop this start check timer
|
|
||||||
self.timer_start_check.stop()
|
|
||||||
|
|
||||||
# create a new timer for the normal check
|
|
||||||
self.timer_check = QTimer()
|
|
||||||
self.timer_check.setInterval(100)
|
|
||||||
self.timer_check.timeout.connect(self._check) # NOQA: connect exist
|
|
||||||
self.timer_check.start()
|
|
||||||
|
|
||||||
# run the check
|
|
||||||
self.browser.web.page().runJavaScript(
|
|
||||||
self._preprocess_check(self.start_check_js),
|
|
||||||
resultCallback=callback
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check(self) -> None:
|
|
||||||
"""
|
"""
|
||||||
Check if the checking condition have been completed
|
Check if the checking condition have been completed
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# if the check evaluated to True, call the success method
|
def check_callback(result: bool):
|
||||||
def callback(result: bool):
|
|
||||||
if result:
|
if result:
|
||||||
# stop this check timer
|
|
||||||
self.timer_check.stop()
|
|
||||||
# mark the test as successful
|
|
||||||
self._success()
|
self._success()
|
||||||
|
|
||||||
# run the check
|
page = self.browser.web.page()
|
||||||
self.browser.web.page().runJavaScript(
|
|
||||||
self._preprocess_check(self.check_js),
|
# preprocess the condition
|
||||||
resultCallback=callback
|
condition = self.check_condition
|
||||||
)
|
condition = condition.replace("#LANGUAGE_CODE#", translate.get_language())
|
||||||
|
|
||||||
|
# run the condition
|
||||||
|
page.runJavaScript(condition, resultCallback=check_callback)
|
||||||
|
|
||||||
# data collection
|
# data collection
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@ from PyQt6.QtCore import Qt, pyqtSignal
|
||||||
from PyQt6.QtGui import QFont
|
from PyQt6.QtGui import QFont
|
||||||
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QButtonGroup, QLineEdit, QAbstractButton
|
from PyQt6.QtWidgets import QFrame, QVBoxLayout, QLabel, QButtonGroup, QLineEdit, QAbstractButton
|
||||||
|
|
||||||
from source import translate, ui
|
from source import translate, widget
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class BaseChoiceQuestion(BaseSurvey):
|
||||||
self.label_question.setFont(font_title)
|
self.label_question.setFont(font_title)
|
||||||
|
|
||||||
# prepare navigation
|
# prepare navigation
|
||||||
self.navigation = ui.SurveyNavigation(signals=signals)
|
self.navigation = widget.SurveyNavigation(signals=signals)
|
||||||
|
|
||||||
# responses
|
# responses
|
||||||
self.frame_responses = QFrame()
|
self.frame_responses = QFrame()
|
||||||
|
@ -102,10 +102,6 @@ class BaseChoiceQuestion(BaseSurvey):
|
||||||
# add the navigation
|
# add the navigation
|
||||||
self._layout.addWidget(self.navigation)
|
self._layout.addWidget(self.navigation)
|
||||||
|
|
||||||
if not self.are_buttons_exclusive():
|
|
||||||
# if the buttons are not exclusive, allow to select no options
|
|
||||||
self.navigation.show_forward()
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "BaseChoiceQuestion":
|
def from_dict(cls, data: dict[str, Any], signals: dict[str, pyqtSignal]) -> "BaseChoiceQuestion":
|
||||||
return cls(
|
return cls(
|
||||||
|
@ -118,7 +114,7 @@ class BaseChoiceQuestion(BaseSurvey):
|
||||||
collected_data = {
|
collected_data = {
|
||||||
"choices": {
|
"choices": {
|
||||||
choice_id: {
|
choice_id: {
|
||||||
"checked": choice_data["button"].isChecked(),
|
"checked": choice_data["button"].isEnabled(),
|
||||||
"details": entry.text() if (entry := choice_data["entry"]) is not None else None
|
"details": entry.text() if (entry := choice_data["entry"]) is not None else None
|
||||||
}
|
}
|
||||||
for choice_id, choice_data in self.buttons_responses.items()
|
for choice_id, choice_data in self.buttons_responses.items()
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
import typing
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt, QLocale
|
|
||||||
from PyQt6.QtWidgets import QWidget, QLabel, QVBoxLayout, QComboBox, QPushButton
|
|
||||||
|
|
||||||
from source import assets_path, translate, ui
|
|
||||||
|
|
||||||
|
|
||||||
class LanguageSelection(QWidget):
|
|
||||||
def __init__(self, parent: QWidget, after: Callable):
|
|
||||||
super().__init__(parent=parent)
|
|
||||||
|
|
||||||
self.after = after
|
|
||||||
|
|
||||||
# layout
|
|
||||||
self._layout = QVBoxLayout()
|
|
||||||
self.setLayout(self._layout)
|
|
||||||
|
|
||||||
# title
|
|
||||||
self.title = QLabel()
|
|
||||||
self._layout.addWidget(self.title)
|
|
||||||
self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
||||||
|
|
||||||
# language selection
|
|
||||||
self.select_language = QComboBox()
|
|
||||||
self._layout.addWidget(self.select_language)
|
|
||||||
|
|
||||||
language_default = QLocale().language() # get the default locale
|
|
||||||
|
|
||||||
for i, language_file in enumerate((assets_path / "language").glob("*.qm")):
|
|
||||||
# add every translated language to the selection
|
|
||||||
|
|
||||||
language_code: str = language_file.stem
|
|
||||||
language = QLocale.codeToLanguage(language_code, QLocale.LanguageCodeType.ISO639)
|
|
||||||
|
|
||||||
self.select_language.addItem(language.name, language_code)
|
|
||||||
|
|
||||||
# if the added language is the default one, select it directly
|
|
||||||
if language == language_default:
|
|
||||||
self.select_language.setCurrentIndex(i)
|
|
||||||
|
|
||||||
self.select_language.currentIndexChanged.connect(self.refresh_language) # NOQA: connect exist
|
|
||||||
|
|
||||||
# start button
|
|
||||||
self.button_start = QPushButton()
|
|
||||||
self._layout.addWidget(self.button_start)
|
|
||||||
self.button_start.clicked.connect(self.start) # NOQA: connect exist
|
|
||||||
|
|
||||||
# refresh the texts
|
|
||||||
self.refresh_language()
|
|
||||||
|
|
||||||
super().show()
|
|
||||||
|
|
||||||
def refresh_language(self):
|
|
||||||
language_code = self.select_language.currentData()
|
|
||||||
|
|
||||||
# load the correct translation in the window
|
|
||||||
window = typing.cast(ui.SurveyWindow, self.window())
|
|
||||||
window.translator.load(str(assets_path / f"language/{language_code}.qm"))
|
|
||||||
|
|
||||||
# apply the language on the custom translator
|
|
||||||
translate.set_language(language_code)
|
|
||||||
|
|
||||||
# refresh the texts
|
|
||||||
self.retranslate()
|
|
||||||
|
|
||||||
def retranslate(self):
|
|
||||||
self.title.setText(self.tr("SELECT YOUR LANGUAGE"))
|
|
||||||
self.button_start.setText(self.tr("START"))
|
|
||||||
|
|
||||||
def start(self) -> None:
|
|
||||||
# call the after event
|
|
||||||
self.after()
|
|
||||||
|
|
||||||
# delete the language selection
|
|
||||||
self.deleteLater()
|
|
|
@ -1,34 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from PyQt6.QtCore import QTranslator
|
|
||||||
from PyQt6.QtGui import QIcon
|
|
||||||
from PyQt6.QtWidgets import QMainWindow, QApplication
|
|
||||||
|
|
||||||
from source import ui, assets_path, __icon_png__, __appname__
|
|
||||||
|
|
||||||
icon_path = assets_path / "icon.png"
|
|
||||||
|
|
||||||
|
|
||||||
class SurveyWindow(QMainWindow):
|
|
||||||
def __init__(self, survey_path: Path | str):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.translator = QTranslator()
|
|
||||||
QApplication.instance().installTranslator(self.translator)
|
|
||||||
|
|
||||||
# window style
|
|
||||||
self.setWindowIcon(QIcon(__icon_png__))
|
|
||||||
self.setWindowTitle(__appname__)
|
|
||||||
|
|
||||||
# start by asking the user his language
|
|
||||||
self.language_selection = ui.LanguageSelection(
|
|
||||||
parent=self,
|
|
||||||
# after the language is selected, start the survey
|
|
||||||
after=lambda: self.setCentralWidget(ui.SurveyEngine.from_file(survey_path))
|
|
||||||
)
|
|
||||||
self.setCentralWidget(self.language_selection)
|
|
||||||
|
|
||||||
def quit(self):
|
|
||||||
# quit the application by closing and deleting the window
|
|
||||||
self.window().close()
|
|
||||||
self.window().deleteLater()
|
|
|
@ -77,7 +77,7 @@ class Browser(QWidget):
|
||||||
# update the progress bar
|
# update the progress bar
|
||||||
self.progress.setValue(value)
|
self.progress.setValue(value)
|
||||||
|
|
||||||
def _load_finished(self, ok: bool):
|
def _load_finished(self):
|
||||||
# update the progress bar
|
# update the progress bar
|
||||||
self.progress.hide()
|
self.progress.hide()
|
||||||
# refresh the navigation buttons
|
# refresh the navigation buttons
|
|
@ -8,7 +8,7 @@ from typing import Optional
|
||||||
from PyQt6.QtCore import pyqtSignal
|
from PyQt6.QtCore import pyqtSignal
|
||||||
from PyQt6.QtWidgets import QVBoxLayout, QProgressBar, QWidget, QMessageBox
|
from PyQt6.QtWidgets import QVBoxLayout, QProgressBar, QWidget, QMessageBox
|
||||||
|
|
||||||
from source import translate, ui, save
|
from source import translate, widget, save
|
||||||
from source.survey.base import BaseSurvey
|
from source.survey.base import BaseSurvey
|
||||||
from source.survey import Empty, survey_get
|
from source.survey import Empty, survey_get
|
||||||
from source.utils import compress
|
from source.utils import compress
|
||||||
|
@ -93,7 +93,7 @@ class SurveyEngine(QWidget):
|
||||||
|
|
||||||
def _on_signal_abandon(self):
|
def _on_signal_abandon(self):
|
||||||
# on abandon, quit the survey
|
# on abandon, quit the survey
|
||||||
window = typing.cast(ui.SurveyWindow, self.window())
|
window = typing.cast(widget.SurveyWindow, self.window())
|
||||||
window.quit()
|
window.quit()
|
||||||
|
|
||||||
def _on_signal_skip(self):
|
def _on_signal_skip(self):
|
||||||
|
@ -166,7 +166,7 @@ class SurveyEngine(QWidget):
|
||||||
)
|
)
|
||||||
|
|
||||||
# finally, close the window
|
# finally, close the window
|
||||||
window = typing.cast(ui.SurveyWindow, self.window())
|
window = typing.cast(widget.SurveyWindow, self.window())
|
||||||
window.quit()
|
window.quit()
|
||||||
|
|
||||||
# signals
|
# signals
|
||||||
|
@ -175,5 +175,5 @@ class SurveyEngine(QWidget):
|
||||||
QMessageBox.warning(
|
QMessageBox.warning(
|
||||||
self,
|
self,
|
||||||
self.tr("WARNING"),
|
self.tr("WARNING"),
|
||||||
message,
|
self.tr(message),
|
||||||
)
|
)
|
23
source/widget/SurveyWindow.py
Normal file
23
source/widget/SurveyWindow.py
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from PyQt6.QtGui import QIcon
|
||||||
|
from PyQt6.QtWidgets import QMainWindow
|
||||||
|
|
||||||
|
from source import widget, assets_path, __icon_png__
|
||||||
|
|
||||||
|
icon_path = assets_path / "icon.png"
|
||||||
|
|
||||||
|
|
||||||
|
class SurveyWindow(QMainWindow):
|
||||||
|
def __init__(self, survey_path: Path | str):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.setWindowIcon(QIcon(__icon_png__))
|
||||||
|
self.setWindowTitle(self.tr("SURVEY"))
|
||||||
|
|
||||||
|
self.setCentralWidget(widget.SurveyEngine.from_file(survey_path))
|
||||||
|
|
||||||
|
def quit(self):
|
||||||
|
# quit the application by closing and deleting the window
|
||||||
|
self.window().close()
|
||||||
|
self.window().deleteLater()
|
|
@ -1,5 +1,4 @@
|
||||||
from .Browser import Browser
|
from .Browser import Browser
|
||||||
from .LanguageSelection import LanguageSelection
|
|
||||||
from .SurveyEngine import SurveyEngine
|
from .SurveyEngine import SurveyEngine
|
||||||
from .SurveyWindow import SurveyWindow
|
from .SurveyWindow import SurveyWindow
|
||||||
from .SurveyNavigation import SurveyNavigation
|
from .SurveyNavigation import SurveyNavigation
|
|
@ -1,21 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
x = list(map(extract.age.extract, datas))
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Âge des personnes sondées")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
bins = np.arange(min(x), max(x), 1)
|
|
||||||
axes.hist(x, bins=bins, edgecolor='black')
|
|
||||||
axes.set_xlabel("Âge")
|
|
||||||
axes.set_ylabel("Quantité")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,42 +0,0 @@
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
ages_completion: dict[int, int] = defaultdict(lambda: 0)
|
|
||||||
ages_count: dict[int, int] = defaultdict(lambda: 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
age = extract.age.extract(data)
|
|
||||||
ages_count[age] += 1
|
|
||||||
|
|
||||||
for survey, survey_data in data["surveys"].items():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
ages_completion[age] += 1
|
|
||||||
|
|
||||||
x = list(ages_completion.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(ages_completion.values()))
|
|
||||||
/ np.array(list(ages_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Nombre moyen de mission complété par âge")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
bins = np.arange(min(x), max(x), 1)
|
|
||||||
axes.hist(x, bins, weights=y, edgecolor="black")
|
|
||||||
axes.set_xlabel("Âge")
|
|
||||||
axes.set_ylabel("Complétion")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,41 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
experience_completion: dict[str, int] = dict.fromkeys(ressource.experience.choices, 0)
|
|
||||||
experience_count: dict[str, int] = dict.fromkeys(ressource.experience.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
experience = extract.experience.extract(data)
|
|
||||||
experience_count[experience] += 1
|
|
||||||
|
|
||||||
for survey, survey_data in data["surveys"].items():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
experience_completion[experience] += 1
|
|
||||||
|
|
||||||
x = list(experience_completion.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(experience_completion.values()))
|
|
||||||
/ np.array(list(experience_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Nombre moyen de mission complété par opinion")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.experience.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.experience.labels)
|
|
||||||
axes.set_xlabel("Expérience")
|
|
||||||
axes.set_ylabel("Complétion")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,41 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
languages_completion: dict[str, int] = dict.fromkeys(ressource.language.choices, 0)
|
|
||||||
languages_count: dict[str, int] = dict.fromkeys(ressource.language.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
language = extract.language.extract(data)
|
|
||||||
languages_count[language] += 1
|
|
||||||
|
|
||||||
for survey, survey_data in data["surveys"].items():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
languages_completion[language] += 1
|
|
||||||
|
|
||||||
x = list(languages_completion.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(languages_completion.values()))
|
|
||||||
/ np.array(list(languages_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Nombre moyen de mission complété par langue")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.language.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.language.labels)
|
|
||||||
axes.set_xlabel("Langue")
|
|
||||||
axes.set_ylabel("Complétion")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,36 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
completions: dict[str] = dict.fromkeys(ressource.mission.choices, 0)
|
|
||||||
|
|
||||||
# TODO : couleur par mission
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
completions[survey] += 1
|
|
||||||
|
|
||||||
x = list(completions.keys())
|
|
||||||
y = list(completions.values())
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Nombre de mission total complété")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.mission.colors, edgecolor='black')
|
|
||||||
axes.set_xlabel("Mission")
|
|
||||||
axes.set_ylabel("Complétion")
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.mission.labels, rotation=45, ha="right")
|
|
||||||
figure.tight_layout()
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,45 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
usage_completion: dict[str, int] = dict.fromkeys(ressource.usage.choices, 0)
|
|
||||||
usage_count: dict[str, int] = dict.fromkeys(ressource.usage.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
usage = next(filter(
|
|
||||||
lambda it: it[1]["checked"],
|
|
||||||
data["surveys"]["question-usage-steam"]["choices"].items()
|
|
||||||
))[0]
|
|
||||||
|
|
||||||
usage_count[usage] += 1
|
|
||||||
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
usage_completion[usage] += 1
|
|
||||||
|
|
||||||
x = list(usage_completion.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(usage_completion.values()))
|
|
||||||
/ np.array(list(usage_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Nombre moyen de mission complété par habitude d'utilisation")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.usage.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.usage.labels)
|
|
||||||
axes.set_xlabel("Usage")
|
|
||||||
axes.set_ylabel("Complétion")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,43 +0,0 @@
|
||||||
from collections import defaultdict
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
ages_duration: dict[int, int] = defaultdict(lambda: 0)
|
|
||||||
ages_count: dict[int, int] = defaultdict(lambda: 0)
|
|
||||||
|
|
||||||
# TODO: affichage en minutes ?
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
age = extract.age.extract(data)
|
|
||||||
ages_count[age] += 1
|
|
||||||
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
ages_duration[age] += extract.mission_duration.extract(data, survey)
|
|
||||||
|
|
||||||
x = list(ages_duration.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(ages_duration.values()))
|
|
||||||
/ np.array(list(ages_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Temps moyen passé par âge")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
bins = np.arange(min(x), max(x), 1)
|
|
||||||
axes.hist(x, bins, weights=y, edgecolor="black")
|
|
||||||
axes.set_xlabel("Âge")
|
|
||||||
axes.set_ylabel("Durée")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,41 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
experience_duration: dict[str, int] = dict.fromkeys(ressource.experience.choices, 0)
|
|
||||||
experience_count: dict[str, int] = dict.fromkeys(ressource.experience.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
experience = extract.experience.extract(data)
|
|
||||||
experience_count[experience] += 1
|
|
||||||
|
|
||||||
for survey, survey_data in data["surveys"].items():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if extract.mission_completed.extract(data, survey):
|
|
||||||
experience_duration[experience] += extract.mission_duration.extract(data, survey)
|
|
||||||
|
|
||||||
x = list(experience_duration.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(experience_duration.values()))
|
|
||||||
/ np.array(list(experience_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Temps moyen passé par opinion")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.experience.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.experience.labels)
|
|
||||||
axes.set_xlabel("Expérience")
|
|
||||||
axes.set_ylabel("Durée")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,40 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
languages_duration: dict[str, int] = dict.fromkeys(ressource.language.choices, 0)
|
|
||||||
languages_count: dict[str, int] = dict.fromkeys(ressource.language.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
language = extract.language.extract(data)
|
|
||||||
languages_count[language] += 1
|
|
||||||
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
languages_duration[language] += extract.mission_duration.extract(data, survey)
|
|
||||||
|
|
||||||
x = list(languages_duration.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(languages_duration.values()))
|
|
||||||
/ np.array(list(languages_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Temps moyen passé par langue")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.language.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.language.labels)
|
|
||||||
axes.set_xlabel("Langue")
|
|
||||||
axes.set_ylabel("Durée")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,37 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
durations: dict[str] = dict.fromkeys(ressource.mission.choices, 0)
|
|
||||||
|
|
||||||
# TODO : marqué en rouge la durée d'abandon ?
|
|
||||||
# TODO : couleur par mission
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
durations[survey] += extract.mission_duration.extract(data, survey)
|
|
||||||
|
|
||||||
x = list(durations.keys())
|
|
||||||
y = np.array(list(durations.values())) / len(datas)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Temps total passé par mission")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.mission.colors, edgecolor='black')
|
|
||||||
axes.set_xlabel("Mission")
|
|
||||||
axes.set_ylabel("Durée")
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.mission.labels, rotation=45, ha="right")
|
|
||||||
figure.tight_layout()
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,40 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
usage_completion: dict[str, int] = dict.fromkeys(ressource.usage.choices, 0)
|
|
||||||
usage_count: dict[str, int] = dict.fromkeys(ressource.usage.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
usage = extract.usage.extract(data)
|
|
||||||
usage_count[usage] += 1
|
|
||||||
|
|
||||||
for survey in data["surveys"].keys():
|
|
||||||
# only scan survey mission
|
|
||||||
if not survey.startswith("mission-"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
usage_completion[usage] += extract.mission_duration.extract(data, survey)
|
|
||||||
|
|
||||||
x = list(usage_completion.keys())
|
|
||||||
y = (
|
|
||||||
np.array(list(usage_completion.values()))
|
|
||||||
/ np.array(list(usage_count.values()))
|
|
||||||
)
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Temps moyen passé par habitude d'utilisation")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.usage.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.usage.labels)
|
|
||||||
axes.set_xlabel("Usage")
|
|
||||||
axes.set_ylabel("Durée")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,30 +0,0 @@
|
||||||
from collections import Counter
|
|
||||||
|
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
experiences: dict[str, int] = dict.fromkeys(ressource.experience.choices, 0)
|
|
||||||
for data in datas:
|
|
||||||
experience = extract.experience.extract(data)
|
|
||||||
experiences[experience] += 1
|
|
||||||
|
|
||||||
counter = Counter(experiences)
|
|
||||||
x = list(counter.keys())
|
|
||||||
y = list(counter.values())
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Opinion des personnes sondées")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.experience.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.experience.labels)
|
|
||||||
axes.set_xlabel("Expérience")
|
|
||||||
axes.set_ylabel("Quantité")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,28 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
missions = dict.fromkeys(ressource.mission.choices, 0)
|
|
||||||
|
|
||||||
for data in datas:
|
|
||||||
missions[extract.hardest_mission.extract(data)] += 1
|
|
||||||
|
|
||||||
x = list(missions.keys())
|
|
||||||
y = list(missions.values())
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Mission la plus difficile des personnes sondées")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.mission.colors, edgecolor='black')
|
|
||||||
axes.set_xlabel("Mission")
|
|
||||||
axes.set_ylabel("Quantité")
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.mission.labels, rotation=45, ha="right")
|
|
||||||
figure.tight_layout()
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,26 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
languages = dict.fromkeys(ressource.language.choices, 0)
|
|
||||||
for language in map(extract.language.extract, datas):
|
|
||||||
languages[language] += 1
|
|
||||||
|
|
||||||
x = list(languages.keys())
|
|
||||||
y = list(languages.values())
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Langue des personnes sondées")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.language.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.language.labels)
|
|
||||||
axes.set_xlabel("Langue")
|
|
||||||
axes.set_ylabel("Quantité")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,26 +0,0 @@
|
||||||
import matplotlib.pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics import extract, ressource
|
|
||||||
|
|
||||||
|
|
||||||
def analyse(datas: list[dict]) -> plt.Figure:
|
|
||||||
usages: dict[str, int] = dict.fromkeys(ressource.usage.choices, 0)
|
|
||||||
for usage in map(extract.usage.extract, datas):
|
|
||||||
usages[usage] += 1
|
|
||||||
|
|
||||||
x = list(usages.keys())
|
|
||||||
y = list(usages.values())
|
|
||||||
|
|
||||||
# prepare plotting
|
|
||||||
figure: plt.Figure = plt.figure()
|
|
||||||
axes = figure.add_subplot(1, 1, 1)
|
|
||||||
axes.set_title("Habitude d'utilisation des personnes sondées")
|
|
||||||
|
|
||||||
# bar chart
|
|
||||||
axes.bar(x, y, color=ressource.usage.colors, edgecolor='black')
|
|
||||||
axes.set_xticks(x)
|
|
||||||
axes.set_xticklabels(ressource.usage.labels)
|
|
||||||
axes.set_xlabel("Usage")
|
|
||||||
axes.set_ylabel("Quantité")
|
|
||||||
|
|
||||||
return figure
|
|
|
@ -1,7 +0,0 @@
|
||||||
from . import usage
|
|
||||||
from . import age
|
|
||||||
from . import mission_duration
|
|
||||||
from . import mission_completed
|
|
||||||
from . import experience
|
|
||||||
from . import hardest_mission
|
|
||||||
from . import language
|
|
|
@ -1,2 +0,0 @@
|
||||||
def extract(data: dict) -> int:
|
|
||||||
return data["surveys"]["question-age"]["value"]
|
|
|
@ -1,5 +0,0 @@
|
||||||
def extract(data: dict) -> str:
|
|
||||||
return next(filter(
|
|
||||||
lambda it: it[1]["checked"],
|
|
||||||
data["surveys"]["question-experience"]["choices"].items()
|
|
||||||
))[0]
|
|
|
@ -1,5 +0,0 @@
|
||||||
def extract(data: dict) -> str:
|
|
||||||
return next(filter(
|
|
||||||
lambda it: it[1]["checked"],
|
|
||||||
data["surveys"]["question-hardest-mission"]["choices"].items()
|
|
||||||
))[0]
|
|
|
@ -1,2 +0,0 @@
|
||||||
def extract(data: dict) -> str:
|
|
||||||
return data["language"]
|
|
|
@ -1,9 +0,0 @@
|
||||||
def extract(data: dict, mission: str) -> bool:
|
|
||||||
events = data["surveys"][mission]["event"]
|
|
||||||
|
|
||||||
try:
|
|
||||||
checks = next(filter(lambda event: event["type"] == "check", events))
|
|
||||||
except StopIteration:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
return True
|
|
|
@ -1,2 +0,0 @@
|
||||||
def extract(data: dict, mission: str) -> float:
|
|
||||||
return data["surveys"][mission]["event"][-1]["time"]
|
|
|
@ -1,5 +0,0 @@
|
||||||
def extract(data: dict) -> str:
|
|
||||||
return next(filter(
|
|
||||||
lambda it: it[1]["checked"],
|
|
||||||
data["surveys"]["question-usage-steam"]["choices"].items()
|
|
||||||
))[0]
|
|
|
@ -1,70 +0,0 @@
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from matplotlib import pyplot as plt
|
|
||||||
|
|
||||||
from tools.statistics.analyse import (age, usage, completion_per_mission, duration_per_mission, completion_per_age,
|
|
||||||
completion_per_usage, duration_per_age, duration_per_usage,
|
|
||||||
completion_per_experience, duration_per_experience, experience, hardest_mission,
|
|
||||||
language, duration_per_language, completion_per_language)
|
|
||||||
|
|
||||||
|
|
||||||
plt.rcParams['text.usetex'] = True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
from source.utils import compress
|
|
||||||
|
|
||||||
import matplotlib
|
|
||||||
matplotlib.use("pgf")
|
|
||||||
matplotlib.rcParams.update({
|
|
||||||
"pgf.texsystem": "pdflatex",
|
|
||||||
'font.family': 'serif',
|
|
||||||
'font.size': 11,
|
|
||||||
'text.usetex': True,
|
|
||||||
'pgf.rcfonts': False,
|
|
||||||
})
|
|
||||||
|
|
||||||
sondage_path = Path(r"./sondage/")
|
|
||||||
graph_path = Path(r"./graph/")
|
|
||||||
graph_path.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
# read every peoples survey data
|
|
||||||
datas_all = [
|
|
||||||
compress.uncompress_data(file.read_bytes()) # decompress the data
|
|
||||||
for file in sondage_path.rglob("*.rsl")
|
|
||||||
]
|
|
||||||
|
|
||||||
# keep only the datas before the steam new year update
|
|
||||||
datas_steam_version_1 = list(filter(lambda data: data["time"] < 1704409200, datas_all))
|
|
||||||
|
|
||||||
# keep only the datas after the steam new year update
|
|
||||||
datas_steam_version_2 = list(filter(lambda data: data["time"] >= 1704409200, datas_all))
|
|
||||||
|
|
||||||
# regroup all the datas
|
|
||||||
datasets = {
|
|
||||||
"all": datas_all,
|
|
||||||
"version_1": datas_steam_version_1,
|
|
||||||
"version_2": datas_steam_version_2,
|
|
||||||
}
|
|
||||||
|
|
||||||
for datas_name, datas in datasets.items():
|
|
||||||
directory = graph_path / datas_name
|
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
age.analyse(datas).savefig(directory / "age.svg")
|
|
||||||
usage.analyse(datas).savefig(directory / "usage.svg")
|
|
||||||
experience.analyse(datas).savefig(directory / "experience.svg")
|
|
||||||
hardest_mission.analyse(datas).savefig(directory / "hardest_mission.svg")
|
|
||||||
language.analyse(datas).savefig(directory / "language.svg")
|
|
||||||
|
|
||||||
completion_per_mission.analyse(datas).savefig(directory / "completion_per_mission.svg")
|
|
||||||
completion_per_age.analyse(datas).savefig(directory / "completion_per_age.svg")
|
|
||||||
completion_per_usage.analyse(datas).savefig(directory / "completion_per_usage.svg")
|
|
||||||
completion_per_experience.analyse(datas).savefig(directory / "completion_per_experience.svg")
|
|
||||||
completion_per_language.analyse(datas).savefig(directory / "completion_per_language.svg")
|
|
||||||
|
|
||||||
duration_per_mission.analyse(datas).savefig(directory / "duration_per_mission.svg")
|
|
||||||
duration_per_age.analyse(datas).savefig(directory / "duration_per_age.svg")
|
|
||||||
duration_per_usage.analyse(datas).savefig(directory / "duration_per_usage.svg")
|
|
||||||
duration_per_experience.analyse(datas).savefig(directory / "duration_per_experience.svg")
|
|
||||||
duration_per_language.analyse(datas).savefig(directory / "duration_per_language.svg")
|
|
|
@ -1,4 +0,0 @@
|
||||||
from . import experience
|
|
||||||
from . import mission
|
|
||||||
from . import usage
|
|
||||||
from . import language
|
|
|
@ -1,3 +0,0 @@
|
||||||
choices = ["yes", "mixed", "no"]
|
|
||||||
labels = ["Oui", "Mitigé", "Non"]
|
|
||||||
colors = ["g", "y", "r"]
|
|
|
@ -1,3 +0,0 @@
|
||||||
choices = ["fr", "en", "es"]
|
|
||||||
labels = ["Français", "Anglais", "Espagnol"]
|
|
||||||
colors = ["b", "g", "r"]
|
|
|
@ -1,36 +0,0 @@
|
||||||
choices = [
|
|
||||||
"mission-language",
|
|
||||||
"mission-price",
|
|
||||||
"mission-community-hub",
|
|
||||||
"mission-game-page",
|
|
||||||
"mission-game-dlc",
|
|
||||||
"mission-actuality-new",
|
|
||||||
"mission-profile",
|
|
||||||
"mission-game-discussion",
|
|
||||||
"mission-gift-card",
|
|
||||||
"mission-workshop",
|
|
||||||
]
|
|
||||||
labels = [
|
|
||||||
"Changement de Langue",
|
|
||||||
"Filtre de Prix",
|
|
||||||
"Hub de la Communauté",
|
|
||||||
"Page de Jeu",
|
|
||||||
"Page de DLC",
|
|
||||||
"Page d'Actualité",
|
|
||||||
"Page de Profil",
|
|
||||||
"Page de Discussion",
|
|
||||||
"Carte Cadeaux",
|
|
||||||
"Workshop",
|
|
||||||
]
|
|
||||||
colors = [
|
|
||||||
"tab:blue",
|
|
||||||
"tab:orange",
|
|
||||||
"tab:green",
|
|
||||||
"tab:red",
|
|
||||||
"tab:olive",
|
|
||||||
"tab:purple",
|
|
||||||
"tab:brown",
|
|
||||||
"tab:pink",
|
|
||||||
"tab:cyan",
|
|
||||||
"tab:gray",
|
|
||||||
]
|
|
|
@ -1,3 +0,0 @@
|
||||||
choices = ["always", "often", "sometime", "rarely", "never"]
|
|
||||||
labels = ["Toujours", "Souvent", "Parfois", "Rarement", "Jamais"]
|
|
||||||
colors = ["g", "c", "y", "m", "r"]
|
|
|
@ -1,20 +0,0 @@
|
||||||
# Web-Replay
|
|
||||||
|
|
||||||
Allow you to replay the `web-mission` tasks that a user did while taking a survey.
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
The installation is the same as the Survey-Engine. Refer to their installation step instead.
|
|
||||||
|
|
||||||
# Run
|
|
||||||
|
|
||||||
You must open a shell in the root directory (the `Survey-Engine` directory).
|
|
||||||
You can run the tool simply by using the command `python3 ./tools/web_replay/main.py`.
|
|
||||||
|
|
||||||
You will need to select a replay file `.rsl` and the mission you want to replay.
|
|
||||||
|
|
||||||
# Warning
|
|
||||||
|
|
||||||
The replay is in early development. Some features are missing and the replay is not very precise.
|
|
||||||
For now, key inputs don't seem to work correctly, so they are written on the bottom right instead.
|
|
||||||
Mouse movement are not perfect and mouse click don't seem to be recognised either.
|
|
|
@ -1,17 +0,0 @@
|
||||||
import sys
|
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QApplication
|
|
||||||
|
|
||||||
from ui import ReplayWindow
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
# create the application
|
|
||||||
application = QApplication(sys.argv)
|
|
||||||
|
|
||||||
# create the window
|
|
||||||
window = ReplayWindow("surveys.json")
|
|
||||||
window.show()
|
|
||||||
|
|
||||||
# start the application
|
|
||||||
application.exec()
|
|
|
@ -1,63 +0,0 @@
|
||||||
from PyQt6.QtCore import pyqtSignal
|
|
||||||
from PyQt6.QtWidgets import QWidget, QLineEdit, QPushButton, QVBoxLayout, QFrame, QHBoxLayout, QComboBox, QFileDialog
|
|
||||||
|
|
||||||
|
|
||||||
class ReplayConfiguration(QWidget):
|
|
||||||
signal_start_replay = pyqtSignal([str, str])
|
|
||||||
|
|
||||||
def __init__(self, missions: list[str]):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# layout
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
# replay path
|
|
||||||
self.frame_path = QFrame()
|
|
||||||
layout.addWidget(self.frame_path)
|
|
||||||
|
|
||||||
layout_path = QHBoxLayout()
|
|
||||||
self.frame_path.setLayout(layout_path)
|
|
||||||
|
|
||||||
self.entry_path = QLineEdit()
|
|
||||||
layout_path.addWidget(self.entry_path)
|
|
||||||
|
|
||||||
self.button_path = QPushButton()
|
|
||||||
layout_path.addWidget(self.button_path)
|
|
||||||
self.button_path.setText("...")
|
|
||||||
self.button_path.clicked.connect(self.select_replay) # NOQA: connect exist
|
|
||||||
|
|
||||||
# mission
|
|
||||||
self.frame_mission = QFrame()
|
|
||||||
layout.addWidget(self.frame_mission)
|
|
||||||
|
|
||||||
layout_mission = QHBoxLayout()
|
|
||||||
self.frame_mission.setLayout(layout_mission)
|
|
||||||
|
|
||||||
self.listbox_mission = QComboBox()
|
|
||||||
layout_mission.addWidget(self.listbox_mission)
|
|
||||||
|
|
||||||
for mission in missions:
|
|
||||||
self.listbox_mission.addItem(mission)
|
|
||||||
|
|
||||||
# button
|
|
||||||
self.button_confirm = QPushButton()
|
|
||||||
layout.addWidget(self.button_confirm)
|
|
||||||
self.button_confirm.setText("Replay")
|
|
||||||
self.button_confirm.clicked.connect(self._start_replay) # NOQA: connect exist
|
|
||||||
|
|
||||||
def select_replay(self):
|
|
||||||
# prompt the user for a file
|
|
||||||
file, raw_filetype = QFileDialog.getOpenFileName(
|
|
||||||
caption="Select the file to replay.",
|
|
||||||
filter="Replay (*.rsl)"
|
|
||||||
)
|
|
||||||
|
|
||||||
# if no file were selected, ignore
|
|
||||||
if not file:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.entry_path.setText(file)
|
|
||||||
|
|
||||||
def _start_replay(self):
|
|
||||||
self.signal_start_replay.emit(self.entry_path.text(), self.listbox_mission.currentText()) # NOQA: emit
|
|
|
@ -1,194 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
from PyQt6.QtCore import Qt, QUrl, QPointF, QTimer
|
|
||||||
from PyQt6.QtGui import QKeyEvent, QMouseEvent
|
|
||||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QApplication, QLabel
|
|
||||||
|
|
||||||
from source.utils import compress
|
|
||||||
from . import ReplayWebEngineView, ReplayNavigation
|
|
||||||
|
|
||||||
|
|
||||||
class ReplayEngine(QWidget):
|
|
||||||
"""
|
|
||||||
This widget allow to replay some event that occurred on a web page
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, replay_path: Path | str, mission: str):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# load the replay
|
|
||||||
with open(replay_path, "rb") as file:
|
|
||||||
replay_data = compress.uncompress_data(file.read())
|
|
||||||
|
|
||||||
self.start_time = datetime.fromtimestamp(replay_data["time"])
|
|
||||||
self.replay_events = replay_data["surveys"][mission]["event"]
|
|
||||||
self.replay_index: int = 0
|
|
||||||
self.replay_time: float = 0
|
|
||||||
|
|
||||||
# layout
|
|
||||||
self._layout = QVBoxLayout()
|
|
||||||
self.setLayout(self._layout)
|
|
||||||
|
|
||||||
# cursor
|
|
||||||
self.cursor = QLabel(self)
|
|
||||||
self.cursor.setFixedSize(20, 20)
|
|
||||||
self.cursor.setStyleSheet("background-color: red; border-radius: 10px;")
|
|
||||||
|
|
||||||
# web
|
|
||||||
self.web = ReplayWebEngineView(self.start_time)
|
|
||||||
self._layout.addWidget(self.web, 1)
|
|
||||||
|
|
||||||
# information
|
|
||||||
self.navigation = ReplayNavigation()
|
|
||||||
self._layout.addWidget(self.navigation)
|
|
||||||
|
|
||||||
# event timer
|
|
||||||
self.timer = QTimer()
|
|
||||||
|
|
||||||
def run_event(self, event: dict, callback: Callable):
|
|
||||||
# TODO: check if click are done correctly, check position, if event are correct, ...
|
|
||||||
|
|
||||||
match event["type"]:
|
|
||||||
case "success":
|
|
||||||
# success event
|
|
||||||
print(f"success ! ({event['time']}s)")
|
|
||||||
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "url":
|
|
||||||
# changing url event
|
|
||||||
self.web.setUrl(QUrl(event["url"]))
|
|
||||||
|
|
||||||
# callback
|
|
||||||
self.web.loadFinished.connect(lambda ok: callback()) # NOQA: connect exist
|
|
||||||
|
|
||||||
case "resize":
|
|
||||||
# changing widget size event
|
|
||||||
w, h = event["size"]
|
|
||||||
zoom_factor: float = self.web.page().contentsSize().width() / w
|
|
||||||
self.web.setZoomFactor(zoom_factor)
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "keyboard_press":
|
|
||||||
# keyboard key pressed event
|
|
||||||
qevent = QKeyEvent(
|
|
||||||
QKeyEvent.Type.KeyPress,
|
|
||||||
event["key"],
|
|
||||||
Qt.KeyboardModifier.NoModifier
|
|
||||||
)
|
|
||||||
qevent.custom = True
|
|
||||||
QApplication.postEvent(self.web.focusProxy(), qevent)
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "keyboard_release":
|
|
||||||
# keyboard key released event
|
|
||||||
qevent = QKeyEvent(
|
|
||||||
QKeyEvent.Type.KeyRelease,
|
|
||||||
event["key"],
|
|
||||||
Qt.KeyboardModifier.NoModifier
|
|
||||||
)
|
|
||||||
qevent.custom = True
|
|
||||||
QApplication.postEvent(self.web.focusProxy(), qevent)
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "mouse_press":
|
|
||||||
# mouse pressed event
|
|
||||||
qevent = QMouseEvent(
|
|
||||||
QMouseEvent.Type.KeyPress,
|
|
||||||
QPointF(*event["position"]) / self.web.zoomFactor(),
|
|
||||||
Qt.MouseButton(event["button"]),
|
|
||||||
Qt.MouseButton.NoButton,
|
|
||||||
Qt.KeyboardModifier.NoModifier
|
|
||||||
)
|
|
||||||
qevent.custom = True
|
|
||||||
QApplication.postEvent(self.web.focusProxy(), qevent)
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "mouse_release":
|
|
||||||
# mouse pressed event
|
|
||||||
qevent = QMouseEvent(
|
|
||||||
QMouseEvent.Type.KeyRelease,
|
|
||||||
QPointF(*event["position"]) / self.web.zoomFactor(),
|
|
||||||
Qt.MouseButton(event["button"]),
|
|
||||||
Qt.MouseButton.NoButton,
|
|
||||||
Qt.KeyboardModifier.NoModifier
|
|
||||||
)
|
|
||||||
qevent.custom = True
|
|
||||||
QApplication.postEvent(self.web.focusProxy(), qevent)
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "mouse_move":
|
|
||||||
# mouse moved event
|
|
||||||
qevent = QMouseEvent(
|
|
||||||
QMouseEvent.Type.MouseMove,
|
|
||||||
QPointF(*event["position"]) / self.web.zoomFactor(),
|
|
||||||
Qt.MouseButton.NoButton,
|
|
||||||
Qt.MouseButton.NoButton,
|
|
||||||
Qt.KeyboardModifier.NoModifier
|
|
||||||
)
|
|
||||||
qevent.custom = True
|
|
||||||
QApplication.postEvent(self.web.focusProxy(), qevent)
|
|
||||||
|
|
||||||
# move the cursor
|
|
||||||
self.cursor.move(QPointF(*event["position"]).toPoint())
|
|
||||||
self.cursor.raise_()
|
|
||||||
|
|
||||||
# callback
|
|
||||||
callback()
|
|
||||||
|
|
||||||
case "scroll":
|
|
||||||
# scroll event
|
|
||||||
x, y = event["position"]
|
|
||||||
self.web.page().runJavaScript(
|
|
||||||
f"window.scrollTo({x}, {y});",
|
|
||||||
resultCallback=lambda result: callback()
|
|
||||||
)
|
|
||||||
|
|
||||||
def next(self):
|
|
||||||
# get event information
|
|
||||||
if self.replay_index >= len(self.replay_events):
|
|
||||||
return
|
|
||||||
|
|
||||||
event = self.replay_events[self.replay_index]
|
|
||||||
self.replay_time = event["time"]
|
|
||||||
self.replay_index = self.replay_index + 1
|
|
||||||
|
|
||||||
# set text
|
|
||||||
self.navigation.set_description(f"{event}")
|
|
||||||
|
|
||||||
# run the event
|
|
||||||
self.run_event(
|
|
||||||
event,
|
|
||||||
self._next_callback
|
|
||||||
)
|
|
||||||
|
|
||||||
def _next_callback(self):
|
|
||||||
# prevent the web loading to call this function again
|
|
||||||
try:
|
|
||||||
self.web.loadFinished.disconnect(self._next_callback) # NOQA: disconnect exist
|
|
||||||
except TypeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# if there are still events after this one
|
|
||||||
if self.replay_index < len(self.replay_events):
|
|
||||||
# next event
|
|
||||||
next_event: dict = self.replay_events[self.replay_index]
|
|
||||||
next_time: float = next_event["time"]
|
|
||||||
|
|
||||||
# prepare the timer to play the event at the corresponding time
|
|
||||||
self.timer.singleShot(
|
|
||||||
round((next_time - self.replay_time) / 200),
|
|
||||||
self.next
|
|
||||||
)
|
|
|
@ -1,19 +0,0 @@
|
||||||
from PyQt6.QtCore import Qt
|
|
||||||
from PyQt6.QtWidgets import QWidget, QVBoxLayout, QLabel
|
|
||||||
|
|
||||||
|
|
||||||
class ReplayNavigation(QWidget):
|
|
||||||
def __init__(self):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# layout
|
|
||||||
layout = QVBoxLayout()
|
|
||||||
self.setLayout(layout)
|
|
||||||
|
|
||||||
# information of the replay
|
|
||||||
self._description = QLabel()
|
|
||||||
layout.addWidget(self._description)
|
|
||||||
self._description.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
|
||||||
|
|
||||||
def set_description(self, text: str):
|
|
||||||
self._description.setText(text)
|
|
|
@ -1,66 +0,0 @@
|
||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from PyQt6.QtCore import QObject, QEvent, QUrl
|
|
||||||
from PyQt6.QtWebEngineWidgets import QWebEngineView
|
|
||||||
|
|
||||||
|
|
||||||
class ReplayWebEngineView(QWebEngineView):
|
|
||||||
def __init__(self, start_time: datetime):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
self.start_time = start_time
|
|
||||||
self._last_url: Optional[QUrl] = None
|
|
||||||
|
|
||||||
self.loadFinished.connect(self._initialize_proxy_event) # NOQA: connect exist
|
|
||||||
|
|
||||||
# event filter
|
|
||||||
|
|
||||||
def setUrl(self, url: QUrl, archive: bool = True) -> None:
|
|
||||||
if archive:
|
|
||||||
# get the archive.org link corresponding to that time
|
|
||||||
archive_time: str = self.start_time.strftime("%Y%m%d%H%M%S")
|
|
||||||
url = QUrl(f"https://web.archive.org/web/{archive_time}/{url.toString()}")
|
|
||||||
|
|
||||||
self._last_url = url
|
|
||||||
|
|
||||||
# call the super function with the archive url instead
|
|
||||||
super().setUrl(url)
|
|
||||||
|
|
||||||
# clean the archive header popup that will appear
|
|
||||||
self.loadFinished.connect(self._on_load_finished) # NOQA: connect exist
|
|
||||||
|
|
||||||
def eventFilter(self, obj: QObject, event: QEvent) -> bool:
|
|
||||||
match event.type():
|
|
||||||
# allow scroll events (they are created automatically)
|
|
||||||
case event.Type.Scroll:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# allow timed events
|
|
||||||
case event.Type.Timer:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# ignore all other events
|
|
||||||
case _:
|
|
||||||
if not getattr(event, "custom", False):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return super().eventFilter(obj, event)
|
|
||||||
|
|
||||||
# events
|
|
||||||
|
|
||||||
def _initialize_proxy_event(self):
|
|
||||||
# make self.eventFilter intercept all focusProxy events
|
|
||||||
self.focusProxy().installEventFilter(self)
|
|
||||||
|
|
||||||
def _on_load_finished(self, ok: bool):
|
|
||||||
# prevent the event from being enabled another time
|
|
||||||
self.loadFinished.disconnect(self._on_load_finished) # NOQA: disconnect exist
|
|
||||||
|
|
||||||
if ok:
|
|
||||||
# hide archive.org header to avoid mouse movement being shifted
|
|
||||||
self.page().runJavaScript("document.getElementById('wm-ipp-base').style.display = 'none';")
|
|
||||||
self._initialize_proxy_event()
|
|
||||||
|
|
||||||
else:
|
|
||||||
self.setUrl(self._last_url, archive=False)
|
|
|
@ -1,38 +0,0 @@
|
||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from PyQt6.QtWidgets import QMainWindow
|
|
||||||
|
|
||||||
from tools.web_replay.ui import ReplayConfiguration, ReplayEngine
|
|
||||||
|
|
||||||
|
|
||||||
class ReplayWindow(QMainWindow):
|
|
||||||
def __init__(self, survey_path: Path | str):
|
|
||||||
super().__init__()
|
|
||||||
|
|
||||||
# get the survey configuration
|
|
||||||
with open(survey_path, encoding="utf-8") as file:
|
|
||||||
survey_configuration: dict = json.load(file)
|
|
||||||
|
|
||||||
# get all the missions available in the survey that can be replayed
|
|
||||||
missions: list[str] = [
|
|
||||||
mission_id
|
|
||||||
for mission_id, mission in survey_configuration["surveys"].items()
|
|
||||||
if mission["type"] == "mission-web"
|
|
||||||
]
|
|
||||||
|
|
||||||
# decoration
|
|
||||||
self.setWindowTitle("Survey Engine - Web Replay")
|
|
||||||
|
|
||||||
# show the configuration
|
|
||||||
self.replay_engine = None
|
|
||||||
self.replay_configuration = ReplayConfiguration(missions=missions)
|
|
||||||
self.setCentralWidget(self.replay_configuration)
|
|
||||||
self.replay_configuration.signal_start_replay.connect(self.start_replay)
|
|
||||||
|
|
||||||
def start_replay(self, replay_path: str, mission: str):
|
|
||||||
# start the replay
|
|
||||||
self.replay_engine = ReplayEngine(replay_path, mission)
|
|
||||||
self.setCentralWidget(self.replay_engine)
|
|
||||||
self.showFullScreen()
|
|
||||||
self.replay_engine.next()
|
|
|
@ -1,5 +0,0 @@
|
||||||
from .ReplayWebEngineView import ReplayWebEngineView
|
|
||||||
from .ReplayNavigation import ReplayNavigation
|
|
||||||
from .ReplayEngine import ReplayEngine
|
|
||||||
from .ReplayConfiguration import ReplayConfiguration
|
|
||||||
from .ReplayWindow import ReplayWindow
|
|
Loading…
Reference in a new issue