Skip to content

Commit

Permalink
feat(frontend): add problem status display (#86)
Browse files Browse the repository at this point in the history
  • Loading branch information
thezzisu authored Mar 29, 2024
1 parent da9bf41 commit 9ddd6be
Show file tree
Hide file tree
Showing 13 changed files with 193 additions and 81 deletions.
2 changes: 2 additions & 0 deletions .yarn/versions/299429b2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
declined:
- "@aoi-js/frontend"
22 changes: 14 additions & 8 deletions apps/frontend/src/components/app/AppList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
item-value="_id"
@update:options="({ page, itemsPerPage }) => apps.execute(0, page, itemsPerPage)"
>
<template v-slot:[`item.slug`]="{ item }">
<code>{{ item.slug }}</code>
</template>
<template v-slot:[`item.title`]="{ item }">
<AccessLevelBadge :access-level="item.accessLevel" inline />
<RouterLink :to="`/org/${orgId}/app/${item._id}`">
{{ item.title }}
<RouterLink :to="`/org/${orgId}/contest/${item._id}`">
<div>
<div>
{{ item.title }}
</div>
<div class="u-text-xs text-secondary">
<code>{{ item.slug }}</code>
</div>
</div>
</RouterLink>
</template>
<template v-slot:[`item.tags`]="{ item }">
Expand All @@ -30,6 +33,9 @@
</VChip>
</VChipGroup>
</template>
<template v-slot:[`item.accessLevel`]="{ item }">
<AccessLevelBadge variant="chip" :access-level="item.accessLevel" inline />
</template>
</VDataTableServer>
</template>

Expand All @@ -55,9 +61,9 @@ const { t } = useI18n()
withTitle(computed(() => t('pages.apps')))
const headers = [
{ title: t('term.slug'), key: 'slug', align: 'start', sortable: false },
{ title: t('term.name'), key: 'title', sortable: false },
{ title: t('term.tags'), key: 'tags', sortable: false }
{ title: t('term.tags'), key: 'tags', sortable: false },
{ title: t('term.access-level'), key: 'accessLevel', align: 'end', sortable: false }
] as const
const {
Expand Down
20 changes: 13 additions & 7 deletions apps/frontend/src/components/contest/ContestList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
item-value="_id"
@update:options="({ page, itemsPerPage }) => contests.execute(0, page, itemsPerPage)"
>
<template v-slot:[`item.slug`]="{ item }">
<code>{{ item.slug }}</code>
</template>
<template v-slot:[`item.title`]="{ item }">
<AccessLevelBadge :access-level="item.accessLevel" inline />
<RouterLink :to="`/org/${orgId}/contest/${item._id}`">
{{ item.title }}
<div>
<div>
{{ item.title }}
</div>
<div class="u-text-xs text-secondary">
<code>{{ item.slug }}</code>
</div>
</div>
</RouterLink>
</template>
<template v-slot:[`item.count`]="{ item }">
Expand Down Expand Up @@ -48,6 +51,9 @@
</VChip>
</VChipGroup>
</template>
<template v-slot:[`item.accessLevel`]="{ item }">
<AccessLevelBadge variant="chip" :access-level="item.accessLevel" inline />
</template>
</VDataTableServer>
</template>

Expand All @@ -74,12 +80,12 @@ const now = +new Date()
withTitle(computed(() => t('pages.contests')))
const headers = [
{ title: t('term.slug'), key: 'slug', align: 'start', sortable: false },
{ title: t('term.name'), key: 'title', sortable: false },
{ title: t('term.participant-count'), key: 'count', sortable: false },
{ title: t('term.contest-time'), key: 'time', sortable: false },
{ title: t('term.contest-stage'), key: 'stage', sortable: false },
{ title: t('term.tags'), key: 'tags', sortable: false }
{ title: t('term.tags'), key: 'tags', sortable: false },
{ title: t('term.access-level'), key: 'accessLevel', align: 'end', sortable: false }
] as const
const {
Expand Down
22 changes: 14 additions & 8 deletions apps/frontend/src/components/plan/PlanList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
item-value="_id"
@update:options="({ page, itemsPerPage }) => plans.execute(0, page, itemsPerPage)"
>
<template v-slot:[`item.slug`]="{ item }">
<code>{{ item.slug }}</code>
</template>
<template v-slot:[`item.title`]="{ item }">
<AccessLevelBadge :access-level="item.accessLevel" inline />
<RouterLink :to="`/org/${orgId}/plan/${item._id}`">
{{ item.title }}
<RouterLink :to="`/org/${orgId}/contest/${item._id}`">
<div>
<div>
{{ item.title }}
</div>
<div class="u-text-xs text-secondary">
<code>{{ item.slug }}</code>
</div>
</div>
</RouterLink>
</template>
<template v-slot:[`item.tags`]="{ item }">
Expand All @@ -30,6 +33,9 @@
</VChip>
</VChipGroup>
</template>
<template v-slot:[`item.accessLevel`]="{ item }">
<AccessLevelBadge variant="chip" :access-level="item.accessLevel" inline />
</template>
</VDataTableServer>
</template>

Expand All @@ -55,9 +61,9 @@ const { t } = useI18n()
withTitle(computed(() => t('pages.plans')))
const headers = [
{ title: t('term.slug'), key: 'slug', align: 'start', sortable: false },
{ title: t('term.name'), key: 'title', sortable: false },
{ title: t('term.tags'), key: 'tags', sortable: false }
{ title: t('term.tags'), key: 'tags', sortable: false },
{ title: t('term.access-level'), key: 'accessLevel', align: 'end', sortable: false }
] as const
const {
Expand Down
28 changes: 19 additions & 9 deletions apps/frontend/src/components/problem/ProblemList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,23 @@
item-value="_id"
@update:options="({ page, itemsPerPage }) => problems.execute(0, page, itemsPerPage)"
>
<template v-slot:[`item.slug`]="{ item }">
<code>{{ item.slug }}</code>
</template>
<template v-slot:[`item.title`]="{ item }">
<AccessLevelBadge :access-level="item.accessLevel" inline />
<RouterLink :to="`/org/${orgId}/problem/${item._id}`">
{{ item.title }}
</RouterLink>
<div class="u-flex u-items-center u-gap-2">
<RouterLink :to="`/org/${orgId}/problem/${item._id}`" class="u-flex u-gap-2">
<div>
<div>
{{ item.title }}
</div>
<div class="u-text-xs text-secondary">
<code>{{ item.slug }}</code>
</div>
</div>
</RouterLink>
<ProblemStatus :org-id="orgId" :problem-id="item._id" :status="item.status" />
</div>
</template>
<template v-slot:[`item.accessLevel`]="{ item }">
<AccessLevelBadge variant="chip" :access-level="item.accessLevel" inline />
</template>
<template v-slot:[`item.tags`]="{ item }">
<ProblemTagGroup :tags="item.tags" :url-prefix="`/org/${orgId}/problem/tag`" />
Expand All @@ -31,6 +40,7 @@ import { useI18n } from 'vue-i18n'
import AccessLevelBadge from '../utils/AccessLevelBadge.vue'
import ProblemStatus from './ProblemStatus.vue'
import ProblemTagGroup from './ProblemTagGroup.vue'
import type { IProblemDTO } from './types'
Expand All @@ -48,9 +58,9 @@ const { t } = useI18n()
withTitle(computed(() => t('pages.problems')))
const headers = [
{ title: t('term.slug'), key: 'slug', align: 'start', sortable: false },
{ title: t('term.name'), key: 'title', sortable: false },
{ title: t('term.tags'), key: 'tags', sortable: false }
{ title: t('term.tags'), key: 'tags', sortable: false },
{ title: t('term.access-level'), key: 'accessLevel', align: 'end', sortable: false }
] as const
const {
Expand Down
22 changes: 22 additions & 0 deletions apps/frontend/src/components/problem/ProblemStatus.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<template>
<div v-if="status" class="u-flex u-gap-2 u-items-center">
<SolutionStatusChip
:status="status.lastSolutionStatus"
abbrev
:score="status.lastSolutionScore"
:to="`/org/${orgId}/problem/${problemId}/solution/${status.lastSolutionId}`"
/>
</div>
</template>

<script setup lang="ts">
import SolutionStatusChip from '../solution/SolutionStatusChip.vue'
import type { IProblemStatusDTO } from './types'
defineProps<{
orgId: string
problemId: string
status?: IProblemStatusDTO
}>()
</script>
5 changes: 4 additions & 1 deletion apps/frontend/src/components/problem/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import type { ProblemConfig } from '@aoi-js/common'
import type { IProblemSettings } from '@aoi-js/server'
import type { IProblemSettings, IProblemStatus } from '@aoi-js/server'

export interface IProblemStatusDTO extends Omit<IProblemStatus, '_id' | 'problemId' | 'userId'> {}

export interface IProblemDTO {
_id: string
Expand All @@ -13,4 +15,5 @@ export interface IProblemDTO {
currentDataHash: string
config: ProblemConfig
settings: IProblemSettings
status?: IProblemStatusDTO
}
33 changes: 33 additions & 0 deletions apps/frontend/src/components/solution/SolutionStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { computed } from 'vue'

export interface ISolutionStatusProps {
status: string
to?: string
abbrev?: boolean
score?: number
}

export function useSolutionStatus(props: ISolutionStatusProps) {
const knownStatus: Record<string, [string, string]> = {
Accepted: ['mdi-check', 'success'],
Success: ['mdi-check', 'info'],
'Memory Limit Exceeded': ['mdi-database-alert-outline', 'error'],
'Time Limit Exceeded': ['mdi-timer-alert-outline', 'error'],
'Wrong Answer': ['mdi-close', 'error'],
'Compile Error': ['mdi-code-braces', 'error'],
'Internal Error': ['mdi-help-circle-outline', ''],
'Runtime Error': ['mdi-alert-decagram-outline', 'error'],
Running: ['mdi-play', 'indigo'],
Queued: ['mdi-timer-sand', 'indigo']
}
const display = computed(() => knownStatus[props.status] ?? ['mdi-circle-outline', 'warning'])
const status = computed(() => {
if (!props.abbrev) return props.status
const abbrev = props.status
.split(' ')
.map((word) => word[0].toUpperCase())
.join('')
return abbrev.length > 1 ? abbrev : props.status.slice(0, 2).toUpperCase()
})
return { display, status }
}
35 changes: 12 additions & 23 deletions apps/frontend/src/components/solution/SolutionStatusChip.vue
Original file line number Diff line number Diff line change
@@ -1,34 +1,23 @@
<template>
<VChip
:text="status"
:prepend-icon="display[0]"
:color="display[1]"
variant="outlined"
:variant="abbrev ? 'text' : 'outlined'"
:to="to"
v-if="to"
/>
<VChip :text="status" :prepend-icon="display[0]" :color="display[1]" variant="outlined" v-else />
>
{{ status }}
<span v-if="score !== undefined">
(<code :style="{ color: palette(score) }" v-text="score" />)
</span>
</VChip>
</template>

<script setup lang="ts">
import { computed } from 'vue'
import { useSolutionStatus, type ISolutionStatusProps } from './SolutionStatus'
const props = defineProps<{
status: string
to?: string
}>()
import { palette } from '@/utils/colors'
const knownStatus: Record<string, [string, string]> = {
Accepted: ['mdi-check', 'success'],
Success: ['mdi-check', 'info'],
'Memory Limit Exceeded': ['mdi-database-alert-outline', 'error'],
'Time Limit Exceeded': ['mdi-timer-alert-outline', 'error'],
'Wrong Answer': ['mdi-close', 'error'],
'Compile Error': ['mdi-code-braces', 'error'],
'Internal Error': ['mdi-help-circle-outline', ''],
'Runtime Error': ['mdi-alert-decagram-outline', 'error'],
Running: ['mdi-play', 'indigo'],
Queued: ['mdi-timer-sand', 'indigo']
}
const display = computed(() => knownStatus[props.status] ?? ['mdi-circle-outline', 'warning'])
const props = defineProps<ISolutionStatusProps>()
const { display, status } = useSolutionStatus(props)
</script>
19 changes: 17 additions & 2 deletions apps/frontend/src/components/utils/AccessLevelBadge.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
<template>
<VBadge :icon="icons[accessLevel]" :color="colors[accessLevel]" v-bind="$attrs">
<VChip
v-if="variant === 'chip'"
variant="tonal"
rounded
:prepend-icon="icons[accessLevel]"
:color="colors[accessLevel]"
v-bind="$attrs"
>
{{ t(`access-level.${names[accessLevel]}`) }}
</VChip>
<VBadge v-else :icon="icons[accessLevel]" :color="colors[accessLevel]" v-bind="$attrs">
<slot />
</VBadge>
</template>

<script setup lang="ts">
import { useI18n } from 'vue-i18n'
defineProps<{
accessLevel: number
variant?: 'chip'
}>()
const icons = ['mdi-earth', 'mdi-shield-outline', 'mdi-lock-outline']
const { t } = useI18n()
const icons = ['mdi-earth', 'mdi-shield-outline', 'mdi-lock-outline']
const colors = ['success', 'info', 'primary']
const names = ['public', 'restricted', 'private']
</script>
6 changes: 6 additions & 0 deletions apps/frontend/src/locales/en.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ term:
runner-id: Runner ID
path: Path
filter: Filter
access-level: Access Level

common:
created-at: Created at
Expand Down Expand Up @@ -222,3 +223,8 @@ platform-issue:
file: File API not supported
file-stream: Web Stream API for File not supported
transferable-stream: Transferable Streams not supported

access-level:
public: Public
restricted: Restricted
private: Private
6 changes: 6 additions & 0 deletions apps/frontend/src/locales/zh-Hans.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ term:
runner-id: 评测机 ID
path: 路径
filter: 过滤
access-level: 访问等级

common:
created-at: 创建于
Expand Down Expand Up @@ -221,3 +222,8 @@ platform-issue:
file: 不支持 File API
file-stream: 不支持文件上的 Web Stream API
transferable-stream: 不支持 Transferable Streams

access-level:
public: 公开
restricted: 受限
private: 私有
Loading

0 comments on commit 9ddd6be

Please sign in to comment.