Adding some new features

This commit is contained in:
Fabian Stamm 2020-01-20 18:03:21 +01:00
parent 2accd04546
commit 94b27f9ee4
11 changed files with 270 additions and 42 deletions

8
ccna4-missing Normal file
View File

@ -0,0 +1,8 @@
'21 | Next is not UL but: undefined',
'52 | No correct answers found!',
'98 | Next is not UL but: undefined',
'99 | Next is not UL but: undefined',
'100 | Next is not UL but: undefined',
'143 | Next is not UL but: undefined',
'144 | Next is not UL but: undefined',
'145 | No correct answers found!'

11
package-lock.json generated
View File

@ -1038,6 +1038,12 @@
"physical-cpu-count": "^2.0.0" "physical-cpu-count": "^2.0.0"
} }
}, },
"@types/elasticlunr": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@types/elasticlunr/-/elasticlunr-0.9.0.tgz",
"integrity": "sha512-7xUGaa0HqDmfawyFd8ANaoTHt5KF/BEdvKfqz4NxGEhbloIXGGtH065MOvC+YYQRZPAA/b5Vt/Xe5AmXoKTmhQ==",
"dev": true
},
"@types/navigo": { "@types/navigo": {
"version": "7.0.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/@types/navigo/-/navigo-7.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/navigo/-/navigo-7.0.1.tgz",
@ -2732,6 +2738,11 @@
"integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=",
"dev": true "dev": true
}, },
"elasticlunr": {
"version": "0.9.5",
"resolved": "https://registry.npmjs.org/elasticlunr/-/elasticlunr-0.9.5.tgz",
"integrity": "sha1-ZVQbswnd3Qz5Ty0ciGGyvmUbsNU="
},
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.3.314", "version": "1.3.314",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.314.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.314.tgz",

View File

@ -11,6 +11,7 @@
"dependencies": { "dependencies": {
"@hibas123/theme": "^1.2.14", "@hibas123/theme": "^1.2.14",
"@hibas123/utils": "^2.2.3", "@hibas123/utils": "^2.2.3",
"elasticlunr": "^0.9.5",
"feather-icons": "^4.24.1", "feather-icons": "^4.24.1",
"navigo": "^7.1.2", "navigo": "^7.1.2",
"svelte": "^3.16.0", "svelte": "^3.16.0",
@ -19,6 +20,7 @@
"uuid": "^3.3.3" "uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/elasticlunr": "^0.9.0",
"@types/navigo": "^7.0.1", "@types/navigo": "^7.0.1",
"@types/node": "^12.12.14", "@types/node": "^12.12.14",
"@types/uuid": "^3.4.6", "@types/uuid": "^3.4.6",

View File

@ -1,28 +1,40 @@
<script> <script>
// import Trash from "feather-icons/dist/icons/trash.svg" // import Trash from "feather-icons/dist/icons/trash.svg"
// import Vaults from "./views/Vaults.svelte"; // import Vaults from "./views/Vaults.svelte";
import { import { pageStore } from "./router";
pageStore
} from "./router";
import { import { DeviceType, DeviceTypes, ModalStore } from "./stores";
DeviceType,
DeviceTypes,
ModalStore
} from "./stores";
$: console.log("Current DeviceType:", DeviceTypes[$DeviceType]); import QuestionManager from "./stores";
const { version } = QuestionManager;
$: console.log("Current DeviceType:", DeviceTypes[$DeviceType]);
</script> </script>
<div> <style>
{#if $DeviceType === DeviceTypes.MOBILE} .version {
<svelte:component this={$pageStore.mobile} params={$pageStore.params} /> position: fixed;
{:else} bottom: 1.5rem;
<svelte:component this={$pageStore.desktop} params={$pageStore.params} /> left: 0.5rem;
{/if} }
</style>
{#if $ModalStore} <div>
<svelte:component this={$ModalStore.component} modal={$ModalStore}/> <div class="header" style="padding: .25rem">
{/if} <span on:click={() => (window.location.hash = '#/')}>CCNA Trainer:</span>
</div> <span>{version}</span>
</div>
{#if $DeviceType === DeviceTypes.MOBILE}
<svelte:component this={$pageStore.mobile} params={$pageStore.params} />
{:else}
<svelte:component this={$pageStore.desktop} params={$pageStore.params} />
{/if}
{#if $ModalStore}
<svelte:component this={$ModalStore.component} modal={$ModalStore} />
{/if}
</div>
<div class="version">{version}</div>

View File

@ -43,4 +43,8 @@ const app = new App({
target: document.getElementById("content") target: document.getElementById("content")
}); });
export default app; export default app;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register("sw.ts");
}

View File

@ -19,14 +19,28 @@ export const pageStore = readable<ActiveRoute>(undefined, (set) => {
router.resolve(); router.resolve();
}); });
import Home from "./views/Home.svelte"
import Overview from "./views/Overview.svelte"; import Overview from "./views/Overview.svelte";
import Browse from "./views/Browse.svelte";
router.on("/", (params) => { router.on("/", (params) => {
setComponent({
desktop: Home as typeof SvelteComponent,
mobile: Home as typeof SvelteComponent,
params: {}
})
}).on("/test", (params) => {
setComponent({ setComponent({
desktop: Overview as typeof SvelteComponent, desktop: Overview as typeof SvelteComponent,
mobile: Overview as typeof SvelteComponent, mobile: Overview as typeof SvelteComponent,
params params
}) })
}).on("/browse", (params) => {
setComponent({
desktop: Browse as typeof SvelteComponent,
mobile: Browse as typeof SvelteComponent,
params: {}
})
}) })
// .on("/:vaultid", (params) => { // .on("/:vaultid", (params) => {

