-
This commit is contained in:
parent
2c9a744667
commit
319b3410ba
@ -15,15 +15,24 @@ if (!get_autologin_state()) {
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SpreadQuiz</title>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<script src="js/req.js"></script>
|
||||
<script src="js/o.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<script src="js/default_frame.js"></script>
|
||||
<link rel="stylesheet" href="style/spreadquiz.css">
|
||||
</head>
|
||||
<body>
|
||||
<section id="game_list_panel">
|
||||
</section>
|
||||
|
||||
<section class="window" shown="false" id="results_window">
|
||||
<section class="window-inner">
|
||||
<section id="results_overview_display">
|
||||
|
||||
</section>
|
||||
<input type="button" value="Bezárás" onclick="hide('results_window')">
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
|
26
gamemgr.php
26
gamemgr.php
@ -13,7 +13,7 @@ const DEFAULT_GAME_PROPERTIES = [
|
||||
|
||||
function create_game(string $name, string $owner, string $description = "", array $properties = DEFAULT_GAME_PROPERTIES, array $contributors = [], array $challenges = []): bool
|
||||
{
|
||||
global $testdb;
|
||||
global $gamedb;
|
||||
$game_data = [
|
||||
"name" => $name,
|
||||
"owner" => $owner,
|
||||
@ -23,7 +23,7 @@ function create_game(string $name, string $owner, string $description = "", arra
|
||||
"properties" => $properties,
|
||||
"groups" => [],
|
||||
];
|
||||
$game_data = $testdb->insert($game_data);
|
||||
$game_data = $gamedb->insert($game_data);
|
||||
|
||||
// prepare game context
|
||||
$id = $game_data["_id"];
|
||||
@ -35,25 +35,25 @@ function create_game(string $name, string $owner, string $description = "", arra
|
||||
|
||||
function get_game(string $gameid): array
|
||||
{
|
||||
global $testdb;
|
||||
return $testdb->findById($gameid);
|
||||
global $gamedb;
|
||||
return $gamedb->findById($gameid);
|
||||
}
|
||||
|
||||
function update_game(array $game_data)
|
||||
{
|
||||
global $testdb;
|
||||
$testdb->update($game_data);
|
||||
global $gamedb;
|
||||
$gamedb->update($game_data);
|
||||
}
|
||||
|
||||
function delete_game(string $gameid)
|
||||
{
|
||||
global $testdb;
|
||||
global $gamedb;
|
||||
$game_data = get_game($gameid);
|
||||
if (count($game_data) != 0) {
|
||||
foreach ($game_data["groups"] as $groupid) {
|
||||
change_group_game_assignments($groupid, null, $gameid);
|
||||
}
|
||||
$testdb->deleteById($gameid);
|
||||
$gamedb->deleteById($gameid);
|
||||
}
|
||||
}
|
||||
|
||||
@ -68,18 +68,18 @@ function change_game_group_assignments(string $gid, $groupname_add, $groupname_r
|
||||
|
||||
function get_all_games()
|
||||
{
|
||||
global $testdb;
|
||||
return $testdb->findAll();
|
||||
global $gamedb;
|
||||
return $gamedb->findAll();
|
||||
}
|
||||
|
||||
function get_all_game_data_by_contributor_nickname(string $nickname): array
|
||||
{
|
||||
global $testdb;
|
||||
global $gamedb;
|
||||
$game_headers = [];
|
||||
if ($nickname !== "*") {
|
||||
$game_data_array = $testdb->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]);
|
||||
$game_data_array = $gamedb->findBy([["owner", "=", $nickname], "OR", ["contributors", "CONTAINS", $nickname]]);
|
||||
} else {
|
||||
$game_data_array = $testdb->findAll();
|
||||
$game_data_array = $gamedb->findAll();
|
||||
}
|
||||
foreach ($game_data_array as $game_data) {
|
||||
$game_headers[] = $game_data;
|
||||
|
@ -16,6 +16,7 @@ require_once "common_func.php";
|
||||
require_once "usermgr.php";
|
||||
require_once "groupmgr.php";
|
||||
require_once "gamemgr.php";
|
||||
require_once "testmgr.php";
|
||||
|
||||
$action = $_REQUEST["action"];
|
||||
|
||||
@ -83,6 +84,61 @@ switch ($action) {
|
||||
$result = json_encode($games_by_groups);
|
||||
}
|
||||
break;
|
||||
case "start_or_continue_test":
|
||||
{
|
||||
$gameid = trim($_REQUEST["gameid"] ?: "");
|
||||
$testid = create_or_continue_test($gameid, $nickname);
|
||||
$result = $testid;
|
||||
}
|
||||
break;
|
||||
case "get_results_overview":
|
||||
{
|
||||
$gameid = trim($_REQUEST["gameid"] ?: "");
|
||||
$concluded_tests = get_concluded_tests($gameid, $nickname);
|
||||
$overviews = [];
|
||||
foreach ($concluded_tests as $ct) {
|
||||
$overview = [
|
||||
"testid" => $ct["_id"],
|
||||
"start_time" => $ct["start_time"],
|
||||
"end_time" => $ct["end_time"],
|
||||
...$ct["summary"]
|
||||
];
|
||||
$overviews[] = $overview;
|
||||
}
|
||||
$result = json_encode($overviews);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// test-related queries
|
||||
if ((($testid = trim($_REQUEST["testid"] ?: "")) !== "") &&
|
||||
((count($test_data = get_test($testid))) > 0) &&
|
||||
($test_data["nickname"] === $nickname)) {
|
||||
|
||||
// update the test if timed
|
||||
update_timed_tests([ $test_data ]);
|
||||
|
||||
switch ($action) {
|
||||
case "get_test":
|
||||
{
|
||||
$test_data_with_current_time = $test_data;
|
||||
$test_data_with_current_time["current_time"] = time();
|
||||
$result = json_encode($test_data_with_current_time);
|
||||
}
|
||||
break;
|
||||
case "save_answer":
|
||||
{
|
||||
$chidx = $_REQUEST["challenge_index"];
|
||||
$answeridx = $_REQUEST["answer_index"];
|
||||
save_answer($testid, $chidx, $answeridx);
|
||||
}
|
||||
break;
|
||||
case "submit_test":
|
||||
{
|
||||
conclude_test($testid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// creator or quizmaster actions
|
||||
|
5
js/common.js
Normal file
5
js/common.js
Normal file
@ -0,0 +1,5 @@
|
||||
function unix_time_to_human_readable(tunix) {
|
||||
const date = new Date(Number(tunix) * 1000);
|
||||
return date.getFullYear() + ". " + String(date.getMonth() + 1).padStart(2, "0") + ". " + String(date.getDate()).padStart(2, "0") + ". "
|
||||
+ String(date.getHours()).padStart(2, "0") + ":" + String(date.getMinutes()).padStart(2, "0") + ":" + String(date.getSeconds()).padStart(2, "0");
|
||||
}
|
@ -9,21 +9,103 @@ function list_available_games() {
|
||||
games_by_groups.forEach((game_collection) => {
|
||||
let group_box = document.createElement("section");
|
||||
group_box.classList.add("group-box");
|
||||
let group_box_caption_container = document.createElement("span");
|
||||
group_box_caption_container.classList.add("group-box-caption-container");
|
||||
let group_box_caption = document.createElement("span");
|
||||
group_box_caption.classList.add("group-box-caption");
|
||||
group_box_caption.innerHTML = game_collection["groupname"];
|
||||
group_box_caption_container.append(group_box_caption);
|
||||
let group_box_inner = document.createElement("section");
|
||||
group_box_inner.classList.add("group-box-inner");
|
||||
group_box.append(group_box_caption, group_box_inner);
|
||||
group_box.append(group_box_caption_container, group_box_inner);
|
||||
|
||||
game_collection["games"].forEach((game) => {
|
||||
let game_box = document.createElement("section");
|
||||
game_box.classList.add("game-box");
|
||||
game_box.innerHTML = game["name"];
|
||||
group_box_inner.appendChild(game_box);
|
||||
let game_box = document.createElement("section");
|
||||
game_box.classList.add("game-box");
|
||||
game_box.addEventListener("click", () => {
|
||||
start_or_continue_test(game["_id"]);
|
||||
});
|
||||
|
||||
let game_box_caption = document.createElement("section");
|
||||
game_box_caption.classList.add("game-box-caption");
|
||||
game_box_caption.innerHTML = game["name"];
|
||||
|
||||
let list_results_btn = document.createElement("section");
|
||||
list_results_btn.classList.add("list-results-btn", "material", "btn");
|
||||
list_results_btn.addEventListener("click", (event) => {
|
||||
event.stopPropagation();
|
||||
list_corresponding_results(game["_id"]);
|
||||
});
|
||||
list_results_btn.innerHTML = "";
|
||||
|
||||
game_box.append(game_box_caption, list_results_btn);
|
||||
|
||||
group_box_inner.append(game_box);
|
||||
});
|
||||
|
||||
game_list_panel.appendChild(group_box);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function start_or_continue_test(gameid) {
|
||||
let req = {
|
||||
action: "start_or_continue_test",
|
||||
gameid: gameid
|
||||
}
|
||||
request(req).then(resp => {
|
||||
if (resp.length > 0) // response is non-zero
|
||||
{
|
||||
open_test(resp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function open_test(testid) {
|
||||
window.open("testground.php?testid=" + testid, "_new");
|
||||
}
|
||||
|
||||
function list_corresponding_results(gameid) {
|
||||
let results_overview_display = document.getElementById("results_overview_display");
|
||||
results_overview_display.innerHTML = "";
|
||||
|
||||
let req = {
|
||||
action: "get_results_overview",
|
||||
gameid: gameid
|
||||
};
|
||||
request(req).then(resp => {
|
||||
let results = JSON.parse(resp);
|
||||
let n = results.length;
|
||||
if (n === 0) { // don't continue with processing an empty array
|
||||
return;
|
||||
}
|
||||
results.sort((a,b) => {return Number(b["testid"]) - Number(a["testid"])}); // sort records by ID
|
||||
results.forEach((record) => {
|
||||
let test_summary_record = document.createElement("section");
|
||||
test_summary_record.classList.add("test-summary-record");
|
||||
test_summary_record.addEventListener("click", () => {
|
||||
open_test(record["testid"]);
|
||||
});
|
||||
|
||||
let sequence_number_sec = document.createElement("section");
|
||||
sequence_number_sec.classList.add("summary-sequence-number");
|
||||
sequence_number_sec.innerText = "#" + n;
|
||||
let duration_sec = document.createElement("section");
|
||||
duration_sec.classList.add("summary-duration");
|
||||
let start_time = unix_time_to_human_readable(record["start_time"]);
|
||||
let end_time = unix_time_to_human_readable(record["end_time"]);
|
||||
duration_sec.innerHTML = `${start_time}-<br>${end_time}<br>${record["correct_answer_n"]}/${record["challenge_n"]}`;
|
||||
|
||||
let percentage = document.createElement("section");
|
||||
percentage.classList.add("summary-percentage");
|
||||
let r = Math.floor((record["correct_answer_n"] / record["challenge_n"]) * 100);
|
||||
percentage.innerHTML = `${r}%`;
|
||||
|
||||
test_summary_record.append(sequence_number_sec, duration_sec, percentage);
|
||||
results_overview_display.appendChild(test_summary_record);
|
||||
|
||||
n--;
|
||||
});
|
||||
show("results_window")
|
||||
});
|
||||
}
|
8
js/main.js
Normal file
8
js/main.js
Normal file
@ -0,0 +1,8 @@
|
||||
function logout() {
|
||||
let req = {
|
||||
action: "logout"
|
||||
};
|
||||
request(req).then(resp => {
|
||||
location.href = "login.php";
|
||||
});
|
||||
}
|
176
js/testground.js
176
js/testground.js
@ -1,30 +1,164 @@
|
||||
function populate_test(test_id) {
|
||||
let test_display = document.getElementById("test_display");
|
||||
let TEST_DATA = {}
|
||||
let INTERVAL_HANDLE = null;
|
||||
|
||||
function print_time_left(t) {
|
||||
let timerS = document.getElementById("timer");
|
||||
let hours = Math.floor(t / 3600);
|
||||
t -= hours * 3600;
|
||||
let minutes = Math.floor(t / 60);
|
||||
t -= minutes * 60;
|
||||
let seconds = t;
|
||||
timerS.innerHTML = String(hours).padStart(2, "0") + ":"
|
||||
+ String(minutes).padStart(2, "0") + ":"
|
||||
+ String(seconds).padStart(2, "0");
|
||||
}
|
||||
|
||||
function populate_infobox(test_data) {
|
||||
if (INTERVAL_HANDLE !== null) {
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
}
|
||||
|
||||
let test_concluded = TEST_DATA["state"] === "concluded";
|
||||
|
||||
let game_nameS = document.getElementById("game_name");
|
||||
let durationS = document.getElementById("duration");
|
||||
let percentageS = document.getElementById("percentage");
|
||||
|
||||
game_nameS.innerHTML = test_data["gamename"];
|
||||
|
||||
if (test_concluded) {
|
||||
let summary = test_data["summary"];
|
||||
let correct_answer_n = summary["correct_answer_n"];
|
||||
let challenge_n = summary["challenge_n"];
|
||||
let r = Math.ceil((correct_answer_n / challenge_n) * 100);
|
||||
percentageS.innerHTML = `${r}% (${correct_answer_n}/${challenge_n})`;
|
||||
|
||||
let start_time = unix_time_to_human_readable(test_data["start_time"]);
|
||||
let end_time = unix_time_to_human_readable(test_data["end_time"]);
|
||||
durationS.value = `${start_time} - ${end_time}`;
|
||||
|
||||
hide("ongoing-info");
|
||||
show("concluded-info");
|
||||
} else {
|
||||
if (test_data["time_limited"]) {
|
||||
let time_left_s = Number(test_data["end_limit_time"]) - Number(test_data["current_time"]);
|
||||
print_time_left(time_left_s);
|
||||
INTERVAL_HANDLE = setInterval(() => {
|
||||
time_left_s--;
|
||||
print_time_left(time_left_s);
|
||||
if (time_left_s <= 0) {
|
||||
populate_all(test_data["_id"]);
|
||||
clearInterval(INTERVAL_HANDLE);
|
||||
INTERVAL_HANDLE = null;
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
show("ongoing-info");
|
||||
hide("concluded-info");
|
||||
}
|
||||
}
|
||||
|
||||
function populate_challenges(test_data) {
|
||||
let test_display = document.getElementById("test_display");
|
||||
test_display.innerHTML = "";
|
||||
|
||||
let test_concluded = TEST_DATA["state"] === "concluded";
|
||||
|
||||
let challenge_N = 0;
|
||||
test_data["challenges"].forEach((challenge) => {
|
||||
let challenge_N_snapshot = challenge_N;
|
||||
let challenge_box = document.createElement("section");
|
||||
challenge_box.classList.add("challenge");
|
||||
let question = document.createElement("span");
|
||||
question.classList.add("question");
|
||||
question.innerHTML = preprocess_inserts(challenge["question"]);
|
||||
let answer_container = document.createElement("section");
|
||||
answer_container.classList.add("answer-container");
|
||||
challenge_box.append(question, answer_container);
|
||||
|
||||
let answer_N = 0;
|
||||
let player_answer = challenge["player_answer"];
|
||||
player_answer = (player_answer !== "") ? Number(player_answer) : -1;
|
||||
challenge["answers"].forEach((answer) => {
|
||||
let answer_section = document.createElement("section");
|
||||
answer_section.classList.add("answer");
|
||||
let answer_radio = document.createElement("input");
|
||||
answer_radio.type = "radio";
|
||||
answer_radio.id = `${challenge_N}_${answer_N}`;
|
||||
answer_radio.name = `challenge_${challenge_N}`;
|
||||
answer_radio.disabled = test_concluded;
|
||||
let answer_N_snapshot = answer_N;
|
||||
answer_radio.addEventListener("input", () => {
|
||||
save_answer(challenge_N_snapshot, answer_N_snapshot);
|
||||
});
|
||||
if (player_answer === answer_N) {
|
||||
answer_radio.checked = true;
|
||||
}
|
||||
|
||||
let answer_text = document.createElement("label");
|
||||
answer_text.innerHTML = preprocess_inserts(answer);
|
||||
answer_text.setAttribute("for", answer_radio.id);
|
||||
if (test_concluded && (challenge["correct_answer"] === answer_N)) {
|
||||
answer_text.classList.add("correct-answer")
|
||||
}
|
||||
|
||||
answer_section.append(answer_radio, answer_text);
|
||||
answer_container.appendChild(answer_section);
|
||||
|
||||
answer_N++;
|
||||
});
|
||||
challenge_N++;
|
||||
|
||||
test_display.appendChild(challenge_box);
|
||||
});
|
||||
|
||||
MathJax.typeset();
|
||||
}
|
||||
|
||||
function populate_all(test_id) {
|
||||
let req = {
|
||||
action: "get_test",
|
||||
id: test_id
|
||||
testid: test_id
|
||||
}
|
||||
request(req).then(resp => {
|
||||
let test_data = JSON.parse(resp);
|
||||
test_data["challenges"].forEach((challenge) => {
|
||||
let challenge_box = document.createElement("section");
|
||||
challenge_box.classList.add("challenge");
|
||||
let question = document.createElement("span");
|
||||
question.classList.add("question");
|
||||
question.innerHTML = challenge["question"];
|
||||
let answer_container = document.createElement("section");
|
||||
answer_container.classList.add("answer-container");
|
||||
challenge_box.append(question, answer_container);
|
||||
TEST_DATA = JSON.parse(resp);
|
||||
populate_challenges(TEST_DATA);
|
||||
populate_infobox(TEST_DATA);
|
||||
});
|
||||
}
|
||||
|
||||
challenge["answers"].forEach((answer) => {
|
||||
let answer_section = document.createElement("section");
|
||||
answer_section.classList.add("answer");
|
||||
answer_section.innerHTML = answer;
|
||||
answer_container.appendChild(answer_section);
|
||||
});
|
||||
function save_answer(chidx, aidx) {
|
||||
let req = {
|
||||
action: "save_answer",
|
||||
testid: TEST_DATA["_id"],
|
||||
challenge_index: chidx,
|
||||
answer_index: aidx,
|
||||
};
|
||||
request(req);
|
||||
}
|
||||
|
||||
test_display.appendChild(challenge_box);
|
||||
});
|
||||
function preprocess_inserts(str) {
|
||||
let code_delim = '`';
|
||||
let parts = str.split(code_delim);
|
||||
let res = "";
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
res += parts[i];
|
||||
if (i % 2 === 0) {
|
||||
res += "<code>";
|
||||
} else {
|
||||
res += "</code>";
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
function submit_test() {
|
||||
let req = {
|
||||
action: "submit_test",
|
||||
testid: TEST_DATA["_id"]
|
||||
}
|
||||
request(req).then(resp => {
|
||||
populate_all(TEST_DATA["_id"]);
|
||||
});
|
||||
}
|
9
main.php
9
main.php
@ -22,6 +22,7 @@ $privilege = $user_data["privilege"];
|
||||
<meta charset="UTF-8">
|
||||
<title>SpreadQuiz</title>
|
||||
<script src="js/req.js"></script>
|
||||
<script src="js/main.js"></script>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<link rel="stylesheet" href="style/spreadquiz.css"/>
|
||||
</head>
|
||||
@ -31,7 +32,13 @@ $privilege = $user_data["privilege"];
|
||||
<iframe id="content_frame" src="default_frame.php"></iframe>
|
||||
</section>
|
||||
<section id="info_pane">
|
||||
<section id="user_info" class="info-pane-element"><?= $user_data["nickname"]; ?></section>
|
||||
<section id="user_info" class="info-pane-element">
|
||||
<?= $user_data["nickname"]; ?>
|
||||
<section style="margin-top: 1em">
|
||||
<input type="button" value="Beállítások"><br>
|
||||
<input type="button" value="Kijelentkezés" onclick="logout()">
|
||||
</section>
|
||||
</section>
|
||||
<?php if ($privilege != PRIVILEGE_PLAYER) { ?>
|
||||
<section id="action_panel" class="info-pane-element">
|
||||
<?php if (($privilege === PRIVILEGE_CREATOR) || ($privilege === PRIVILEGE_QUIZMASTER)) { ?>
|
||||
|
45
style/quizmaster_area.css
Normal file
45
style/quizmaster_area.css
Normal file
@ -0,0 +1,45 @@
|
||||
section#table_section {
|
||||
position: sticky;
|
||||
height: calc(100vh - 4em);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
table.management {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.management thead th {
|
||||
padding: 0.5em 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: darkgray;
|
||||
}
|
||||
|
||||
table.management tbody td {
|
||||
border: 1pt lightgrey solid;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.management tbody tr[highlight="true"] {
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
table.management tbody tr[highlight="false"] td input {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
section#user_manager_action_bar {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
td.checkbox {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.hintbox-window {
|
||||
position: fixed;
|
||||
display: block;
|
||||
width: 15em;
|
||||
height: 8em;
|
||||
}
|
@ -1,3 +1,10 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Autour+One&family=Kanit:wght@500&display=swap');
|
||||
@import url("https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0");
|
||||
|
||||
body {
|
||||
font-family: 'Autour One', sans-serif;
|
||||
}
|
||||
|
||||
*[shown="false"] {
|
||||
visibility: hidden;
|
||||
display: none;
|
||||
@ -10,6 +17,10 @@
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.material {
|
||||
font-family: 'Material Symbols Outlined', sans-serif;
|
||||
}
|
||||
|
||||
/* ----------------- */
|
||||
|
||||
section#screen_panel {
|
||||
@ -31,13 +42,15 @@ section#content_pane, section#info_pane {
|
||||
section#content_pane {
|
||||
left: 0;
|
||||
width: 80vw;
|
||||
background-color: lightcyan;
|
||||
/*background-color: lightcyan;*/
|
||||
}
|
||||
|
||||
section#info_pane {
|
||||
right: 0;
|
||||
width: 20vw;
|
||||
background-color: beige;
|
||||
background-color: whitesmoke;
|
||||
border-left: 5px solid #176767;
|
||||
box-shadow: -5px 0 #d3e5e5;
|
||||
}
|
||||
|
||||
.info-pane-element {
|
||||
@ -50,9 +63,19 @@ section#info_pane {
|
||||
section#user_info {
|
||||
position: relative;
|
||||
top: 0;
|
||||
background-color: aquamarine;
|
||||
background-color: #176767;
|
||||
color: whitesmoke;
|
||||
font-size: 18pt;
|
||||
text-align: center;
|
||||
height: 1.2em;
|
||||
overflow: clip;
|
||||
transition: 0.3s ease;
|
||||
border-bottom: 0 dashed whitesmoke;
|
||||
}
|
||||
|
||||
section#user_info:hover {
|
||||
height: 5em;
|
||||
border-width: 0.5em;
|
||||
}
|
||||
|
||||
section#action_panel {
|
||||
@ -61,46 +84,11 @@ section#action_panel {
|
||||
|
||||
iframe#content_frame {
|
||||
display: block;
|
||||
width: 100%;
|
||||
width: calc(100% - 1em);
|
||||
height: 100%;
|
||||
border: 0 transparent;
|
||||
}
|
||||
|
||||
section#table_section {
|
||||
position: sticky;
|
||||
height: calc(100vh - 4em);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
table.management {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
table.management thead th {
|
||||
padding: 0.5em 0;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: darkgray;
|
||||
}
|
||||
|
||||
table.management tbody td {
|
||||
border: 1pt lightgrey solid;
|
||||
padding: 0.3em 0.5em;
|
||||
}
|
||||
|
||||
table.management tbody tr[highlight="true"] {
|
||||
background-color: antiquewhite;
|
||||
}
|
||||
|
||||
table.management tbody tr[highlight="false"] td input {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
section#user_manager_action_bar {
|
||||
margin-top: 0.5em;
|
||||
}
|
||||
|
||||
section.window {
|
||||
position: fixed;
|
||||
display: block;
|
||||
@ -115,23 +103,12 @@ section.window {
|
||||
section.window-inner {
|
||||
margin: 0 auto;
|
||||
display: block;
|
||||
border: 2pt solid darkslategray;
|
||||
border: 2pt solid #176767;
|
||||
background-color: whitesmoke;
|
||||
padding: 1em;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
td.checkbox {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.hintbox-window {
|
||||
position: fixed;
|
||||
display: block;
|
||||
width: 15em;
|
||||
height: 8em;
|
||||
}
|
||||
|
||||
/* ----- */
|
||||
|
||||
section#game_list_panel {
|
||||
@ -145,15 +122,30 @@ section.group-box {
|
||||
margin: 1em;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: beige;
|
||||
background-color: #f8fbfb;
|
||||
border: 0.5px solid #176767;
|
||||
/*border-radius: 0 0 0.2em 0.2em;*/
|
||||
box-shadow: 5px 5px #d3e5e5;
|
||||
}
|
||||
|
||||
span.group-box-caption {
|
||||
span.group-box-caption-container {
|
||||
display: block;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background-color: #176767;
|
||||
box-shadow: 0 5px #d3e5e5;
|
||||
}
|
||||
|
||||
span.group-box-caption {
|
||||
display: inline-block;
|
||||
/*position: relative;*/
|
||||
/*top: -0.5em;*/
|
||||
color: #176767;
|
||||
background-color: #d3e5e5;
|
||||
font-weight: bold;
|
||||
font-size: 1.2em;
|
||||
padding: 1em;
|
||||
background-color: chartreuse;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
section.group-box-inner {
|
||||
@ -164,13 +156,45 @@ section.group-box-inner {
|
||||
}
|
||||
|
||||
section.game-box {
|
||||
display: table-cell;
|
||||
width: 8em;
|
||||
height: 8em;
|
||||
border: 2pt dashed gray;
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 12em;
|
||||
height: 12em;
|
||||
border: 2pt solid #176767;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
cursor: pointer;
|
||||
margin-right: 1em;
|
||||
overflow: clip;
|
||||
box-shadow: 5px 5px #d3e5e5;
|
||||
transform: scale(100%) rotate(0);
|
||||
background-color: whitesmoke;
|
||||
transition: ease 0.3s;
|
||||
}
|
||||
|
||||
section.game-box:hover {
|
||||
transform: scale(105%) rotate(3deg);
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
section.game-box-caption {
|
||||
margin-top: 2em;
|
||||
font-size: 1.3em;
|
||||
rotate: -10deg;
|
||||
background-color: #176767;
|
||||
width: 13em;
|
||||
position: relative;
|
||||
left: -3em;
|
||||
padding: 0.6em;
|
||||
color: whitesmoke;
|
||||
box-shadow: 0 5px #d3e5e5;
|
||||
}
|
||||
|
||||
section.list-results-btn {
|
||||
display: block;
|
||||
position: absolute;
|
||||
right: 0.2em;
|
||||
bottom: 0.2em;
|
||||
}
|
||||
|
||||
section#test_area {
|
||||
@ -181,11 +205,20 @@ section#test_area {
|
||||
}
|
||||
|
||||
section.challenge {
|
||||
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
width: 40em;
|
||||
/*border: 1px solid black;*/
|
||||
padding: 1em;
|
||||
background-color: #d3e5e5;
|
||||
margin-bottom: 0.5em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
span.question {
|
||||
|
||||
font-size: 1.3em;
|
||||
font-weight: bold;
|
||||
color: #176767;
|
||||
}
|
||||
|
||||
section.answer-container {
|
||||
@ -193,5 +226,112 @@ section.answer-container {
|
||||
}
|
||||
|
||||
section.answer {
|
||||
margin: 0.3em 0.8em;
|
||||
display: block;
|
||||
}
|
||||
|
||||
section.answer label {
|
||||
margin-left: 0.5em;
|
||||
padding: 0.3em 0.5em;
|
||||
border-radius: 0.3em;
|
||||
}
|
||||
|
||||
section.answer label.correct-answer {
|
||||
border: 2px solid #176767 !important;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
section.answer input[type="radio"]:checked+label:not(.correct-answer) {
|
||||
background-color: #176767;
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
section#infobox {
|
||||
display: block;
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
margin: 1em;
|
||||
/*padding: 1em;*/
|
||||
background: #f8fbfb;
|
||||
border: 0.5px solid #176767;
|
||||
min-width: 15em;
|
||||
min-height: 5em;
|
||||
box-shadow: 5px 5px #d3e5e5;
|
||||
overflow: clip;
|
||||
}
|
||||
|
||||
section#infobox > section {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
span#game_name {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 1em;
|
||||
background-color: #176767;
|
||||
box-shadow: 0px 5px #d3e5e5;
|
||||
color: whitesmoke;
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
section#timer {
|
||||
color: #176767;
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
section#percentage {
|
||||
color: #176767;
|
||||
}
|
||||
|
||||
span.infobox-description {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background-color: transparent;
|
||||
padding: 0.3em;
|
||||
border-radius: 0.2em;
|
||||
font-size: 1.3em;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: rgba(128, 128, 128, 0.30);
|
||||
}
|
||||
|
||||
section.test-summary-record {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
border: 0.5px solid #176767;
|
||||
margin-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.summary-sequence-number {
|
||||
font-size: 2em;
|
||||
display: inline-block;
|
||||
background-color: #176767;
|
||||
color: whitesmoke;
|
||||
padding: 0.5em;
|
||||
width: 1.5em;
|
||||
box-shadow: 5px 0 #d3e5e5;
|
||||
}
|
||||
|
||||
.summary-duration {
|
||||
display: inline-block;
|
||||
margin-left: 1em;
|
||||
color: #176767;
|
||||
font-size: 0.7em;
|
||||
}
|
||||
|
||||
.summary-percentage {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
margin-left: 1em;
|
||||
background-color: #176767;
|
||||
padding: 0 1em;
|
||||
box-shadow: -5px 0 #d3e5e5;
|
||||
color: whitesmoke;
|
||||
bottom: 0;
|
||||
}
|
@ -8,7 +8,10 @@ if (!get_autologin_state() || !isset($_REQUEST["testid"])) {
|
||||
exit();
|
||||
}
|
||||
|
||||
$testid = $_REQUEST["testid"];
|
||||
$testid = trim($_REQUEST["testid"] ?: "");
|
||||
if ($testid === "") {
|
||||
exit();
|
||||
}
|
||||
|
||||
?>
|
||||
|
||||
@ -17,15 +20,35 @@ $testid = $_REQUEST["testid"];
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>SpreadQuiz</title>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<script src="js/req.js"></script>
|
||||
<script src="js/spreadquiz.js"></script>
|
||||
<script src="js/o.js"></script>
|
||||
<script src="js/common.js"></script>
|
||||
<script src="js/testground.js"></script>
|
||||
<link rel="stylesheet" href="style/spreadquiz.css">
|
||||
<script id="MathJax-script" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<section id="test_display">
|
||||
<section id="test_display">
|
||||
|
||||
</section>
|
||||
<section id="infobox">
|
||||
<span id="game_name"></span>
|
||||
<section>
|
||||
<section id="ongoing-info">
|
||||
<span class="infobox-description">Hátralevő idő:</span>
|
||||
<section id="timer">10:00:00</section>
|
||||
<input type="button" value="Beküld" onclick="submit_test()">
|
||||
</section>
|
||||
<section id="concluded-info">
|
||||
<section id="duration"></section>
|
||||
<span class="infobox-description">Eredmény:</span>
|
||||
<section id="percentage">95% (19/20)</section>
|
||||
</section>
|
||||
</section>
|
||||
</section>
|
||||
<script>
|
||||
populate_all("<?=$testid ?>");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
103
testmgr.php
103
testmgr.php
@ -29,7 +29,7 @@ function create_or_continue_test(string $gameid, string $nickname): string
|
||||
}
|
||||
|
||||
// check if the user had taken this test before
|
||||
$fetch_criteria = [["gameid", "=", $gameid], "AND", ["nickname", "=", $nickname]];
|
||||
$fetch_criteria = [["gameid", "=", (int)$gameid], "AND", ["nickname", "=", $nickname]];
|
||||
$previous_tests = $testdb->findBy($fetch_criteria);
|
||||
if (count($previous_tests) > 0) { // if there are previous attempts, then...
|
||||
// update timed tests to see if they had expired
|
||||
@ -53,12 +53,15 @@ function create_or_continue_test(string $gameid, string $nickname): string
|
||||
|
||||
function create_test(array $game_data, array $user_data) : string
|
||||
{
|
||||
global $testdb;
|
||||
$gameid = $game_data["_id"];
|
||||
$game_properties = $game_data["properties"];
|
||||
|
||||
// fill basic data
|
||||
$game_data = [
|
||||
$test_data = [
|
||||
"gameid" => $gameid,
|
||||
"nickname" => $user_data["nickname"],
|
||||
"gamename" => $game_data["name"]
|
||||
];
|
||||
|
||||
// fill challenges
|
||||
@ -67,11 +70,33 @@ function create_test(array $game_data, array $user_data) : string
|
||||
// shuffle answers
|
||||
foreach ($challenges as &$ch) {
|
||||
shuffle($ch["answers"]);
|
||||
$ch["correct_answer"] = "";
|
||||
$ch["player_answer"] = "";
|
||||
}
|
||||
|
||||
$game_data["challenges"] = $challenges;
|
||||
|
||||
// involve properties
|
||||
$now = time();
|
||||
$properties = [
|
||||
"state" => TEST_ONGOING,
|
||||
"time_limited" => (($game_properties["time_limit"] ?: -1) > -1),
|
||||
"start_time" => $now,
|
||||
"repeatable" => $game_properties["repeatable"] ?: false
|
||||
];
|
||||
if ($properties["time_limited"]) {
|
||||
$properties["end_limit_time"] = $now + $game_properties["time_limit"];
|
||||
}
|
||||
|
||||
// merge properties and test data
|
||||
$test_data = array_merge($test_data, $properties);
|
||||
|
||||
// add challenges
|
||||
$test_data["challenges"] = $challenges;
|
||||
|
||||
// store game
|
||||
$test_data = $testdb->insert($test_data);
|
||||
|
||||
$testid = $test_data["_id"];
|
||||
return $testid;
|
||||
}
|
||||
|
||||
function update_timed_tests(array $test_data_array)
|
||||
@ -79,9 +104,8 @@ function update_timed_tests(array $test_data_array)
|
||||
$now = time();
|
||||
foreach ($test_data_array as $test_data) {
|
||||
// look for unprocessed expired tests
|
||||
if (($test_data["state"] === TEST_ONGOING) && ($test_data["end_limit_time"] < $now)) {
|
||||
$test_data["state"] = TEST_CONCLUDED;
|
||||
update_test($test_data);
|
||||
if (($test_data["state"] === TEST_ONGOING) && ($test_data["time_limited"]) && ($test_data["end_limit_time"] < $now)) {
|
||||
conclude_test($test_data["_id"]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -90,4 +114,69 @@ function update_test(array $test_data)
|
||||
{
|
||||
global $testdb;
|
||||
$testdb->update($test_data);
|
||||
}
|
||||
|
||||
function get_test(string $testid): array
|
||||
{
|
||||
global $testdb;
|
||||
return $testdb->findById($testid);
|
||||
}
|
||||
|
||||
function save_answer(string $testid, $chidx, $ansidx)
|
||||
{
|
||||
$test_data = get_test($testid);
|
||||
$chidx = (int)$chidx;
|
||||
if ((count($test_data) > 0) && ($test_data["state"] === TEST_ONGOING)) {
|
||||
if ($chidx < count($test_data["challenges"])) {
|
||||
$test_data["challenges"][$chidx]["player_answer"] = $ansidx;
|
||||
update_test($test_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get_concluded_tests(string $gameid, string $nickname) {
|
||||
global $testdb;
|
||||
$fetch_criteria = [["gameid", "=", (int)$gameid], "AND", ["nickname", "=", $nickname], "AND", ["state", "=", TEST_CONCLUDED]];
|
||||
$test_data_array = $testdb->findBy($fetch_criteria);
|
||||
return $test_data_array;
|
||||
}
|
||||
|
||||
function conclude_test(string $testid) {
|
||||
$test_data = get_test($testid);
|
||||
if (count($test_data) === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// load game data
|
||||
//$game_data = get_game($test_data["gameid"]);
|
||||
$game_challenges = load_challenges($test_data["gameid"]);
|
||||
|
||||
// check the answers
|
||||
$challenge_n = count($test_data["challenges"]); // number of challenges
|
||||
$cans_n = 0; // number of correct answers
|
||||
for ($chidx = 0; $chidx < $challenge_n; $chidx++) {
|
||||
// get challenge
|
||||
$tch = &$test_data["challenges"][$chidx];
|
||||
$gch = $game_challenges[$chidx];
|
||||
|
||||
// translate correct answer into an index by the shuffled answer order
|
||||
$cans_idx = array_search($gch["correct_answer"], $tch["answers"]);
|
||||
$tch["correct_answer"] = $cans_idx;
|
||||
|
||||
// check the player's answer
|
||||
$player_answer = trim($tch["player_answer"]);
|
||||
if (($player_answer !== "") && ($cans_idx === (int)$player_answer)) {
|
||||
$cans_n++;
|
||||
}
|
||||
}
|
||||
|
||||
// set state and fill summary
|
||||
$test_data["state"] = TEST_CONCLUDED;
|
||||
$test_data["end_time"] = time();
|
||||
$test_data["summary"] = [
|
||||
"challenge_n" => $challenge_n,
|
||||
"correct_answer_n" => $cans_n
|
||||
];
|
||||
|
||||
update_test($test_data);
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user