1+ < html >
2+
3+ < head > < title > Typing</ title > </ head >
4+
5+ < body >
6+
7+ < div id ="selectDiv ">
8+ < p > Enter raw GitHub link</ p >
9+ < p class ="small "> Or, leave blank for default code</ p >
10+ < input id ="githubinput " type ="text " size =50 > </ input >
11+ < button id ="githubbutton " style ="display: inline-block; "> -> </ button >
12+ </ div >
13+
14+ < div id ="writingDiv " class ="displaynone ">
15+ < div id ="timer "> 00:00</ div >
16+ < div tabindex ="0 " id ="words "> </ div >
17+ </ div >
18+
19+ < div id ="resultDiv " class ="displaynone ">
20+ Your speed is < span id ="cpmDiv "> </ span > CPM!
21+ < br />
22+ Accuracy: < span id ="accuracy "> </ span > %
23+ </ div >
24+
25+ < script >
26+ var wordsDiv = document . getElementById ( "words" ) ;
27+
28+ var myCode = `import os
29+ import sqlite3
30+ from flask import Flask, request, redirect
31+
32+ app = Flask(__name__)
33+
34+ script_folder = os.path.dirname(os.path.realpath(__file__))
35+ dbs = [f.replace(".db", "") for f in os.listdir(script_folder) if f.endswith(".db")]
36+
37+ @app.route("/")
38+ def home():
39+ municipality = request.args.get("m")
40+ case_id = request.args.get("id")
41+
42+ if case_id is None or municipality is None:
43+ return "Use endpoint /?m=MUNICIPALITY&?id=CASE_ID to view a case"
44+
45+ if not municipality in dbs:
46+ return "Invalid municipality, try: " + ", ".join(dbs)
47+
48+ result = "Invalid case id"
49+ conn = sqlite3.connect("{}.db".format(municipality))
50+
51+ row = conn.execute("SELECT htmldocument FROM PostCase WHERE id={}".format(case_id)).fetchone()
52+ if row is not None:
53+ result = row[0]
54+
55+ conn.close()
56+
57+ return result
58+
59+ @app.route("/<path:path>")
60+ def external_redirect(path):
61+ return redirect("https://innsyn.ddv.no/{}".format(path))` ;
62+
63+ var lines = myCode . split ( "\n" ) ;
64+ var allCharacters = [ ] ;
65+
66+ function generateCode ( ) {
67+ lines = myCode . split ( "\n" ) ;
68+ allCharacters = [ ] ;
69+
70+ for ( var i = 0 ; i < lines . length ; i ++ ) {
71+ var line = lines [ i ] ;
72+
73+ var lineDiv = document . createElement ( "div" ) ;
74+
75+ for ( var j = 0 ; j < line . length ; j ++ ) {
76+ const character = document . createElement ( "character" ) ;
77+ character . innerHTML = line [ j ] ;
78+
79+ if ( i > 0 && j == line . length - 1 ) {
80+ character . classList . add ( "last" ) ;
81+ }
82+
83+ lineDiv . appendChild ( character ) ;
84+ allCharacters . push ( character ) ;
85+ }
86+
87+ if ( line . length > 0 ) wordsDiv . appendChild ( lineDiv ) ;
88+ }
89+
90+ allCharacters [ 0 ] . classList . add ( "active" ) ;
91+ allCharacters [ allCharacters . length - 1 ] . classList . add ( "finalElement" ) ;
92+ }
93+
94+ var characterProgress = 0 ;
95+ var numCorrect = 0 ;
96+ var numErrors = 0 ;
97+
98+ var elapsedTime = 0 ;
99+ var intervalId = undefined ;
100+ var isTimerRunning = false ;
101+ function startTimer ( ) {
102+ if ( isTimerRunning ) return ;
103+ isTimerRunning = true ;
104+
105+ const timer = document . getElementById ( "timer" ) ;
106+ timer . classList . add ( "running" ) ;
107+
108+ var startTime = Date . now ( ) ;
109+
110+ intervalId = setInterval ( function ( ) {
111+ var newTime = Date . now ( ) ;
112+ elapsedTime = Math . floor ( ( newTime - startTime ) / 1000 ) ;
113+
114+ var minutes = Math . floor ( elapsedTime / 60 ) ;
115+ var seconds = elapsedTime - minutes * 60 ;
116+
117+ var minutesText = "" ;
118+ if ( minutes < 10 ) minutesText = "0" + minutes ;
119+ else minutesText += minutes ;
120+
121+ var secondsText = "" ;
122+ if ( seconds < 10 ) secondsText = "0" + seconds ;
123+ else secondsText += seconds ;
124+
125+ timer . innerHTML = minutesText + ":" + secondsText ;
126+ } , 100 ) ;
127+ }
128+
129+ function writing_done ( ) {
130+ clearInterval ( intervalId ) ;
131+ document . getElementById ( "words" ) . removeEventListener ( "keydown" , onKeyDown_handler ) ;
132+
133+ var length = myCode . replaceAll ( "\n" , "" ) . length ;
134+
135+ var cpm = Math . round ( 60 * ( length / elapsedTime ) ) ;
136+ var accuracy = Math . round ( 100 * numCorrect / length ) ;
137+
138+ document . getElementById ( "writingDiv" ) . classList . add ( "displaynone" ) ;
139+ document . getElementById ( "resultDiv" ) . classList . remove ( "displaynone" ) ;
140+
141+ document . getElementById ( "cpmDiv" ) . innerHTML = "" + cpm ;
142+ document . getElementById ( "accuracy" ) . innerHTML = "" + accuracy ;
143+ }
144+
145+ function onKeyDown_handler ( key ) {
146+ key . preventDefault ( ) ;
147+
148+ startTimer ( ) ;
149+
150+ if ( key . key == "Backspace" ) {
151+ if ( characterProgress === 0 ) return false ;
152+
153+ allCharacters [ characterProgress ] . classList . remove ( "active" ) ;
154+ characterProgress -- ;
155+
156+ if ( allCharacters [ characterProgress ] . classList . contains ( "correct" ) ) {
157+ numCorrect -- ;
158+ } else {
159+ numErrors -- ;
160+ }
161+
162+ allCharacters [ characterProgress ] . classList . remove ( "correct" ) ;
163+ allCharacters [ characterProgress ] . classList . remove ( "error" ) ;
164+ allCharacters [ characterProgress ] . classList . add ( "active" ) ;
165+ }
166+
167+ if ( key . key . length > 1 ) return false ;
168+
169+ if ( allCharacters [ characterProgress ] . innerHTML === key . key ) {
170+ allCharacters [ characterProgress ] . classList . add ( "correct" ) ;
171+ numCorrect ++ ;
172+ } else {
173+ allCharacters [ characterProgress ] . classList . add ( "error" ) ;
174+ numErrors ++ ;
175+ }
176+
177+ allCharacters [ characterProgress ] . classList . remove ( "active" )
178+
179+ if ( allCharacters [ characterProgress ] . classList . contains ( "finalElement" ) ) {
180+ writing_done ( ) ;
181+ return false ;
182+ }
183+
184+ if ( allCharacters [ characterProgress ] . classList . contains ( "last" ) ) {
185+ var line = wordsDiv . childNodes [ 0 ] ;
186+ for ( let i = 0 ; i < line . childNodes . length ; i ++ ) {
187+ var j = allCharacters . indexOf ( line . childNodes [ i ] ) ;
188+ allCharacters . splice ( j , 1 ) ;
189+ }
190+
191+ wordsDiv . removeChild ( wordsDiv . childNodes [ 0 ] ) ;
192+ characterProgress = wordsDiv . childNodes [ 0 ] . childNodes . length ;
193+ allCharacters [ characterProgress ] . classList . add ( "active" ) ;
194+ return false ;
195+ }
196+
197+ characterProgress ++ ;
198+ allCharacters [ characterProgress ] . classList . add ( "active" )
199+ return false ;
200+ }
201+
202+ document . getElementById ( "words" ) . addEventListener ( "keydown" , onKeyDown_handler ) ;
203+
204+ document . getElementById ( "githubbutton" ) . addEventListener ( "click" , function ( ) {
205+ var link = document . getElementById ( "githubinput" ) . value ;
206+ if ( link . length === 0 ) {
207+ generateCode ( ) ;
208+ document . getElementById ( "selectDiv" ) . classList . add ( "displaynone" ) ;
209+ document . getElementById ( "writingDiv" ) . classList . remove ( "displaynone" ) ;
210+ return ;
211+ }
212+
213+ fetch ( link ) . then ( ( response ) => {
214+ response . text ( ) . then ( ( data ) => {
215+ myCode = data ;
216+ generateCode ( ) ;
217+ document . getElementById ( "selectDiv" ) . classList . add ( "displaynone" ) ;
218+ document . getElementById ( "writingDiv" ) . classList . remove ( "displaynone" ) ;
219+ } ) ;
220+ } ) ;
221+ } ) ;
222+ </ script >
223+
224+ < style >
225+
226+ body {
227+ background : # 323437 ;
228+ }
229+
230+ # timer {
231+ font-family : monospace;
232+ font-size : 48px ;
233+ color : # 646669 ;;
234+ }
235+
236+ # words {
237+ font-family : monospace;
238+ font-size : 24px ;
239+ overflow : hidden;
240+ height : 100px ;
241+ min-width : 800px ;
242+ margin : 0 auto;
243+ background : # 323437 ;
244+ padding : 15px ;
245+ position : absolute;
246+ top : 50% ;
247+ left : 50% ;
248+ -ms-transform : translateX (-50% ) translateY (-50% );
249+ -webkit-transform : translate (-50% , -50% );
250+ transform : translate (-50% , -50% );
251+ }
252+
253+ # resultDiv {
254+ font-family : monospace;
255+ font-size : 36px ;
256+ overflow : hidden;
257+ height : 100px ;
258+ color : # d1d0c5 ;
259+ margin : 0 auto;
260+ background : # 323437 ;
261+ padding : 15px ;
262+ position : absolute;
263+ top : 50% ;
264+ left : 50% ;
265+ -ms-transform : translateX (-50% ) translateY (-50% );
266+ -webkit-transform : translate (-50% , -50% );
267+ transform : translate (-50% , -50% );
268+ text-align : center;
269+ }
270+
271+ # selectDiv {
272+ font-family : monospace;
273+ font-size : 36px ;
274+ overflow : hidden;
275+ height : 250px ;
276+ color : # d1d0c5 ;
277+ margin : 0 auto;
278+ background : # 323437 ;
279+ padding : 15px ;
280+ position : absolute;
281+ top : 50% ;
282+ left : 50% ;
283+ -ms-transform : translateX (-50% ) translateY (-50% );
284+ -webkit-transform : translate (-50% , -50% );
285+ transform : translate (-50% , -50% );
286+ text-align : center;
287+ }
288+
289+ character {
290+ white-space : pre;
291+ color : # 646669 ;
292+ }
293+
294+ .correct {
295+ color : # d1d0c5 ;
296+ }
297+
298+ # timer .running {
299+ color : # d1d0c5 ;
300+ }
301+
302+ .error {
303+ color : # ca4754 ;
304+ }
305+
306+ .active {
307+ border-bottom : 2px dashed yellow;
308+ }
309+
310+ .displaynone {
311+ display : none;
312+ }
313+
314+ p .small {
315+ margin-top : -25px ;
316+ font-size : 14px ;
317+ }
318+
319+ button , input {
320+ outline : none;
321+ border : none;
322+ height : 25px ;
323+ }
324+
325+ </ style >
326+
327+ </ body >
328+
329+ </ html >
0 commit comments