View File

@ -1,28 +1,35 @@
import { writable, readable } from "svelte/store"; import { writable, readable } from "svelte/store";
import { Question, Exam } from "./data"; import { Question, Exam, QuestionTypes } from "./data";
import CCNA2 from "../ccna2.json"; import CCNA2 from "../ccna2.json";
import CCNA3 from "../ccna3.json"; import CCNA3 from "../ccna3.json";
import CCNA4 from "../ccna4.json"; import CCNA4 from "../ccna4.json";
import TestData from "../data-test.json"; import TestData from "../data-test.json";
const tests = new Map<string, Exam>(); const Tests = new Map<string, Exam>();
tests.set("ccna4", CCNA4); Tests.set("ccna4", CCNA4);
tests.set("ccna3", CCNA3); Tests.set("ccna3", CCNA3);
tests.set("ccna2", CCNA2); Tests.set("ccna2", CCNA2);
tests.set("test", TestData) Tests.set("test", TestData)
const DEFAULT = "ccna4"; const DEFAULT = "ccna4";
let test = new URL(window.location.href).searchParams.get("exam") || DEFAULT; export let test = new URL(window.location.href).searchParams.get("exam") || DEFAULT;
const dataVersion = tests.has(test) ? test : DEFAULT; const dataVersion = Tests.has(test) ? test : DEFAULT;
const Data = tests.get(dataVersion); const Data = Tests.get(dataVersion);
const QuestionMap = new Map<string, Question>();
Data.questions.forEach(q => QuestionMap.set(q.id, q));
console.log("Running exam:", dataVersion); console.log("Running exam:", dataVersion);
const runsShould = 3; const runsShould = 3;
import elasticlunr from "elasticlunr";
console.log(elasticlunr);
class QuestionManager { class QuestionManager {
version: string = dataVersion; version: string = dataVersion;
@ -40,6 +47,22 @@ class QuestionManager {
level: number level: number
}[]; }[];
index: elasticlunr.Index<{ id: string, title: string, answers: string }>;
private getAnswerString(question: Question) {
switch (question.type) {
case QuestionTypes.SelectOne:
case QuestionTypes.SelectMultiple:
return Object.values(question.options).join(" ; ");
case QuestionTypes.TextInput:
return question.correct;
case QuestionTypes.AssignValues:
return [...Object.values(question.fields), ...Object.values(question.values)].join(" ; ");
default:
return "";
}
}
constructor() { constructor() {
this.availableQuestions = Data.questions.map(question => { this.availableQuestions = Data.questions.map(question => {
return { return {
@ -49,6 +72,32 @@ class QuestionManager {
}) })
this.getNewActive(); this.getNewActive();
this.index = elasticlunr(function () {
this.addField("title")
this.addField("answers");
this.setRef("id")
})
Data.questions
.map(e => ({
id: e.id,
title: e.title,
answers: this.getAnswerString(e)
}))
.forEach(e => this.index.addDoc(e));
}
search(term: string) {
if (term === "")
return Data.questions;
const match = this.index.search(term, {
fields: {
title: { boost: 2 },
answers: { boost: 1 }
}
}).map(e => QuestionMap.get(e.ref));
return match;
} }
getNewActive() { getNewActive() {
@ -108,4 +157,6 @@ export const IsDark = readable(Theme.isDark(), set => {
return () => Theme.isDarkObservable.unsubscribe(onChange); return () => Theme.isDarkObservable.unsubscribe(onChange);
}) })
export { ModalStore } from "./modals"; export { ModalStore } from "./modals";
export const AvailableTests = Array.from(Tests.keys());

37
src/sw.ts Normal file
View File

@ -0,0 +1,37 @@
var CACHE = 'cache-and-update';
self.addEventListener('install', function (evt) {
console.log('The service worker is being installed.');
evt.waitUntil(precache());
});
self.addEventListener('fetch', function (evt: FetchEvent) {
console.log('The service worker is serving the asset.');
evt.respondWith(fromCache(evt.request, update(evt.request)));
evt.waitUntil(update(evt.request));
});
function precache() {
return caches.open(CACHE).then(function (cache) {
return cache.addAll([
"./",
"./index.html"
]);
});
}
function fromCache(request, update) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request).then(function (matching) {
return matching || update;
});
});
}
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response);
});
});
}

