Compare commits

...

16 Commits

Author SHA1 Message Date
ff968f62a8 Changing search engine 2020-01-20 23:03:31 +01:00
94b27f9ee4 Adding some new features 2020-01-20 18:03:21 +01:00
2accd04546 Adding ccna4 preview questions. 2020-01-06 16:23:08 +01:00
b124d55273 Adding support for all CCNA3 questions 2019-12-09 01:52:50 +01:00
a980e25e93 Making multiple examps available
Updating dependencies
Possibly fixing errors on MultipleSelect Questions
2019-12-04 14:13:48 +01:00
18beebda7a Updating to CCNA3 2019-11-25 10:26:52 +01:00
ef8880b240 Formatting 2019-09-11 19:01:58 +02:00
c1cf1847cd formatting 2019-09-11 18:47:23 +02:00
82aed19f85 Randomize order of options 2019-09-11 18:36:31 +02:00
f091b318d4 Adding correctness background color 2019-09-11 18:10:39 +02:00
f4a8439aee C 2019-09-11 18:01:31 +02:00
51eaa7a565 Making button right 2019-09-11 17:53:10 +02:00
4a108aef06 asd 2019-09-11 17:51:54 +02:00
684e193049 Fixing possible to large index 2019-09-11 17:50:32 +02:00
50d1e9ccae Fixing data 2019-09-11 17:19:54 +02:00
2d918eff7e Fixing some bugs 2019-09-11 17:09:46 +02:00
22 changed files with 6976 additions and 2575 deletions

View File

@ -410,10 +410,10 @@
"images": [],
"type": 0,
"options": {
"q29-0": "switchport mode access switchport port-security",
"q29-1": "switchport mode access",
"q29-2": "switchport mode access switchport port-security maximum 2 switchport port-security mac-address sticky",
"q29-3": "switchport mode access switchport port-security maximum 2 switchport port-security mac-address\n sticky switchport port-security violation protect"
"q29-0": "switchport mode access <br> switchport port-security",
"q29-1": "switchport mode access <br> switchport port-security <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky <br> switchport port-security violation restrict",
"q29-2": "switchport mode access <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky",
"q29-3": "switchport mode access <br> switchport port-security maximum 2 <br> switchport port-security mac-address sticky <br> switchport port-security violation protect"
},
"correct": "q29-1"
},
@ -553,10 +553,10 @@
"images": [],
"type": 0,
"options": {
"q39-0": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 ip dhcp excluded-address 192.168.100.254 ip\n dhcp pool LAN POOL-100 network 192.168.100.0 255.255.255.0 ip default gateway 192.168.100.1",
"q39-1": "ip dhcp excluded-address 192.168.100.1 192.168.100.9 ip dhcp excluded-address 192.168.101.254 ip dhcp\n pool LAN POOL-100 ip network 192.168.100.0 255.255.254.0 ip default-gateway 192.168.100.1",
"q39-2": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 ip dhcp\n excluded-address 192.168.101.254 ip dhcp pool LAN POOL-100 network 192.168.100.0\n 255.255.254.0 default-router 192.168.100.1",
"q39-3": "dhcp pool LAN-POOL 100 ip dhcp excluded-address 192.168.100.1 192.168.100.9 ip dhcp excluded-address\n 192.168.100.254 network 192.168.100.0 255.255.254.0 default-router 192.168.101.1"
"q39-0": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 <br> ip dhcp excluded-address 192.168.100.254 <br> ip dhcp pool LAN POOL-100 <br> network 192.168.100.0 255.255.255.0 <br> ip default gateway 192.168.100.1",
"q39-1": "ip dhcp excluded-address 192.168.100.1 192.168.100.9 <br> ip dhcp excluded-address 192.168.101.254 <br> ip dhcp pool LAN POOL-100 <br> ip network 192.168.100.0 255.255.254.0 <br> ip default-gateway 192.168.100.1",
"q39-2": "ip dhcp excluded-address 192.168.100.1 192.168.100.10 <br> ip dhcp excluded-address 192.168.101.254 <br> ip dhcp pool LAN POOL-100 <br> network 192.168.100.0 255.255.254.0 <br> default-router 192.168.100.1",
"q39-3": "dhcp pool LAN-POOL 100 <br> ip dhcp excluded-address 192.168.100.1 192.168.100.9 <br> ip dhcp excluded-address 192.168.100.254 <br> network 192.168.100.0 255.255.254.0 <br> default-router 192.168.101.1"
},
"correct": "q39-2"
},
@ -862,10 +862,10 @@
"images": [],
"type": 0,
"options": {
"q59-0": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat inside source list\n 1 interface serial 0/0/0 overload",
"q59-1": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp",
"q59-2": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp overload",
"q59-3": "access-list 1 permit 10.0.0.0 0.255.255.255 ip nat pool comp 192.168.2.1 192.168.2.8 netmask\n 255.255.255.240 ip nat inside source list 1 pool comp overload ip nat inside source static 10.0.0.5\n 209.165.200.225"
"q59-0": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat inside source list 1 interface serial 0/0/0 overload",
"q59-1": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp",
"q59-2": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp overload",
"q59-3": "access-list 1 permit 10.0.0.0 0.255.255.255 <br> ip nat pool comp 192.168.2.1 192.168.2.8 netmask 255.255.255.240 <br> ip nat inside source list 1 pool comp overload <br> ip nat inside source static 10.0.0.5 209.165.200.225"
},
"correct": "q59-0"
},
@ -1025,10 +1025,10 @@
"images": [],
"type": 0,
"options": {
"q74-0": "shutdown no shutdown",
"q74-1": "shutdown no switchport port-security",
"q74-2": "shutdown no switchport port-security violation shutdown",
"q74-3": "shutdown no switchport port-security maximum"
"q74-0": "shutdown <br> no shutdown",
"q74-1": "shutdown <br> no switchport port-security",
"q74-2": "shutdown <br> no switchport port-security violation shutdown",
"q74-3": "shutdown <br> no switchport port-security maximum"
},
"correct": "q74-0"
},
@ -1344,10 +1344,10 @@
"images": [],
"type": 0,
"options": {
"q96-0": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# spanning-tree vlan 1",
"q96-1": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# spanning-tree portfast",
"q96-2": "Switch(config)# interface gigabitethernet 1/1 ",
"q96-3": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# switchport access vlan 1"
"q96-0": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# spanning-tree vlan 1",
"q96-1": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# spanning-tree portfast",
"q96-2": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport mode trunk",
"q96-3": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport access vlan 1"
},
"correct": "q96-2"
},

