Skip to content

Commit a24d823

Browse files
committed
feat: redesign testcase UI with icons and bordered containers
- Add VSCode Codicon-based SVG icons (Play, Edit, Trash, Skip, Stop, etc.) - Replace text action buttons with icon buttons in testcase header - Add elapsed time badge with status-based colors (blue/green/red/orange) - Replace arrow-based stdio rows with rounded bordered containers - Add expand button to open stdio content in editor - Add chevron toggle for visibility control - Update limits section with gear icon and cleaner styling - Add ORANGE_COLOR for CE status
1 parent 6720ae6 commit a24d823

File tree

3 files changed

+501
-163
lines changed

3 files changed

+501
-163
lines changed

src/webview/components.tsx

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,229 @@ export const GRAY_COLOR = "#52525C";
55
export const GREEN_COLOR = "#475B45";
66
export const RED_COLOR = "#6C4549";
77
export const BLUE_COLOR = "#4C6179";
8+
export const ORANGE_COLOR = "#7A5C3D";
9+
10+
// Icon component props
11+
interface IconProps {
12+
onClick?: () => unknown;
13+
className?: string;
14+
}
15+
16+
// VSCode Codicon-based SVG icons
17+
export const PlayIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
18+
<svg
19+
xmlns="http://www.w3.org/2000/svg"
20+
viewBox="0 0 16 16"
21+
fill="currentColor"
22+
class={`w-4 h-4 ${className ?? ""}`}
23+
onClick={onClick}
24+
>
25+
<title>Run</title>
26+
<path d="M3.78 2L3 2.41v12l.78.42 9-6.19V8.19l-9-6.19zM4 13.48V3.35l7.6 5.07L4 13.48z" />
27+
</svg>
28+
);
29+
30+
export const EditIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
31+
<svg
32+
xmlns="http://www.w3.org/2000/svg"
33+
viewBox="0 0 16 16"
34+
fill="currentColor"
35+
class={`w-4 h-4 ${className ?? ""}`}
36+
onClick={onClick}
37+
>
38+
<title>Edit</title>
39+
<path d="M13.23 1h-1.46L3.52 9.25l-.16.22L1 13.59 2.41 15l4.12-2.36.22-.16L15 4.23V2.77L13.23 1zM2.41 13.59l1.51-3 1.45 1.45-2.96 1.55zm3.83-2.06L4.47 9.76l8-8 1.77 1.77-8 8z" />
40+
</svg>
41+
);
42+
43+
export const TrashIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
44+
<svg
45+
xmlns="http://www.w3.org/2000/svg"
46+
viewBox="0 0 16 16"
47+
fill="currentColor"
48+
class={`w-4 h-4 ${className ?? ""}`}
49+
onClick={onClick}
50+
>
51+
<title>Delete</title>
52+
<path
53+
fillRule="evenodd"
54+
clipRule="evenodd"
55+
d="M10 3h3v1h-1v9l-1 1H4l-1-1V4H2V3h3V2a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v1zM9 2H6v1h3V2zM4 13h7V4H4v9zm2-8H5v7h1V5zm1 0h1v7H7V5zm2 0h1v7H9V5z"
56+
/>
57+
</svg>
58+
);
59+
60+
export const SkipIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
61+
<svg
62+
xmlns="http://www.w3.org/2000/svg"
63+
viewBox="0 0 16 16"
64+
fill="currentColor"
65+
class={`w-4 h-4 ${className ?? ""}`}
66+
onClick={onClick}
67+
>
68+
<title>Skip</title>
69+
<path d="M4.5 3H6v4.5l4-2.5v2.5l4-2.5v7l-4-2.5V12l-4-2.5V14H4.5V3z" />
70+
</svg>
71+
);
72+
73+
export const UnskipIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
74+
<svg
75+
xmlns="http://www.w3.org/2000/svg"
76+
viewBox="0 0 16 16"
77+
fill="currentColor"
78+
class={`w-4 h-4 ${className ?? ""}`}
79+
onClick={onClick}
80+
>
81+
<title>Unskip</title>
82+
<path d="M11.5 3H10v4.5l-4-2.5v2.5l-4-2.5v7l4-2.5V12l4-2.5V14h1.5V3z" />
83+
</svg>
84+
);
85+
86+
export const StopIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
87+
<svg
88+
xmlns="http://www.w3.org/2000/svg"
89+
viewBox="0 0 16 16"
90+
fill="currentColor"
91+
class={`w-4 h-4 ${className ?? ""}`}
92+
onClick={onClick}
93+
>
94+
<title>Stop</title>
95+
<path d="M2 2v12h12V2H2zm10.75 10.75H3.25V3.25h9.5v9.5z" />
96+
</svg>
97+
);
98+
99+
export const SaveIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
100+
<svg
101+
xmlns="http://www.w3.org/2000/svg"
102+
viewBox="0 0 16 16"
103+
fill="currentColor"
104+
class={`w-4 h-4 ${className ?? ""}`}
105+
onClick={onClick}
106+
>
107+
<title>Save</title>
108+
<path
109+
fillRule="evenodd"
110+
clipRule="evenodd"
111+
d="M13.353 1.146l1.5 1.5L15 2.793V14.5l-.5.5h-13l-.5-.5v-13l.5-.5h11.707l.146.146zM2 2v12h11V3.207L11.793 2H11v4H4V2H2zm8 0H5v3h5V2z"
112+
/>
113+
</svg>
114+
);
115+
116+
export const ChevronDownIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
117+
<svg
118+
xmlns="http://www.w3.org/2000/svg"
119+
viewBox="0 0 16 16"
120+
fill="currentColor"
121+
class={`w-4 h-4 ${className ?? ""}`}
122+
onClick={onClick}
123+
>
124+
<title>Expand</title>
125+
<path
126+
fillRule="evenodd"
127+
clipRule="evenodd"
128+
d="M7.976 10.072l4.357-4.357.62.618L8.284 11h-.618L3 6.333l.619-.618 4.357 4.357z"
129+
/>
130+
</svg>
131+
);
132+
133+
export const ChevronUpIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
134+
<svg
135+
xmlns="http://www.w3.org/2000/svg"
136+
viewBox="0 0 16 16"
137+
fill="currentColor"
138+
class={`w-4 h-4 ${className ?? ""}`}
139+
onClick={onClick}
140+
>
141+
<title>Collapse</title>
142+
<path
143+
fillRule="evenodd"
144+
clipRule="evenodd"
145+
d="M8.024 5.928l-4.357 4.357-.62-.618L7.716 5h.618L13 9.667l-.619.618-4.357-4.357z"
146+
/>
147+
</svg>
148+
);
149+
150+
export const ExpandIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
151+
<svg
152+
xmlns="http://www.w3.org/2000/svg"
153+
viewBox="0 0 16 16"
154+
fill="currentColor"
155+
class={`w-4 h-4 ${className ?? ""}`}
156+
onClick={onClick}
157+
>
158+
<title>Open in Editor</title>
159+
<path d="M1.5 1H6v1H2v12h12v-4h1v4.5l-.5.5h-13l-.5-.5v-13l.5-.5z" />
160+
<path d="M15 1.5V8h-1V2.707L7.243 9.465l-.707-.708L13.293 2H8V1h6.5l.5.5z" />
161+
</svg>
162+
);
163+
164+
export const GearIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
165+
<svg
166+
xmlns="http://www.w3.org/2000/svg"
167+
viewBox="0 0 16 16"
168+
fill="currentColor"
169+
class={`w-4 h-4 ${className ?? ""}`}
170+
onClick={onClick}
171+
>
172+
<title>Settings</title>
173+
<path
174+
fillRule="evenodd"
175+
clipRule="evenodd"
176+
d="M14 8c0-.388-.038-.768-.1-1.14l1.58-1.18-.5-1.73-1.94.23c-.28-.4-.6-.77-.96-1.1l.38-1.92-1.64-.87L9.54 1.9A5.953 5.953 0 0 0 8 1.75c-.52 0-1.03.06-1.52.17L5.14.32l-1.64.87.38 1.92c-.36.33-.68.7-.96 1.1l-1.94-.23-.5 1.73 1.58 1.18c-.06.37-.1.752-.1 1.14 0 .388.038.768.1 1.14l-1.58 1.18.5 1.73 1.94-.23c.28.4.6.77.96 1.1l-.38 1.92 1.64.87 1.34-1.58c.49.1 1 .16 1.52.16s1.03-.06 1.52-.16l1.34 1.58 1.64-.87-.38-1.92c.36-.33.68-.7.96-1.1l1.94.23.5-1.73-1.58-1.18c.06-.37.1-.752.1-1.14zM8 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"
177+
/>
178+
</svg>
179+
);
180+
181+
export const CheckIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
182+
<svg
183+
xmlns="http://www.w3.org/2000/svg"
184+
viewBox="0 0 16 16"
185+
fill="currentColor"
186+
class={`w-4 h-4 ${className ?? ""}`}
187+
onClick={onClick}
188+
>
189+
<title>Accept</title>
190+
<path
191+
fillRule="evenodd"
192+
clipRule="evenodd"
193+
d="M14.431 3.323l-8.47 10-.79-.036-3.35-4.77.818-.574 2.978 4.24 8.051-9.506.763.646z"
194+
/>
195+
</svg>
196+
);
197+
198+
export const CloseIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
199+
<svg
200+
xmlns="http://www.w3.org/2000/svg"
201+
viewBox="0 0 16 16"
202+
fill="currentColor"
203+
class={`w-4 h-4 ${className ?? ""}`}
204+
onClick={onClick}
205+
>
206+
<title>Decline</title>
207+
<path
208+
fillRule="evenodd"
209+
clipRule="evenodd"
210+
d="M8 8.707l3.646 3.647.708-.707L8.707 8l3.647-3.646-.707-.708L8 7.293 4.354 3.646l-.707.708L7.293 8l-3.646 3.646.707.708L8 8.707z"
211+
/>
212+
</svg>
213+
);
214+
215+
export const DiffIcon: FunctionComponent<IconProps> = ({ onClick, className }) => (
216+
<svg
217+
xmlns="http://www.w3.org/2000/svg"
218+
viewBox="0 0 16 16"
219+
fill="currentColor"
220+
class={`w-4 h-4 ${className ?? ""}`}
221+
onClick={onClick}
222+
>
223+
<title>Compare</title>
224+
<path
225+
fillRule="evenodd"
226+
clipRule="evenodd"
227+
d="M2 3.5l.5-.5h5l.5.5v9l-.5.5h-5l-.5-.5v-9zM3 12h4V4H3v8zm6.5-8.5l.5-.5h5l.5.5v9l-.5.5h-5l-.5-.5v-9zm1 .5v8h4V4h-4zM4 6h2v1H4V6zm0 2h2v1H4V8zm6-2h2v1h-2V6z"
228+
/>
229+
</svg>
230+
);
8231

