- Editions replaced by INSTEG, HAREC and UPPGRADERING (Instegscertifikat -> HAREC) - Question pool optional; simulator, homework tool and solutions switch off without one - All UI strings, templates, renderers and project JS localised to sv-SE (decimal comma, Swedish non-breaking-space rules, Å/Ä/Ö in Morse table) - DARC branding, membership CTA and German links removed; attribution footer added - Page names centralised in src/pages.py - README and docs rewritten in Swedish Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
293 lines
18 KiB
HTML
293 lines
18 KiB
HTML
<div id="exam-simulator" class="exam-simulator" v-cloak>
|
||
<div v-if="loading" class="d-flex align-items-center justify-content-center gap-3 py-5" role="status">
|
||
<div class="spinner-border text-primary" aria-hidden="true"></div>
|
||
<span>Provfrågor laddas …</span>
|
||
</div>
|
||
|
||
<div v-else-if="loadError" class="alert alert-danger" role="alert">
|
||
<h1 class="h4">Provsimuleringen kunde inte laddas</h1>
|
||
<p class="mb-0" v-text="loadError"></p>
|
||
</div>
|
||
|
||
<section v-else-if="view === 'shared' && sharedResult" class="mx-auto mb-5 exam-summary" aria-labelledby="shared-heading">
|
||
<h1 id="shared-heading">Delat resultat</h1>
|
||
<template v-if="sharedResult.kind === 'homework'">
|
||
<p class="lead">Övning på 50Ω</p>
|
||
<div class="alert alert-primary">
|
||
<strong><span v-text="sharedResult.score"></span> av <span v-text="sharedResult.total"></span> frågor rätt besvarade</strong>
|
||
</div>
|
||
</template>
|
||
<template v-else>
|
||
<p class="lead" v-text="sharedResult.choice.label"></p>
|
||
<div class="alert" :class="sharedResult.passed ? 'alert-success' : 'alert-danger'">
|
||
<strong v-text="sharedResult.passed ? 'Provet godkänt' : 'Provet inte godkänt'"></strong>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table align-middle">
|
||
<thead><tr><th>Del</th><th>Resultat</th><th>Status</th></tr></thead>
|
||
<tbody>
|
||
<tr v-for="(part, index) in sharedResult.choice.parts" :key="part">
|
||
<td><b v-text="part + ' – ' + partDefinitions[part].label"></b></td>
|
||
<td><b v-text="sharedResult.scores[index]"></b>/25</td>
|
||
<td><span class="badge" :class="sharedResult.scores[index] >= 19 ? 'text-bg-success' : 'text-bg-danger'"
|
||
v-text="sharedResult.scores[index] >= 19 ? 'Godkänd' : 'Inte godkänd'"></span></td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</template>
|
||
<img v-if="resultImageUrl" class="img-fluid border mb-4" :src="resultImageUrl"
|
||
alt="Provsimulering">
|
||
<div v-if="resultForSharing" class="border-top pt-3 mt-3">
|
||
<h2 class="h5">Dela resultatet</h2>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<button type="button" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center" @click="shareNative">
|
||
<span class="material-icons fs-6 me-1" aria-hidden="true">share</span> Dela …
|
||
</button>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" @click="copyResultLink">Kopiera länk</button>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.mastodon" target="_blank" rel="noopener noreferrer">Mastodon</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.bluesky" target="_blank" rel="noopener noreferrer">Bluesky</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.facebook" target="_blank" rel="noopener noreferrer">Facebook</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.x" target="_blank" rel="noopener noreferrer">X (tidigare Twitter)</a>
|
||
</div>
|
||
<small v-if="shareMessage" class="d-block mt-2" role="status" v-text="shareMessage"></small>
|
||
</div>
|
||
<button type="button" class="btn btn-primary mt-4" @click="newExam">Starta en egen simulering</button>
|
||
</section>
|
||
|
||
<section v-else-if="!session" class="mx-auto mb-5 exam-start" aria-labelledby="exam-heading">
|
||
<h1 id="exam-heading">Provsimulering <sup class="exam-beta fs-6">BETA</sup></h1>
|
||
<p class="lead">Vilket prov vill du simulera?</p>
|
||
|
||
<h2 class="h4 mt-5">Första provet</h2>
|
||
<p class="mb-2">Har du inte gjort något amatörradioprov än? Då är det här rätt simulering för dig.</p>
|
||
<div class="row row-cols-1 row-cols-md-3 g-2">
|
||
<div v-for="choice in primaryChoices" :key="choice.id" class="col">
|
||
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
||
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
||
<span class="d-block" v-text="choice.label"></span>
|
||
<small class="d-block" v-text="choice.detail"></small>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="h4 mt-4">Uppgraderingsprov</h2>
|
||
<p class="mb-2">Har du redan ett certifikat och vill uppgradera? Välj då lämpligt prov här.</p>
|
||
<div class="row row-cols-1 row-cols-md-3 g-2">
|
||
<div v-for="choice in upgradeChoices" :key="choice.id" class="col">
|
||
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
||
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
||
<span class="d-block" v-text="choice.label"></span>
|
||
<small class="d-block" v-text="choice.detail"></small>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 class="h4 mt-4">Enskilda provdelar</h2>
|
||
<p class="mb-2">Öva på en enskild provdel under provliknande förhållanden.</p>
|
||
<div class="row row-cols-1 row-cols-sm-2 row-cols-lg-5 g-2">
|
||
<div v-for="choice in singlePartChoices" :key="choice.id" class="col">
|
||
<button type="button" class="btn w-100 h-100 py-3 text-white"
|
||
:style="{ backgroundColor: choice.color, borderColor: choice.color }" @click="startExam(choice)">
|
||
<span class="d-block" v-text="choice.label"></span>
|
||
<small class="d-block" v-text="choice.detail"></small>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="alert alert-primary mt-4 mb-0">
|
||
Varje provdel innehåller 25 frågor. För delarna B, V, N och E finns 45 minuter vardera,
|
||
för teknik A 60 minuter. En del är godkänd från 19 poäng.
|
||
</div>
|
||
</section>
|
||
|
||
<section v-else-if="view === 'summary'" class="mx-auto mb-5 exam-summary" aria-labelledby="summary-heading">
|
||
<h1 id="summary-heading" v-text="isHomework ? 'Resultat av övningen' : 'Totalresultat'"></h1>
|
||
<p class="lead" v-text="currentChoice.label"></p>
|
||
<div class="alert d-flex align-items-center gap-2" :class="overallAlertClass">
|
||
<span class="material-icons" aria-hidden="true" v-text="overallIcon"></span>
|
||
<div>
|
||
<strong v-text="overallTitle"></strong>
|
||
<div v-text="overallText"></div>
|
||
<p v-if="overallState === 'failed'" class="mt-2 mb-0">
|
||
Be om hjälp av erfarna kursledare, till exempel på en kurs eller hos en lokal klubb i din närhet.
|
||
Alla möjligheter hittar du på <a href="index.html">startsidan</a>.
|
||
</p>
|
||
<template v-else-if="overallState === 'passed'">
|
||
<p class="mt-2 mb-0">
|
||
Du har redan lärt dig mycket. När du känner dig redo kan du anmäla dig till provet.
|
||
All information hittar du under <a href="prov.html">Prov</a>.
|
||
</p>
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table align-middle">
|
||
<thead><tr><th>Del</th><th>Resultat</th><th>Status</th><th></th></tr></thead>
|
||
<tbody>
|
||
<tr v-for="(part, index) in session.parts" :key="part.code">
|
||
<td><b v-text="part.code + ' – ' + partDefinitions[part.code].label"></b></td>
|
||
<template v-if="part.status === 'evaluated'">
|
||
<td><b v-text="part.score"></b>/<span v-text="part.questions.length"></span></td>
|
||
<td><span class="badge" :class="resultBadgeClass(part)" v-text="resultLabel(part)"></span></td>
|
||
<td><button type="button" class="btn btn-outline-dark btn-sm" @click="reviewPart(index)">Genomgång</button></td>
|
||
</template>
|
||
<template v-else>
|
||
<td colspan="3" class="text-secondary">Inte besvarad</td>
|
||
</template>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<img v-if="resultImageUrl" class="img-fluid border mb-4" :src="resultImageUrl"
|
||
alt="Provsimulering">
|
||
<div v-if="resultForSharing" class="border-top pt-3 mt-3">
|
||
<h2 class="h5">Dela resultatet</h2>
|
||
<div class="d-flex flex-wrap gap-2">
|
||
<button type="button" class="btn btn-outline-secondary btn-sm d-inline-flex align-items-center" @click="shareNative">
|
||
<span class="material-icons fs-6 me-1" aria-hidden="true">share</span> Dela …
|
||
</button>
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" @click="copyResultLink">Kopiera länk</button>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.mastodon" target="_blank" rel="noopener noreferrer">Mastodon</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.bluesky" target="_blank" rel="noopener noreferrer">Bluesky</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.facebook" target="_blank" rel="noopener noreferrer">Facebook</a>
|
||
<a class="btn btn-outline-secondary btn-sm" :href="socialShareLinks.x" target="_blank" rel="noopener noreferrer">X (tidigare Twitter)</a>
|
||
</div>
|
||
<small v-if="shareMessage" class="d-block mt-2" role="status" v-text="shareMessage"></small>
|
||
</div>
|
||
<div class="d-flex flex-wrap gap-2 mt-4">
|
||
<button v-if="isHomework" type="button" class="btn btn-primary" @click="retryHomework">Gör övningen igen</button>
|
||
<button type="button" class="btn btn-outline-secondary" @click="newExam">Ny simulering</button>
|
||
</div>
|
||
</section>
|
||
|
||
<section v-else class="exam-session" aria-labelledby="part-heading">
|
||
<header class="exam-toolbar bg-white border-bottom py-2 mb-3">
|
||
<div>
|
||
<small class="text-secondary"
|
||
v-text="isHomework ? currentQuestionCount + ' valda frågor' : currentChoice.label + ' · Del ' + (displayedPartIndex + 1) + ' av ' + session.parts.length"></small>
|
||
<h1 id="part-heading" class="h4 mb-0" v-text="currentPartDefinition.label"></h1>
|
||
</div>
|
||
<div class="d-flex align-items-center flex-wrap gap-2">
|
||
<button v-if="currentPart.status === 'running' && !isHomework" type="button"
|
||
class="btn btn-sm d-inline-flex align-items-center"
|
||
:class="currentPart.paused ? 'btn-warning' : (remainingSeconds <= 300 ? 'btn-danger' : 'btn-secondary')"
|
||
@click="toggleTimer"
|
||
:title="currentPart.paused ? 'Fortsätt timern' : 'Pausa timern'"
|
||
:aria-label="(currentPart.paused ? 'Fortsätt timern, återstående tid ' : 'Pausa timern, återstående tid ') + formattedTime">
|
||
<span class="material-icons fs-6 me-1" aria-hidden="true"
|
||
v-text="currentPart.paused ? 'play_arrow' : 'pause'"></span>
|
||
<span role="timer" v-text="formattedTime"></span>
|
||
</button>
|
||
<span v-else-if="currentPart.status !== 'running'" class="btn btn-secondary btn-sm d-inline-flex align-items-center">Inlämnad</span>
|
||
<button v-if="currentPart.status === 'evaluated' && !isReviewing && hasNextPart"
|
||
type="button" class="btn btn-primary btn-sm" @click="startNextPart">Starta nästa del</button>
|
||
<a v-else-if="currentPart.status === 'evaluated' && !isReviewing"
|
||
class="btn btn-primary btn-sm" :href="completedResultUrl"
|
||
v-text="isHomework ? 'Resultat' : 'Totalresultat'"></a>
|
||
<button v-else-if="isReviewing" type="button" class="btn btn-primary btn-sm" @click="showSummary">Totalresultat</button>
|
||
<button type="button" class="btn btn-secondary btn-sm d-inline-flex align-items-center" @click="abortExam"
|
||
v-text="isHomework ? 'Avbryt övningen' : 'Avbryt simuleringen'"></button>
|
||
</div>
|
||
</header>
|
||
|
||
<div v-if="currentPart.status === 'evaluated'" class="alert d-flex align-items-center gap-2"
|
||
:class="resultAlertClass(currentPart)">
|
||
<span class="material-icons" aria-hidden="true" v-text="resultIcon(currentPart)"></span>
|
||
<div>
|
||
<strong><span v-text="currentPart.score"></span> av <span v-text="currentQuestionCount"></span> poäng – <span v-text="resultLabel(currentPart)"></span></strong>
|
||
<div v-text="resultDescription(currentPart)"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="exam-paper-layout"
|
||
:key="'exam-part-' + displayedPartIndex + '-' + currentPart.code + '-' + currentPart.status">
|
||
<main class="exam-questions course">
|
||
<template v-for="(questionState, questionIndex) in currentPart.questions" :key="questionState.id">
|
||
<article class="question exam-question" :class="'layout-' + questionData(questionState).layout"
|
||
:id="'exam-question-' + questionIndex">
|
||
<div class="question-text">
|
||
<span v-text="displayQuestionNumber(questionState, questionIndex) + ': '"></span><span v-html="questionData(questionState).question"></span>
|
||
</div>
|
||
<div v-if="questionData(questionState).picture" class="question-picture">
|
||
<img :src="'pictures/' + questionData(questionState).picture + '.svg'"
|
||
:alt="pictureAlt(questionData(questionState).picture, 'Bild till fråga ' + questionState.id)">
|
||
</div>
|
||
<div class="answers">
|
||
<button v-for="(sourceIndex, answerIndex) in questionState.order" :key="answerIndex" type="button"
|
||
:disabled="currentPart.status !== 'running' || currentPart.paused"
|
||
:class="questionAnswerClass(questionIndex, answerIndex)"
|
||
@click="selectAnswer(questionIndex, answerIndex)">
|
||
<span v-if="questionData(questionState).answers[sourceIndex].text"
|
||
v-html="questionData(questionState).answers[sourceIndex].text"></span>
|
||
<span v-if="questionData(questionState).answers[sourceIndex].picture" class="question-picture">
|
||
<img :src="'pictures/' + questionData(questionState).answers[sourceIndex].picture + '.svg'"
|
||
:alt="pictureAlt(questionData(questionState).answers[sourceIndex].picture,
|
||
'Bild till svar ' + answerLabels[answerIndex] + ' på fråga ' + questionState.id)">
|
||
</span>
|
||
<span v-if="currentPart.status === 'evaluated' && sourceIndex === 0" class="result material-icons">check</span>
|
||
<span v-else-if="currentPart.status === 'evaluated' && questionState.selected === answerIndex" class="result material-icons">clear</span>
|
||
</button>
|
||
</div>
|
||
</article>
|
||
<div v-if="currentPart.status === 'evaluated' && questionData(questionState).hasSolution" class="solution">
|
||
<a class="btn btn-warning btn-sm d-inline-flex align-items-center" :href="questionState.id + '.html'" target="_blank"
|
||
:aria-label="'Lösningstips till fråga ' + questionState.id">
|
||
<span class="material-icons fs-6 me-1" aria-hidden="true">lightbulb</span> Visa lösningsvägen
|
||
</a>
|
||
</div>
|
||
</template>
|
||
|
||
<div class="border-top pt-3 mb-5 d-flex flex-wrap gap-2 align-items-center justify-content-end">
|
||
<template v-if="currentPart.status === 'running'">
|
||
<span class="me-auto"><b v-text="answeredCount"></b> av <span v-text="currentQuestionCount"></span> frågor besvarade.</span>
|
||
<button type="button" class="btn btn-primary" @click="requestSubmit" v-text="isHomework ? 'Lämna in övningen' : 'Lämna in provdelen'"></button>
|
||
</template>
|
||
<template v-else-if="isReviewing">
|
||
<button type="button" class="btn btn-primary" @click="showSummary">Tillbaka till totalresultatet</button>
|
||
</template>
|
||
<template v-else-if="hasNextPart">
|
||
<button type="button" class="btn btn-primary" @click="startNextPart">Starta nästa provdel</button>
|
||
<button type="button" class="btn btn-outline-secondary" @click="finishExamEarly">Avsluta simuleringen här</button>
|
||
</template>
|
||
<template v-else>
|
||
<a class="btn btn-primary" :href="completedResultUrl"
|
||
v-text="isHomework ? 'Visa resultatet' : 'Visa totalresultatet'"></a>
|
||
</template>
|
||
</div>
|
||
</main>
|
||
|
||
<aside class="exam-answer-sheet" :class="{ 'exam-answer-sheet-homework': isHomework }" aria-label="Svarsblad">
|
||
<div class="border border-dark bg-white p-2">
|
||
<h2 class="h5 text-center">Svarsblad</h2>
|
||
<div class="border-top border-bottom border-dark py-1 mb-2">
|
||
<strong class="d-block" v-text="currentPartDefinition.label"></strong>
|
||
<small v-if="isHomework"><span v-text="currentQuestionCount"></span> frågor · utan tidsgräns</small>
|
||
<small v-else><span v-text="currentPartDefinition.minutes"></span> minuter · 25 frågor</small>
|
||
</div>
|
||
<table class="answer-table">
|
||
<thead><tr><th>Nr</th><th v-for="label in answerLabels" :key="label" v-text="label"></th></tr></thead>
|
||
<tbody>
|
||
<tr v-for="(question, questionIndex) in currentPart.questions" :key="question.id">
|
||
<th scope="row">
|
||
<button type="button" class="answer-number" @click="scrollToQuestion(questionIndex)"
|
||
:aria-label="'Till fråga ' + displayQuestionNumber(question, questionIndex)" v-text="displayQuestionNumber(question, questionIndex)"></button>
|
||
</th>
|
||
<td v-for="(_, answerIndex) in answerLabels" :key="answerIndex"
|
||
:class="answerCellClass(questionIndex, answerIndex)">
|
||
<button type="button" class="answer-mark"
|
||
:disabled="currentPart.status !== 'running' || currentPart.paused"
|
||
@click="selectAnswer(questionIndex, answerIndex)"
|
||
:aria-label="'Fråga ' + displayQuestionNumber(question, questionIndex) + ', svar ' + answerLabels[answerIndex]"
|
||
v-html="answerMark(questionIndex, answerIndex)"></button>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<small class="d-block text-end mt-1"><b v-text="answeredCount"></b> av <span v-text="currentQuestionCount"></span> besvarade</small>
|
||
</div>
|
||
</aside>
|
||
</div>
|
||
</section>
|
||
</div>
|