2113
ccna3.json Normal file

File diff suppressed because it is too large Load Diff

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!'

2007
ccna4.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +0,0 @@
{
"title": "CCNA 2 Final Exam",
"questions": [{
"id": "quest1",
"type": 0,
"title": "The buffers for packet processing and the running configuration file are temporarily stored in which type of router memory?",
"images": [],
"options": {
"q1-1": "flash",
"q1-2": "NVRAM",
"q1-3": "RAM",
"q1-4": "ROM"
},
"correct": "q1-3"
},
{
"id": "quest2",
"type": 0,
"title": "Refer to the exhibit. A company has an internal network of 192.168.10.0/24 for their employee workstations and a DMZ network of 192.168.3.0/24 to host servers. The company uses NAT when inside hosts connect to outside network. A network administrator issues the show ip nat translations command to check the NAT configurations. Which one of source IPv4 addresses is translated by R1 with PAT",
"images": [
"i258133v1n1_258133.png"
],
"options": {
"q2-1": "10.0.0.31",
"q2-2": "192.168.3.5",
"q2-3": "192.168.3.33",
"q2-4": "192.168.10.35",
"q2-5": "172.16.20.5"
},
"correct": "q2-4"
}
]
}

195
data-test.json Normal file
View File

@ -0,0 +1,195 @@
{
"title": "CCNA 2 Final Exam",
"questions": [
{
"id": "asd",
"type": 2,
"title": "Match the description to the EIGRP packet type. (Not all options are used.)",
"images": [],
"fields": {
"f1": "used to propagate routing information to EIGRP neighbors",
"f2": "used to acknowledge the receipt of an EIGTP message that was sent using reliable delivery",
"f3": "used for neighbor discovery and to maintain neighbor adjacencies",
"f4": "send in response to an EIGRP query",
"f5": "used to query routes from neighbors"
},
"values": {
"v1": "hello packets",
"v2": "query packets",
"v3": "reply packets",
"v4": "update packets",
"v5": "acknowledgment packets",
"v6": "database description packets"
},
"correct": {
"f1": "v4",
"f2": "v5",
"f3": "v1",
"f4": "v3",
"f5": "v2"
}
},
{
"id": "ccna3-q38",
"type": 2,
"title": "Match each description to its corresponding LSA type. (Not all options are used.)",
"images": [],
"fields": {
"f1": "generated by the DR on a multiaccess segment and flooded within an area",
"f2": "generated by ABRs and sent between areas",
"f3": "generated by ABRs and sent between areas to advertise the location of an AS",
"f4": "generated by all routers and flooded within an area"
},
"values": {
"v1": "type 1",
"v2": "type 2",
"v3": "type 3",
"v4": "type 4",
"v5": "type 5"
},
"correct": {
"f1": "v2",
"f2": "v3",
"f3": "v4",
"f4": "v1"
}
},
{
"id": "ccna3-q135",
"type": 2,
"title": "Match the order in which the link-state routing process occurs on a router. (Not all options are used.)",
"images": [],
"fields": {
"f1": "step 1",
"f2": "step 2",
"f3": "step 3",
"f4": "step 4",
"f5": "step 5"
},
"values": {
"v1": "Each router is responsible for “saying hello” to its neighbors on directly connected networks.",
"v2": "Each router builds a Link-State Packet (LSP) containing the state of each directly connected link",
"v3": "Each router learns about its own directly connected networks",
"v4": "Each router increments the hop count for the destination network",
"v5": "Each router floods the LSP to all neighbors, who then store all LSPs received in a database",
"v6": "Each router uses the database to construct a complete map of the topology and computes the best"
},
"correct": {
"f1": "v3",
"f2": "v1",
"f3": "v2",
"f4": "v5",
"f5": "v6"
}
},
{
"id": "ccna3-q136",
"type": 2,
"title": " Match the description to the term. (Not all options are used.)",
"images": [],
"fields": {
"f1": "This is the algorithm used by OSPF",
"f2": "This is where the details of the neighboring routers can be found",
"f3": "This is where you can find the topology table",
"f4": "All the routers are in the backbone area"
},
"values": {
"v1": "adjacency database",
"v2": "Shortest Path First",
"v3": "single-area OSPF",
"v4": "DUAL",
"v5": "link-state database",
"v6": "multiarea OSPF"
},
"correct": {
"f1": "v2",
"f2": "v1",
"f3": "v5",
"f4": "v3"
}
},
{
"id": "ccna3-q137",
"type": 2,
"title": "Match the order of precedence to the process logic that an OSPFv3 network router goes through in choosing a router ID. (Not all options are used.)",
"images": [],
"fields": {
"f1": "priority 1",
"f2": "priority 2",
"f3": "priority 3",
"f4": "priority 4"
},
"values": {
"v1": "The router displays a console message to configure the router ID manually",
"v2": "The router uses the highest configured IPv4 address of an active interface",
"v3": "The router chooses the highest IPv6 address that is configured on the router",
"v4": "The router uses the highest configured IPv4 address of a loopback interface",
"v5": "The router uses the explicitly configured router ID if any"
},
"correct": {
"f1": "v5",
"f2": "v4",
"f3": "v2",
"f4": "v1"
}
},
{
"id": "ccna3-q140",
"type": 2,
"title": "Match each OSPF router type description with its name. (Not all options are used.)",
"images": [],
"fields": {
"f1": "All the routers of this type have identical LSDBs",
"f2": "All the routers of this type mainrain separate LSDBs for each area to which they connect",
"f3": "All the routers of this type can import non-OSPF network information to the OSPF network and vice versa using route redistribution"
},
"values": {
"v1": "internal router",
"v2": "backbone router",
"v3": "autonomus system boudary router (ASBR)",
"v4": "area border router (ABR)"
},
"correct": {
"f1": "v1",
"f2": "v4",
"f3": "v3"
}
}
],
"dead": [
{
"id": "quest1",
"type": 1,
"title": "The buffers for packet processing and the running configuration file are temporarily stored in which type of router memory?",
"images": [],
"options": {
"q1-1": "flash",
"q1-2": "NVRAM",
"q1-3": "RAM",
"q1-4": "ROM"
},
"correct": [
"q1-3"
]
},
{
"id": "quest2",
"type": 1,
"title": "Refer to the exhibit. A company has an internal network of 192.168.10.0/24 for their employee workstations and a DMZ network of 192.168.3.0/24 to host servers. The company uses NAT when inside hosts connect to outside network. A network administrator issues the show ip nat translations command to check the NAT configurations. Which one of source IPv4 addresses is translated by R1 with PAT",
"images": [
"i258133v1n1_258133.png"
],
"options": {
"q2-1": "10.0.0.31",
"q2-2": "192.168.3.5",
"q2-3": "192.168.3.33",
"q2-4": "192.168.10.35",
"q2-5": "172.16.20.5"
},
"correct": [
"q2-4",
"q2-2"
]
}
]
}

