Skip to content

Commit a2542dc

Browse files
100glelaike9m
andauthored
feat: improve challenge pages UI (laike9m#16)
* feat: improve challenge pages ui and format html content * feat: add pico css and adjust template directory structure * refactor: Refactor pages to use jinja's Inheritance template * feat: Add button spinner to run button * refactor: Add sidebar with challenge list and dark mode toggle * Add header to challenge area * chore: simplify class name for challenge header * Update challenge template layout and styles * Add data-theme attribute to HTML tag in base.html template. * Add badge component to challenge header to show python version --------- Co-authored-by: laike9m <[email protected]>
1 parent 53e9f57 commit a2542dc

File tree

7 files changed

+341
-85
lines changed

7 files changed

+341
-85
lines changed

templates/base.html

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<!DOCTYPE html>
2+
<html lang="en" data-theme="light">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@1/css/pico.min.css">
8+
{% block head %}{% endblock %}
9+
</head>
10+
11+
<body>
12+
{% include 'components/github_ribbon.html' %}
13+
{% block content %}{% endblock %}
14+
</body>
15+
16+
</html>

templates/challenge.html

+175-65
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,138 @@
1-
<head>
2-
<meta name="viewport" content="width=device-width, initial-scale=1" />
3-
<link
4-
rel="stylesheet"
5-
href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css"
6-
/>
1+
{% extends "base.html" %}
2+
3+
{% block head %}
4+
{{ super() }}
5+
6+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.css" />
77
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/codemirror.min.js"></script>
88
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.15/mode/python/python.min.js"></script>
9-
<style>
10-
html {
11-
font-size: 1.2rem;
9+
<title>Python Type Challenge - {{ name }}</title>
10+
11+
<style type="text/css">
12+
.challenge-container {
13+
display: flex;
14+
padding: 1rem 2rem;
15+
justify-content: center;
16+
max-height: 100vh;
17+
width: max(80vw, 80%);
18+
}
19+
20+
/* Sidebar Area */
21+
.sidebar-container {
22+
display: flex;
23+
flex-direction: column;
24+
align-items: center;
25+
margin-right: 2rem;
26+
}
27+
28+
.sidebar-container .sidebar-challenge-list {
29+
overflow-y: auto;
30+
max-width: fit-content;
31+
min-width: 100px;
1232
}
1333

14-
.container {
34+
.sidebar-container .sidebar-actions {
1535
display: flex;
16-
flex-direction: row;
1736
justify-content: center;
18-
margin: 0 auto;
37+
width: 100%;
38+
align-items: center;
39+
margin-top: 8px;
40+
padding-top: 8px;
41+
border-top: 1px solid hsl(205, 20%, 94%);
1942
}
2043

21-
.navigation-container {
22-
padding: 10px;
23-
flex: 1;
44+
/* Challenge Area */
45+
.challenge-area {
46+
display: flex;
47+
flex-direction: column;
2448
}
2549

26-
.codemirror-container {
27-
padding: 10px;
50+
.challenge-header__title {
51+
display: flex;
52+
align-items: center;
53+
margin-bottom: 1rem;
54+
}
55+
56+
.challenge-header__title > span:nth-child(1) {
57+
font-size: 30px;
58+
font-weight: bold;
59+
margin-right: 20px;
60+
}
61+
62+
.challenge-main {
63+
display: flex;
64+
justify-content: space-between;
65+
gap: 24px;
66+
}
67+
68+
/* Code Editor Area */
69+
.challenge-main .codemirror-container {
2870
display: flex;
2971
flex-direction: column;
30-
flex: 4;
72+
gap: 16px;
73+
width: 50%;
3174
}
3275

33-
.result-container {
34-
flex: 4;
76+
.codemirror-container #run-button {
77+
align-self: flex-end;
78+
width: 100px;
79+
padding: 2px 4px;
80+
border-radius: 4px;
81+
}
82+
83+
.tests-result-container {
3584
display: flex;
36-
justify-content: start;
37-
margin-left: 10px;
85+
flex-direction: column;
86+
justify-content: space-between;
87+
width: 50%;
88+
gap: 16px;
89+
}
90+
91+
.test-result-container #tests {
92+
width: 50%;
3893
}
3994

4095
/* 当视口宽度小于 800px 时,更改布局为单列 */
4196
@media only screen and (max-width: 800px) {
42-
.container {
97+
.challenge-container {
4398
flex-direction: column;
99+
align-items: center;
100+
margin-top: 20px;
101+
height: 100%;
102+
width: 100%;
103+
}
104+
105+
.sidebar-container {
106+
overflow-y: auto;
107+
margin-right: 0;
108+
margin-bottom: 2rem;
109+
min-height: 100px;
110+
}
111+
112+
.sidebar-container .sidebar-challenge-list {
113+
height: 100%;
114+
}
115+
116+
.challenge-header__title {
117+
justify-content: space-between;
118+
font-size: 14px;
119+
}
120+
121+
.challenge-header__title > span:nth-child(1) {
122+
margin-right: auto;
123+
font-size: 20px;
124+
}
125+
126+
.challenge-main {
127+
flex-direction: column;
128+
justify-content: center;
129+
align-items: center;
130+
gap: 0;
131+
}
132+
133+
.challenge-main .codemirror-container,
134+
.challenge-main .tests-result-container {
135+
width: 100%;
44136
}
45137
}
46138

@@ -53,67 +145,80 @@
53145
border: 2px solid blue;
54146
}
55147

56-
.code {
57-
background-color: #ffffcc;
58-
}
59148
</style>
60-
</head>
61-
62-
<body>
63-
{% include 'github_ribbon.html' %}
64-
<div class="container">
65-
<div class="navigation-container">{% include 'challenge_list.html' %}</div>
66-
<div class="codemirror-container">
67-
<div
68-
style="
69-
display: flex;
70-
justify-content: space-between;
71-
align-items: center;
72-
"
73-
>
74-
<h2>Challenge - {{name}}</h2>
75-
<p>Python version: {{ python_info }}</p>
149+
{% endblock %}
150+
151+
{% block content %}
152+
<div class="challenge-container container-fluid">
153+
<!-- Left/Top Sidebar -->
154+
<div class="sidebar-container">
155+
<div class="sidebar-challenge-list">
156+
{% include 'components/challenge_list.html' %}
76157
</div>
77-
<p style="margin-top: 0; line-height: 1.5">
158+
<div class="sidebar-actions">
159+
{% include 'components/darkmode.html' %}
160+
</div>
161+
</div>
162+
163+
<!-- Right/Bottom Challenge Area-->
164+
<div class="challenge-area">
165+
<!-- Challenge Header Area-->
166+
<div class="challenge-header">
167+
<div class="challenge-header__title">
168+
<span>Challenge - {{ name }}</span>
169+
{%include 'components/badge.html' with context %}
170+
</div>
171+
<p class="challenge-header__exerpt" style="margin-top: 0; line-height: 1.5">
78172
Complete code following the instructions below, so that there are no type errors
79173
throughout the test code except for those lines followed by a <code># expect-type-error</code> comment.
80174
You don't need to implement the function, just add type annotations.
81175
Hit the "Run" button to see result.
82176
</p>
83-
<div id="editor"></div>
84-
<div style="display: flex; justify-content: flex-end">
85-
<button id="run-button" style="width: 100px; margin-top: 10px">
177+
</div>
178+
179+
<div class="challenge-main">
180+
<!-- Code Editor Area -->
181+
<div class="codemirror-container">
182+
<div id="editor"></div>
183+
<button id="run-button">
86184
▶️ Run
87185
</button>
186+
</div>
187+
<!-- Test Cases and Result Area -->
188+
<div class="tests-result-container">
189+
<div id="tests"></div>
190+
<div id="result" style="white-space: pre-line"></div>
191+
</div>
88192
</div>
89-
<p style="margin-top: 0">Test cases</p>
90-
<div id="tests"></div>
91-
</div>
92-
<div class="result-container">
93-
<p id="result" style="white-space: pre-line"></p>
94193
</div>
95194
</div>
195+
96196
<script type="text/javascript">
97-
let code_under_test = {{code_under_test | tojson}};
98-
console.log(code_under_test);
99-
var myCodeMirror = CodeMirror(document.getElementById("editor"), {
100-
value: code_under_test,
197+
let codeMirrorShared = {
101198
mode: "python",
102199
lineWrapping: true,
103200
lineNumbers: true,
104-
indentUnit: 4
201+
}
202+
let code_under_test = {{ code_under_test | tojson }};
203+
let myCodeMirror = CodeMirror(document.getElementById("editor"), {
204+
value: code_under_test,
205+
...codeMirrorShared,
105206
});
106-
let test_code = {{test_code | tojson}};
207+
let test_code = {{ test_code | tojson }};
107208
CodeMirror(document.getElementById("tests"), {
108209
value: test_code,
109-
mode: "python",
110-
lineWrapping: true,
111-
lineNumbers: true,
112-
readOnly: "nocursor"
210+
readOnly: "nocursor",
211+
...codeMirrorShared,
113212
});
114213

115-
document.getElementById('run-button').onclick = function () {
116-
var code = myCodeMirror.getValue();
214+
let runButton = document.getElementById('run-button')
215+
runButton.onclick = function () {
216+
// set button spinner
217+
let rawInnerText = runButton.innerText;
218+
runButton.ariaBusy = true;
219+
runButton.innerText = ""
220+
221+
let code = myCodeMirror.getValue();
117222
fetch('/run/{{name}}', {
118223
method: 'POST',
119224
body: code
@@ -122,12 +227,17 @@ <h2>Challenge - {{name}}</h2>
122227
.then(result => document.getElementById("result").innerHTML = result)
123228
.catch((error) => {
124229
console.error('Error:', error);
230+
})
231+
.finally(() => {
232+
// reset button spinner
233+
runButton.ariaBusy = false;
234+
runButton.innerText = rawInnerText;
125235
});
126236
};
127237

128238
// If window size is > 800, expand the challenge list.
129239
function checkWidth() {
130-
var detailsElement = document.getElementById('challenge-list');
240+
let detailsElement = document.getElementById('challenge-list');
131241
if (window.innerWidth < 800) {
132242
detailsElement.removeAttribute('open');
133243
} else {
@@ -137,4 +247,4 @@ <h2>Challenge - {{name}}</h2>
137247
checkWidth(); // Check the width initially
138248
window.addEventListener('resize', checkWidth);
139249
</script>
140-
</body>
250+
{% endblock %}

templates/components/badge.html

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
2+
<head>
3+
<style type="text/css">
4+
.badge {
5+
display: flex;
6+
align-items: center;
7+
justify-content: center;
8+
padding: 2px 4px;
9+
height: 30px;
10+
border-radius: 5px;
11+
background-color: #6cb8d4;
12+
color: white;
13+
font-size: 15px;
14+
font-weight: bold;
15+
}
16+
17+
.badge span:nth-child(1) svg {
18+
width: 16px;
19+
height: 16px;
20+
margin-right: 5px;
21+
}
22+
23+
.badge span:nth-child(2) {
24+
font-size: 14px;
25+
font-weight: lighter;
26+
}
27+
</style>
28+
</head>
29+
30+
31+
<div class="badge">
32+
<span>
33+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 256 255"><defs><linearGradient id="logosPython0" x1="12.959%" x2="79.639%" y1="12.039%" y2="78.201%"><stop offset="0%" stop-color="#387EB8"/><stop offset="100%" stop-color="#366994"/></linearGradient><linearGradient id="logosPython1" x1="19.128%" x2="90.742%" y1="20.579%" y2="88.429%"><stop offset="0%" stop-color="#FFE052"/><stop offset="100%" stop-color="#FFC331"/></linearGradient></defs><path fill="url(#logosPython0)" d="M126.916.072c-64.832 0-60.784 28.115-60.784 28.115l.072 29.128h61.868v8.745H41.631S.145 61.355.145 126.77c0 65.417 36.21 63.097 36.21 63.097h21.61v-30.356s-1.165-36.21 35.632-36.21h61.362s34.475.557 34.475-33.319V33.97S194.67.072 126.916.072ZM92.802 19.66a11.12 11.12 0 0 1 11.13 11.13a11.12 11.12 0 0 1-11.13 11.13a11.12 11.12 0 0 1-11.13-11.13a11.12 11.12 0 0 1 11.13-11.13Z"/><path fill="url(#logosPython1)" d="M128.757 254.126c64.832 0 60.784-28.115 60.784-28.115l-.072-29.127H127.6v-8.745h86.441s41.486 4.705 41.486-60.712c0-65.416-36.21-63.096-36.21-63.096h-21.61v30.355s1.165 36.21-35.632 36.21h-61.362s-34.475-.557-34.475 33.32v56.013s-5.235 33.897 62.518 33.897Zm34.114-19.586a11.12 11.12 0 0 1-11.13-11.13a11.12 11.12 0 0 1 11.13-11.131a11.12 11.12 0 0 1 11.13 11.13a11.12 11.12 0 0 1-11.13 11.13Z"/></svg>
34+
</span>
35+
<span>{{ python_info }}</span>
36+
</div>

0 commit comments

Comments
 (0)