78
src/views/Browse.svelte Normal file
View File

@ -0,0 +1,78 @@
<script>
import QuestionManager from "../stores";
import { QuestionTypes } from "../data";
let term = "";
$: questions = QuestionManager.search(term);
let opened = undefined;
// $:opened = openedId === undefined ? undefined : questions.find(e=>e.id === openedId);
$: console.log("Opened:", opened)
</script>
<div class="container" style="overflow-x: hidden">
<br />
<input
class="inp"
style="width: 100%"
type="text"
bind:value={term}
placeholder="Search..." />
<ul class="list list-divider list-clickable">
{#each questions as question}
<li on:click={()=>opened = opened === question.id ? undefined : question.id}>
<p>
{@html question.title}
</p>
{#if opened === question.id}
{#if question.type === QuestionTypes.SelectOne}
<ul>
{#each Object.entries(question.options) as [key, value]}
<li class={question.correct === key ? "correct" : ""}>
{value}
</li>
{/each}
</ul>
{:else if question.type === QuestionTypes.SelectMultiple}
<ul>
{#each Object.entries(question.options) as [key, value]}
<li class={question.correct.indexOf(key) >= 0 ? "correct" : ""}>
{value}
</li>
{/each}
</ul>
{:else if question.type === QuestionTypes.TextInput}
<p>{question.correct}</p>
{:else if question.type === QuestionTypes.AssignValues}
<ul>
{#each Object.entries(question.fields) as [key, value]}
<li>
{value}{question.values[question.correct[key]]}
</li>
{/each}
</ul>
{:else}
Unknown type!
{/if}
{/if}
</li>
{/each}
</ul>
</div>
<style>
.correct {
color: green
}
</style>
<!-- {#if opened}
<div class="modal">
{@html opened.title};
<div class="modal-action">
<button class="btn" on:click={()=>openedId = undefined}>Close</button>
</div>
</div>
{/if} -->

20
src/views/Home.svelte Normal file
View File

@ -0,0 +1,20 @@
<script>
import {AvailableTests, test} from "../stores";
let selected = test;
</script>
<div class="container" style="overflow-x: hidden">
<p>Test your knowlegde</p>
<button class="btn btn-primary" on:click={()=>window.location.hash = "#/test"}>Test</button>
<p>Browse and search specific questions</p>
<button class="btn btn-primary" on:click={()=>window.location.hash = "#/browse"}>Search questions</button>
<br>
<select class="inp" bind:value={selected}>
{#each AvailableTests as test}
<option>{test}</option>
{/each}
</select>
<button class="btn" on:click={()=>window.location.href = "./?exam=" + selected}>switch</button>
</div>

View File

@ -1,8 +1,7 @@
<script> <script>
import QuestionManager from "../stores"; import QuestionManager from "../stores";
const { const {
progress, progress
version
} = QuestionManager; } = QuestionManager;
import Question from "./Question.svelte" import Question from "./Question.svelte"
@ -20,19 +19,11 @@
footer>div { footer>div {
background-color: var(--primary); background-color: var(--primary);
} }
.version {
position: fixed;
bottom :1.5rem;
left: .5rem;
}
</style> </style>
<div class="container" style="overflow-x: hidden"> <div class="container" style="overflow-x: hidden">
<Question /> <Question />
</div> </div>
<div class="version">{version}</div>
<footer class="elv-24"> <footer class="elv-24">
<div style={"width:" + $progress + "%;" }>{($progress).toFixed(2)}%</div> <div style={"width:" + $progress + "%;" }>{($progress).toFixed(2)}%</div>
</footer> </footer>