4583
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -9,32 +9,27 @@
"author": "Fabian Stamm <dev@fabianstamm.de>",
"license": "ISC",
"dependencies": {
"@hibas123/theme": "^1.2.6",
"@hibas123/utils": "^2.1.1",
"@hibas123/theme": "^1.2.14",
"@hibas123/utils": "^2.2.3",
"feather-icons": "^4.24.1",
"fuse.js": "^3.4.6",
"navigo": "^7.1.2",
"rollup-plugin-json": "^4.0.0",
"svelte": "^3.12.1",
"ts-node": "^8.3.0",
"svelte": "^3.16.0",
"svelte-key": "^1.0.0",
"ts-node": "^8.5.4",
"uuid": "^3.3.3"
},
"devDependencies": {
"@types/navigo": "^7.0.1",
"@types/node": "^12.7.5",
"@types/uuid": "^3.4.5",
"node-sass": "^4.12.0",
"parcel-bundler": "^1.12.3",
"parcel-plugin-svelte": "^3.0.1",
"rollup": "^1.21.2",
"rollup-plugin-commonjs": "^10.1.0",
"rollup-plugin-node-resolve": "^5.2.0",
"rollup-plugin-svelte": "^5.1.0",
"rollup-plugin-terser": "^5.1.1",
"rollup-plugin-typescript2": "^0.24.1",
"@types/node": "^12.12.14",
"@types/uuid": "^3.4.6",
"node-sass": "^4.13.0",
"parcel-bundler": "^1.12.4",
"parcel-plugin-svelte": "^4.0.5",
"svelte-preprocess-sass": "^0.2.0",
"typescript": "^3.6.3"
"typescript": "^3.7.3"
},
"browserslist": [
"last 2 Chrome versions"
]
}
}