9232
interface ArrowSvgPropsGeneric extends ArrowSvgProps {
10233
d: string;

src/webview/judge/App.tsx

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import * as v from "valibot";
44

55
import { Status, Stdio } from "~shared/enums";
66
import { TestcaseSchema } from "~shared/schemas";
7-
import { BLUE_COLOR } from "~webview/components";
7+
import { BLUE_COLOR, GearIcon } from "~webview/components";
88
import { observable } from "~external/observable";
99
import {
1010
DeleteMessageSchema,
@@ -165,25 +165,20 @@ export default function App() {
165165
next test
166166
</button>
167167
</div>
168-
<div class="m-6 flex gap-x-2 items-center my-3 bg-zinc-800">
169-
<button
170-
type="button"
171-
class="text-base leading-tight px-3 w-fit display-font"
172-
style={{ backgroundColor: BLUE_COLOR }}
173-
>
174-
time limit
175-
</button>
168+
<div class="m-6 flex gap-x-2 items-center my-3">
169+
<GearIcon className="shrink-0" />
170+
<span class="text-xs text-zinc-400 uppercase tracking-wide shrink-0">limits</span>
176171
<input
177172
type="number"
178-
class="appearance-none bg-transparent border-none focus:outline-none text-base leading-tight display-font w-fit"
173+
class="appearance-none bg-transparent border-b border-zinc-600 focus:border-zinc-400 focus:outline-none text-base leading-tight display-font w-20"
179174
value={newTimeLimit.value}
180175
onInput={handleTimeLimitInput}
181176
onKeyUp={handleTimeLimitKeyUp}
182177
/>
183-
<span class="text-base leading-tight display-font w-fit">ms</span>
178+
<span class="text-base leading-tight display-font shrink-0">ms</span>
184179
<button
185180
type="button"
186-
class="text-base leading-tight px-3 w-fit display-font"
181+
class="text-base leading-tight px-3 py-0.5 rounded display-font hover:opacity-80 transition-opacity"
187182
style={{ backgroundColor: BLUE_COLOR }}
188183
onClick={submitTimeLimit}
189184
>

0 commit comments

Comments
 (0)