Skip to content

Commit 2c1b9cf

Browse files
committed
대회 문제 풀이 페이지 내 남은 시간 표시하도록 수정 및 문제 풀이 결과 목록 표시 및 바로 이동 가능하도록 개선
1 parent def2d65 commit 2c1b9cf

10 files changed

Lines changed: 1057 additions & 155 deletions

File tree

frontend/src/pages/oj/components/NavBar.vue

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -182,15 +182,58 @@ export default {
182182
this._menuEl = menuEl
183183
this._onMenuOver = this.handleMenuMouseOver.bind(this)
184184
this._onMenuLeave = this.handleMenuMouseLeave.bind(this)
185-
this._onWindowResize = () => this.moveIndicatorToActive()
185+
this._onWindowResize = () => this.scheduleIndicatorUpdate()
186+
this._onWindowLoad = () => this.scheduleIndicatorUpdate()
186187
menuEl.addEventListener("mouseover", this._onMenuOver)
187188
menuEl.addEventListener("mouseleave", this._onMenuLeave)
188189
window.addEventListener("resize", this._onWindowResize)
189-
this.moveIndicatorToActive()
190-
requestAnimationFrame(() => {
190+
window.addEventListener("load", this._onWindowLoad)
191+
this.observeIndicatorLayout(menuEl)
192+
this.scheduleIndicatorUpdate()
193+
this.showIndicatorWhenLayoutReady()
194+
},
195+
observeIndicatorLayout(menuEl) {
196+
if (window.ResizeObserver) {
197+
this._indicatorResizeObserver = new ResizeObserver(() => {
198+
this.scheduleIndicatorUpdate()
199+
})
200+
this._indicatorResizeObserver.observe(menuEl)
201+
this._indicatorResizeObserver.observe(this.$el)
202+
}
203+
204+
this._indicatorImages = Array.prototype.slice.call(
205+
menuEl.querySelectorAll("img"),
206+
)
207+
this._onIndicatorAssetLoad = () => this.scheduleIndicatorUpdate()
208+
this._indicatorImages.forEach((img) => {
209+
if (img.complete) return
210+
img.addEventListener("load", this._onIndicatorAssetLoad)
211+
img.addEventListener("error", this._onIndicatorAssetLoad)
212+
})
213+
},
214+
showIndicatorWhenLayoutReady() {
215+
const showIndicator = () => {
216+
this.scheduleIndicatorUpdate()
191217
requestAnimationFrame(() => {
192-
this.indicatorReady = true
218+
requestAnimationFrame(() => {
219+
if (this._menuEl) this.indicatorReady = true
220+
})
193221
})
222+
}
223+
224+
if (document.fonts && document.fonts.ready) {
225+
document.fonts.ready.then(showIndicator)
226+
} else {
227+
showIndicator()
228+
}
229+
},
230+
scheduleIndicatorUpdate() {
231+
if (this._indicatorFrame) {
232+
cancelAnimationFrame(this._indicatorFrame)
233+
}
234+
this._indicatorFrame = requestAnimationFrame(() => {
235+
this._indicatorFrame = null
236+
this.moveIndicatorToActive()
194237
})
195238
},
196239
teardownIndicator() {
@@ -201,10 +244,30 @@ export default {
201244
if (this._onWindowResize) {
202245
window.removeEventListener("resize", this._onWindowResize)
203246
}
247+
if (this._onWindowLoad) {
248+
window.removeEventListener("load", this._onWindowLoad)
249+
}
250+
if (this._indicatorResizeObserver) {
251+
this._indicatorResizeObserver.disconnect()
252+
}
253+
if (this._indicatorImages && this._onIndicatorAssetLoad) {
254+
this._indicatorImages.forEach((img) => {
255+
img.removeEventListener("load", this._onIndicatorAssetLoad)
256+
img.removeEventListener("error", this._onIndicatorAssetLoad)
257+
})
258+
}
259+
if (this._indicatorFrame) {
260+
cancelAnimationFrame(this._indicatorFrame)
261+
}
204262
this._menuEl = null
205263
this._onMenuOver = null
206264
this._onMenuLeave = null
207265
this._onWindowResize = null
266+
this._onWindowLoad = null
267+
this._indicatorResizeObserver = null
268+
this._indicatorImages = null
269+
this._onIndicatorAssetLoad = null
270+
this._indicatorFrame = null
208271
},
209272
handleMenuMouseOver(e) {
210273
if (!this._menuEl) return
@@ -267,7 +330,7 @@ export default {
267330
},
268331
watch: {
269332
activeMenu() {
270-
this.$nextTick(() => this.moveIndicatorToActive())
333+
this.$nextTick(() => this.scheduleIndicatorUpdate())
271334
},
272335
},
273336
}

frontend/src/pages/oj/views/contest/children/Overview.vue

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,15 @@
33
<div class="contestTitle">
44
<p>{{ contest.title }}</p>
55
<div slot="extra" style="flex-shrink: 0">
6-
<Tag type="dot" :color="countdownColor">
7-
<span id="countdown">{{ countdown }}</span>
8-
</Tag>
6+
<div class="contest-timer" :class="'contest-timer--' + (countdownParts ? countdownParts.status : 'ended')" v-if="countdown">
7+
<template v-if="countdownParts && countdownParts.status !== 'ended'">
8+
<span class="contest-timer__label">{{ countdownParts.status === 'running' ? '남은 시간' : '시작까지' }}</span>
9+
<span class="contest-timer__text">{{ formattedTime }}</span>
10+
</template>
11+
<template v-else>
12+
<span class="contest-timer__label">대회 종료</span>
13+
</template>
14+
</div>
915
</div>
1016
</div>
1117
<div class="contestContent">
@@ -80,12 +86,25 @@ export default {
8086
...mapState({
8187
contest: (state) => state.contest.contest,
8288
}),
83-
...mapGetters(["contestStatus", "countdown", "passwordFormVisible"]),
89+
...mapGetters(["contestStatus", "countdown", "countdownParts", "passwordFormVisible"]),
8490
countdownColor() {
8591
if (this.contestStatus) {
8692
return CONTEST_STATUS_REVERSE[this.contestStatus].color
8793
}
8894
},
95+
formattedTime() {
96+
if (!this.countdownParts || this.countdownParts.status === 'ended') {
97+
return ''
98+
}
99+
const pad = (num) => String(num).padStart(2, '0')
100+
const { days, hours, minutes, seconds } = this.countdownParts
101+
102+
let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`
103+
if (days > 0) {
104+
timeStr = `${days}${timeStr}`
105+
}
106+
return timeStr
107+
},
89108
},
90109
}
91110
</script>
@@ -136,4 +155,28 @@ export default {
136155
padding: 2px 7px;
137156
}
138157
}
158+
159+
/* ── Contest Timer ── */
160+
.contest-timer {
161+
display: flex;
162+
align-items: center;
163+
gap: 6px;
164+
font-size: 16px;
165+
color: var(--text-color);
166+
}
167+
168+
.contest-timer__label {
169+
font-weight: 500;
170+
color: #64748b;
171+
}
172+
173+
.contest-timer__text {
174+
font-weight: 600;
175+
font-variant-numeric: tabular-nums;
176+
color: var(--text-color);
177+
}
178+
179+
:root.dark .contest-timer__label {
180+
color: #94a3b8;
181+
}
139182
</style>

0 commit comments

Comments
 (0)