View File

@ -1,95 +0,0 @@
// import * as rollup from "rollup";
import svelteplg from 'rollup-plugin-svelte';
import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs';
import typescript from "rollup-plugin-typescript2";
import rjson from "rollup-plugin-json";
import * as fs from "fs";
import {
terser
} from 'rollup-plugin-terser';
const production = process.argv.indexOf("-d") < 0;
console.log(`Runnig in ${production ? "production" : "development"} mode!`);
let plg = [];
if (production) {
plg.push(terser())
}
if (!fs.existsSync("build"))
fs.mkdirSync("build");
fs.copyFileSync("src/index.html", "build/index.html");
export default {
input: `./src/main.ts`,
output: {
sourcemap: true,
format: 'iife',
name: 'app',
file: `build/bundle.js`
},
watch: {
clearScreen: false
},
plugins: [
cssStringPlugin({
include: [
"@hibas123/theme/out/base.css",
"@hibas123/theme/out/light.css",
"@hibas123/theme/out/dark.css"
]
}),
// svgSveltePlugin(),
resolve({
browser: true
}),
typescript({
tsconfig: "./src/tsconfig.json"
}),
svelteplg({
// enable run-time checks when not in production
dev: !production,
extensions: [".svg", ".svelte"],
css: css => {
css.write(`build/bundle.css`);
}
}),
commonjs({
namedExports: {
"node_modules/js-sha256/src/sha256.js": ["sha256"],
"node_modules/aes-js/index.js": ["ModeOfOperation"]
}
}),
rjson(),
...plg
]
};
import * as path from "path";
function cssStringPlugin({
include
}) {
return {
name: 'css-to-string', // this name will show up in warnings and errors
resolveId(source) {
if (include.indexOf(source) >= 0)
return source;
return null; // other ids should be handled as usually
},
load(id) {
if (include.indexOf(id) >= 0) {
const p = "./node_modules/" + id;
let r = `export default ${JSON.stringify(fs.readFileSync(p).toString("utf-8")).replace("'", "\'")}`;
return r;
}
return null;
}
};
}

