Compare commits
16 Commits
b130482e21
...
master
Author | SHA1 | Date | |
---|---|---|---|
ff968f62a8 | |||
94b27f9ee4 | |||
2accd04546 | |||
b124d55273 | |||
a980e25e93 | |||
18beebda7a | |||
ef8880b240 | |||
c1cf1847cd | |||
82aed19f85 | |||
f091b318d4 | |||
f4a8439aee | |||
51eaa7a565 | |||
4a108aef06 | |||
684e193049 | |||
50d1e9ccae | |||
2d918eff7e |
@ -410,10 +410,10 @@
|
|||||||
"images": [],
|
"images": [],
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"options": {
|
"options": {
|
||||||
"q29-0": "switchport mode access switchport port-security",
|
"q29-0": "switchport mode access <br> switchport port-security",
|
||||||
"q29-1": "switchport mode access",
|
"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 switchport port-security maximum 2 switchport port-security mac-address sticky",
|
"q29-2": "switchport mode access <br> switchport port-security maximum 2 <br> 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-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"
|
"correct": "q29-1"
|
||||||
},
|
},
|
||||||
@ -553,10 +553,10 @@
|
|||||||
"images": [],
|
"images": [],
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"options": {
|
"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-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 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-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 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-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 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-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"
|
"correct": "q39-2"
|
||||||
},
|
},
|
||||||
@ -862,10 +862,10 @@
|
|||||||
"images": [],
|
"images": [],
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"options": {
|
"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-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 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-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 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-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 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-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"
|
"correct": "q59-0"
|
||||||
},
|
},
|
||||||
@ -1025,10 +1025,10 @@
|
|||||||
"images": [],
|
"images": [],
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"options": {
|
"options": {
|
||||||
"q74-0": "shutdown no shutdown",
|
"q74-0": "shutdown <br> no shutdown",
|
||||||
"q74-1": "shutdown no switchport port-security",
|
"q74-1": "shutdown <br> no switchport port-security",
|
||||||
"q74-2": "shutdown no switchport port-security violation shutdown",
|
"q74-2": "shutdown <br> no switchport port-security violation shutdown",
|
||||||
"q74-3": "shutdown no switchport port-security maximum"
|
"q74-3": "shutdown <br> no switchport port-security maximum"
|
||||||
},
|
},
|
||||||
"correct": "q74-0"
|
"correct": "q74-0"
|
||||||
},
|
},
|
||||||
@ -1344,10 +1344,10 @@
|
|||||||
"images": [],
|
"images": [],
|
||||||
"type": 0,
|
"type": 0,
|
||||||
"options": {
|
"options": {
|
||||||
"q96-0": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# spanning-tree 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 Switch(config-if)# spanning-tree portfast",
|
"q96-1": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# spanning-tree portfast",
|
||||||
"q96-2": "Switch(config)# interface gigabitethernet 1/1 ",
|
"q96-2": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport mode trunk",
|
||||||
"q96-3": "Switch(config)# interface gigabitethernet 1/1 Switch(config-if)# switchport access vlan 1"
|
"q96-3": "Switch(config)# interface gigabitethernet 1/1 <br> Switch(config-if)# switchport access vlan 1"
|
||||||
},
|
},
|
||||||
"correct": "q96-2"
|
"correct": "q96-2"
|
||||||
},
|
},
|
2113
ccna3.json
Normal file
2113
ccna3.json
Normal file
File diff suppressed because it is too large
Load Diff
8
ccna4-missing
Normal file
8
ccna4-missing
Normal 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
2007
ccna4.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -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
195
data-test.json
Normal 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
4583
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
@ -9,30 +9,25 @@
|
|||||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hibas123/theme": "^1.2.6",
|
"@hibas123/theme": "^1.2.14",
|
||||||
"@hibas123/utils": "^2.1.1",
|
"@hibas123/utils": "^2.2.3",
|
||||||
"feather-icons": "^4.24.1",
|
"feather-icons": "^4.24.1",
|
||||||
|
"fuse.js": "^3.4.6",
|
||||||
"navigo": "^7.1.2",
|
"navigo": "^7.1.2",
|
||||||
"rollup-plugin-json": "^4.0.0",
|
"svelte": "^3.16.0",
|
||||||
"svelte": "^3.12.1",
|
"svelte-key": "^1.0.0",
|
||||||
"ts-node": "^8.3.0",
|
"ts-node": "^8.5.4",
|
||||||
"uuid": "^3.3.3"
|
"uuid": "^3.3.3"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/navigo": "^7.0.1",
|
"@types/navigo": "^7.0.1",
|
||||||
"@types/node": "^12.7.5",
|
"@types/node": "^12.12.14",
|
||||||
"@types/uuid": "^3.4.5",
|
"@types/uuid": "^3.4.6",
|
||||||
"node-sass": "^4.12.0",
|
"node-sass": "^4.13.0",
|
||||||
"parcel-bundler": "^1.12.3",
|
"parcel-bundler": "^1.12.4",
|
||||||
"parcel-plugin-svelte": "^3.0.1",
|
"parcel-plugin-svelte": "^4.0.5",
|
||||||
"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",
|
|
||||||
"svelte-preprocess-sass": "^0.2.0",
|
"svelte-preprocess-sass": "^0.2.0",
|
||||||
"typescript": "^3.6.3"
|
"typescript": "^3.7.3"
|
||||||
},
|
},
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 2 Chrome versions"
|
"last 2 Chrome versions"
|
||||||
|
@ -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;
|
|
||||||
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -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>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="version">{version}</div>
|
||||||
|
@ -44,3 +44,7 @@ const app = new App({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default app;
|
export default app;
|
||||||
|
|
||||||
|
if ('serviceWorker' in navigator) {
|
||||||
|
navigator.serviceWorker.register("sw.ts");
|
||||||
|
}
|
9
src/rand.ts
Normal file
9
src/rand.ts
Normal 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;
|
||||||
|
}
|
@ -19,15 +19,28 @@ export const pageStore = readable<ActiveRoute>(undefined, (set) => {
|
|||||||
router.resolve();
|
router.resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
import VaultsMobile from "./views/Overview.svelte";
|
import Home from "./views/Home.svelte"
|
||||||
import VaultsDesktop from "./views/Overview.svelte";
|
import Overview from "./views/Overview.svelte";
|
||||||
|
import Browse from "./views/Browse.svelte";
|
||||||
|
|
||||||
router.on("/", (params) => {
|
router.on("/", (params) => {
|
||||||
setComponent({
|
setComponent({
|
||||||
desktop: VaultsDesktop as typeof SvelteComponent,
|
desktop: Home as typeof SvelteComponent,
|
||||||
mobile: VaultsMobile as typeof SvelteComponent,
|
mobile: Home as typeof SvelteComponent,
|
||||||
|
params: {}
|
||||||
|
})
|
||||||
|
}).on("/test", (params) => {
|
||||||
|
setComponent({
|
||||||
|
desktop: 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) => {
|
||||||
|
@ -1,10 +1,36 @@
|
|||||||
import { writable, readable } from "svelte/store";
|
import { writable, readable } from "svelte/store";
|
||||||
import Data from "../data.json";
|
import { Question, Exam, QuestionTypes } from "./data";
|
||||||
import { Question } 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;
|
const runsShould = 3;
|
||||||
|
|
||||||
|
import Fuse from "fuse.js";
|
||||||
|
|
||||||
class QuestionManager {
|
class QuestionManager {
|
||||||
|
version: string = dataVersion;
|
||||||
|
|
||||||
activeQuestion = writable<{
|
activeQuestion = writable<{
|
||||||
correct: () => void;
|
correct: () => void;
|
||||||
wrong: () => void;
|
wrong: () => void;
|
||||||
@ -19,6 +45,22 @@ class QuestionManager {
|
|||||||
level: number
|
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() {
|
constructor() {
|
||||||
this.availableQuestions = Data.questions.map(question => {
|
this.availableQuestions = Data.questions.map(question => {
|
||||||
return {
|
return {
|
||||||
@ -28,6 +70,30 @@ class QuestionManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.getNewActive();
|
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() {
|
getNewActive() {
|
||||||
@ -88,3 +154,5 @@ export const IsDark = readable(Theme.isDark(), set => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
export { ModalStore } from "./modals";
|
export { ModalStore } from "./modals";
|
||||||
|
|
||||||
|
export const AvailableTests = Array.from(Tests.keys());
|
37
src/sw.ts
Normal file
37
src/sw.ts
Normal 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
78
src/views/Browse.svelte
Normal 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
20
src/views/Home.svelte
Normal 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>
|
@ -25,5 +25,5 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="elv-24">
|
<footer class="elv-24">
|
||||||
<div style={"width:" + $progress + "%;" }>{$progress}%</div>
|
<div style={"width:" + $progress + "%;" }>{($progress).toFixed(2)}%</div>
|
||||||
</footer>
|
</footer>
|
@ -8,6 +8,7 @@
|
|||||||
import SelectMultiple from "./questions/SelectMultiple.svelte";
|
import SelectMultiple from "./questions/SelectMultiple.svelte";
|
||||||
import AssignValues from "./questions/AssignValues.svelte";
|
import AssignValues from "./questions/AssignValues.svelte";
|
||||||
import TextInput from "./questions/TextInput.svelte";
|
import TextInput from "./questions/TextInput.svelte";
|
||||||
|
import Identity from 'svelte-key'
|
||||||
|
|
||||||
const questions = new Map();
|
const questions = new Map();
|
||||||
questions.set(QuestionTypes.SelectOne, SelectOne);
|
questions.set(QuestionTypes.SelectOne, SelectOne);
|
||||||
@ -24,18 +25,19 @@
|
|||||||
let idx = 0;
|
let idx = 0;
|
||||||
|
|
||||||
function next() {
|
function next() {
|
||||||
idx++;
|
showResult = false;
|
||||||
if (isCorrect) {
|
if (isCorrect) {
|
||||||
isCorrect = false;
|
isCorrect = false;
|
||||||
$activeQuestion.correct()
|
$activeQuestion.correct()
|
||||||
} else {
|
} else {
|
||||||
$activeQuestion.wrong();
|
$activeQuestion.wrong();
|
||||||
}
|
}
|
||||||
showResult = false;
|
idx++;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if question}
|
{#if question}
|
||||||
|
<div style={"background:" + (showResult ? (isCorrect ? "rgba(92, 255, 92, 0.30)" : "rgba(255, 92, 92, 0.46)") : "") + ";"}>
|
||||||
<div class="margin">
|
<div class="margin">
|
||||||
<h2>{@html question.title}</h2>
|
<h2>{@html question.title}</h2>
|
||||||
</div>
|
</div>
|
||||||
@ -45,17 +47,30 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
<div class="margin">
|
<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>
|
||||||
<div class="margin">
|
<div class="margin fl">
|
||||||
{#if !showResult}
|
{#if !showResult}
|
||||||
<button class="btn" on:click={()=>showResult = true}>
|
<button class="btn lbtn" on:click={()=>showResult = true}>
|
||||||
Check
|
Check
|
||||||
</button>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button class="btn" on:click={()=>next()}>
|
<button class="btn lbtn" on:click={()=>next()}>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.fl {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lbtn {
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
</style>
|
@ -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} -->
|
||||||
|
@ -1,38 +1,31 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import randomize from "../../rand.ts";
|
||||||
|
|
||||||
export let question;
|
export let question;
|
||||||
export let showResult = false;
|
export let showResult = false;
|
||||||
export let isCorrect = false;
|
export let isCorrect = false;
|
||||||
|
|
||||||
|
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
|
||||||
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
|
|
||||||
|
|
||||||
let selected = [];
|
let selected = [];
|
||||||
|
|
||||||
$: isCorrect = selected.length === question.correct.length && selected.every(val => question.correct.find(v => v === val));
|
$: isCorrect = selected.length === question.correct.length && selected.every(val => question.correct.find(v => v === val));
|
||||||
|
|
||||||
$: console.log("Selected:", selected, "showResult:", showResult, "isCorrect", isCorrect);
|
$: 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>
|
</script>
|
||||||
<h3>Select {question.correct.length}</h3>
|
<h3>Select {question.correct.length}</h3>
|
||||||
{#each options as option}
|
{#each options as option (option.key)}
|
||||||
<div
|
<div
|
||||||
key={option.key}
|
key={option.key}
|
||||||
class:should={showResult && question.correct.indexOf(option.key) >= 0}
|
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
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={option.key}
|
id={option.key}
|
||||||
checked={selected.indexOf(option.key) >= 0}
|
bind:group={selected}
|
||||||
on:click={(evt)=>showResult ? evt.preventDefault() : toggle(option.key)}
|
on:click={(evt)=>showResult ? evt.preventDefault() : undefined}
|
||||||
|
value={option.key}
|
||||||
>
|
>
|
||||||
<span></span>
|
<span></span>
|
||||||
</label>
|
</label>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import randomize from "../../rand.ts";
|
||||||
|
|
||||||
export let question;
|
export let question;
|
||||||
export let showResult = false;
|
export let showResult = false;
|
||||||
|
|
||||||
export let isCorrect = false;
|
export let isCorrect = false;
|
||||||
|
|
||||||
|
$: options = randomize(Object.keys(question.options).map(e => ({ key: e, value: question.options[e] })));
|
||||||
$: options = Object.keys(question.options).map(e => ({ key: e, value: question.options[e] }));
|
|
||||||
|
|
||||||
let selected = undefined;
|
let selected = undefined;
|
||||||
$: isCorrect = selected === question.correct;
|
$: isCorrect = selected === question.correct;
|
||||||
@ -19,7 +20,7 @@
|
|||||||
key={option.key}
|
key={option.key}
|
||||||
class:should={showResult && option.key === question.correct }
|
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
|
<input
|
||||||
type="radio"
|
type="radio"
|
||||||
id={option.key}
|
id={option.key}
|
||||||
|
Reference in New Issue
Block a user