View File

@ -1,28 +1,40 @@
<script>
// import Trash from "feather-icons/dist/icons/trash.svg"
// import Vaults from "./views/Vaults.svelte";
// import Trash from "feather-icons/dist/icons/trash.svg"
// import Vaults from "./views/Vaults.svelte";
import {
pageStore
} from "./router";
import { pageStore } from "./router";
import {
DeviceType,
DeviceTypes,
ModalStore
} from "./stores";
import { 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>
<div>
{#if $DeviceType === DeviceTypes.MOBILE}
<svelte:component this={$pageStore.mobile} params={$pageStore.params} />
{:else}
<svelte:component this={$pageStore.desktop} params={$pageStore.params} />
{/if}
<style>
.version {
position: fixed;
bottom: 1.5rem;
left: 0.5rem;
}
</style>
{#if $ModalStore}
<svelte:component this={$ModalStore.component} modal={$ModalStore}/>
{/if}
</div>
<div>
<div class="header" style="padding: .25rem">
<span on:click={() => (window.location.hash = '#/')}>CCNA Trainer:</span>
<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")
});
export default app;
export default app;
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register("sw.ts");
}

9
src/rand.ts Normal file
View File

@ -0,0 +1,9 @@
export default function randomize<T>(input: T[]): T[] {
let res: T[] = [];
input = [...input];
while (input.length > 0) {
let randomIndex = Math.floor(Math.random() * input.length);
res.push(...input.splice(randomIndex, 1));
}
return res;
}

View File

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

View File

@ -1,10 +1,36 @@
import { writable, readable } from "svelte/store";
import Data from "../data.json";
import { Question } from "./data";
import { Question, Exam, QuestionTypes } from "./data";
import CCNA2 from "../ccna2.json";
import CCNA3 from "../ccna3.json";
import CCNA4 from "../ccna4.json";
import TestData from "../data-test.json";
const Tests = new Map<string, Exam>();
Tests.set("ccna4", CCNA4);
Tests.set("ccna3", CCNA3);
Tests.set("ccna2", CCNA2);
Tests.set("test", TestData)
const DEFAULT = "ccna4";
export let test = new URL(window.location.href).searchParams.get("exam") || DEFAULT;
const dataVersion = Tests.has(test) ? test : DEFAULT;
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);
const runsShould = 3;
import Fuse from "fuse.js";
class QuestionManager {
version: string = dataVersion;
activeQuestion = writable<{
correct: () => void;
wrong: () => void;
@ -19,6 +45,22 @@ class QuestionManager {
level: number
}[];
index: Fuse<any, any>;
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() {
this.availableQuestions = Data.questions.map(question => {
return {
@ -28,6 +70,30 @@ class QuestionManager {
})
this.getNewActive();
const searchData = Data.questions
.map(e => ({
id: e.id,
title: e.title,
answers: this.getAnswerString(e)
}))
this.index = new Fuse(searchData, {
keys: [{
name: "title",
weight: 0.8
}, {
name: "answers",
weight: 0.2
}]
})
}
search(term: string) {
if (term === "")
return Data.questions;
const match = (this.index.search(term) as any[]).map(e => QuestionMap.get(e.id));
return match;
}
getNewActive() {
@ -87,4 +153,6 @@ export const IsDark = readable(Theme.isDark(), set => {
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

@ -25,5 +25,5 @@
</div>
<footer class="elv-24">
<div style={"width:" + $progress + "%;" }>{$progress}%</div>
<div style={"width:" + $progress + "%;" }>{($progress).toFixed(2)}%</div>
</footer>

View File

@ -8,6 +8,7 @@
import SelectMultiple from "./questions/SelectMultiple.svelte";
import AssignValues from "./questions/AssignValues.svelte";
import TextInput from "./questions/TextInput.svelte";
import Identity from 'svelte-key'
const questions = new Map();
questions.set(QuestionTypes.SelectOne, SelectOne);
@ -24,18 +25,19 @@
let idx = 0;
function next() {
idx++;
showResult = false;
if (isCorrect) {
isCorrect = false;
$activeQuestion.correct()
} else {
$activeQuestion.wrong();
}
showResult = false;
idx++;
}
</script>
{#if question}
<div style={"background:" + (showResult ? (isCorrect ? "rgba(92, 255, 92, 0.30)" : "rgba(255, 92, 92, 0.46)") : "") + ";"}>
<div class="margin">
<h2>{@html question.title}</h2>
</div>
@ -45,17 +47,30 @@
</div>
{/each}
<div class="margin">
<svelte:component key={idx} this={component} {question} {showResult} bind:isCorrect />
<Identity key={idx}>
<svelte:component key={idx} this={component} {question} {showResult} bind:isCorrect />
</Identity>
</div>
<div class="margin">
<div class="margin fl">
{#if !showResult}
<button class="btn" on:click={()=>showResult = true}>
<button class="btn lbtn" on:click={()=>showResult = true}>
Check
</button>
{:else}
<button class="btn" on:click={()=>next()}>
<button class="btn lbtn" on:click={()=>next()}>
Next
</button>
{/if}
</div>
{/if}
</div>
{/if}
<style>
.fl {
display: flex;
}
.lbtn {
margin-left: auto;
}
</style>

View File

@ -1 +1,81 @@
The developer was to lazy to implement this type of question. Sorry :)
<script>
import {onMount} from "svelte";
import randomize from "../../rand.ts";
export let question;
export let showResult = false;
export let isCorrect = false;
let fields = [];
onMount(()=>{
fields = randomize(Object.keys(question.fields).map(e => ({
key: e,
value: question.fields[e]
})));
})
$: values = Object.keys(question.values).map(e => ({
key: e,
value: question.values[e]
}))
let selected = {};
// $: isCorrect = selected === question.correct;
$: console.log(
"Selected:",
selected,
"showResult:",
showResult,
"isCorrect",
isCorrect
);
$: {
isCorrect = fields.every(field=>selected[field.key] === question.correct[field.key]);
}
</script>
{#each fields as field (field.key)}
<div class="input-group">
<label>{field.value}</label>
<select class="inp" bind:value={selected[field.key]}>
<option value={undefined}>-- select --</option>
{#each values as value}
<option value={value.key}>{value.value}</option>
{/each}
</select>
{#if showResult}
<div class="should">{question.values[question.correct[field.key]]}</div>
{/if}
</div>
{/each}
<style>
.should {
color: var(--primary) !important;
margin-left: 1rem;
}
</style>
<!-- {#each options as option}
<div
key={option.key}
class:should={showResult && option.key === question.correct }
>
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
<input
type="radio"
id={option.key}
checked={option.key===selected}
on:click={(evt)=>showResult ?
evt.preventDefault() : selected = option.key}
>
<span></span>
</label>
<br/>
</div>
{/each} -->

View File

@ -1,38 +1,31 @@
<script>
import randomize from "../../rand.ts";
export let question;
export let showResult = false;
export let isCorrect = false;
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
let selected = [];
$: isCorrect = selected.length === question.correct.length && selected.every(val => question.correct.find(v => v === val));
$: console.log("Selected:", selected, "showResult:", showResult, "isCorrect", isCorrect);
function toggle(id) {
if (selected.indexOf(id) < 0) {
selected = [...selected, id];
} else {
selected = selected.filter(e => e !== id);
}
}
</script>
<h3>Select {question.correct.length}</h3>
{#each options as option}
{#each options as option (option.key)}
<div
key={option.key}
class:should={showResult && question.correct.indexOf(option.key) >= 0}
>
<label class="input-checkbox" for={option.key}>{option.value}
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
<input
type="checkbox"
id={option.key}
checked={selected.indexOf(option.key) >= 0}
on:click={(evt)=>showResult ? evt.preventDefault() : toggle(option.key)}
id={option.key}
bind:group={selected}
on:click={(evt)=>showResult ? evt.preventDefault() : undefined}
value={option.key}
>
<span></span>
</label>

View File

@ -1,11 +1,12 @@
<script>
import randomize from "../../rand.ts";
export let question;
export let showResult = false;
export let isCorrect = false;
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
let selected = undefined;
$: isCorrect = selected === question.correct;
@ -19,7 +20,7 @@
key={option.key}
class:should={showResult && option.key === question.correct }
>
<label class="input-checkbox" for={option.key}>{option.value}
<label class="input-checkbox" for={option.key}>{@html option.value.replace(/\n/g, "<br>")}
<input
type="radio"
id={option.key}