Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: mobile homework page #168

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/auto/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ declare module '@vue/runtime-core' {
DueCountdown: typeof import('./../components/DueCountdown.vue')['default']
HomeworkCard: typeof import('./../components/Homework/HomeworkCard.vue')['default']
HomeworkForm: typeof import('./../components/Homework/HomeworkForm.vue')['default']
HomeworkProblems: typeof import('./../components/Homework/HomeworkProblems.vue')['default']
IUilAngleDoubleDown: typeof import('~icons/uil/angle-double-down')['default']
IUilAngleDoubleLeft: typeof import('~icons/uil/angle-double-left')['default']
IUilAngleDoubleRight: typeof import('~icons/uil/angle-double-right')['default']
Expand Down Expand Up @@ -59,6 +60,7 @@ declare module '@vue/runtime-core' {
ProblemCard: typeof import('./../components/Problem/ProblemCard.vue')['default']
ProblemDescriptionForm: typeof import('./../components/Problem/Forms/ProblemDescriptionForm.vue')['default']
ProblemForm: typeof import('./../components/Problem/ProblemForm.vue')['default']
ProblemInfoCard: typeof import('./../components/Problem/ProblemInfoCard.vue')['default']
ProblemMultiSelect: typeof import('./../components/Homework/Fields/ProblemMultiSelect.vue')['default']
ProblemTestdataDescriptionModal: typeof import('./../components/Problem/Forms/ProblemTestdataDescriptionModal.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
Expand Down
96 changes: 33 additions & 63 deletions src/components/Homework/HomeworkCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { computed } from "vue";
import { useSession } from "@/stores/session";
import { formatTime } from "@/utils/formatTime";
import { useI18n } from "vue-i18n";
import useInteractions from "@/composables/useInteractions";

import type { ProblemId2Meta } from "@/composables/useProblemSelection";
import { isQuotaUnlimited } from "@/constants";

const { t } = useI18n();
const { isDesktop } = useInteractions();

interface Props {
homework: HomeworkListItem | HomeworkPreviewForm;
Expand Down Expand Up @@ -46,8 +48,8 @@ const state = computed(() => {
<template>
<div class="card mx-auto w-full bg-base-100 shadow-xl">
<div class="card-body">
<div class="flex items-start justify-between">
<div class="lg:text-2x card-title mb-8 md:text-xl">
<div class="flex flex-col items-start justify-between sm:flex-row">
<div class="lg:text-2x card-title md:mb-8 md:text-xl">
{{ homework.name }}
<div :class="['badge', STATUS_CLASS[state]]">{{ state }}</div>
</div>
Expand All @@ -58,7 +60,7 @@ const state = computed(() => {
<div class="mb-8 w-full lg:flex-[2_1_0%]">
<div class="card-title">{{ t("components.hw.card.availability.text") }}</div>
<div class="mt-2 flex flex-wrap overflow-x-auto lg:flex-nowrap">
<table class="table-compact table w-full">
<table v-if="isDesktop" class="table-compact table w-full">
<thead>
<tr>
<th>{{ t("components.hw.card.availability.from") }}</th>
Expand All @@ -72,73 +74,41 @@ const state = computed(() => {
</tr>
</tbody>
</table>
<div v-else class="flex flex-wrap text-sm">
<div>
<span>{{ formatTime(homework.start) }}</span>
</div>
~
<div>
<span>{{ formatTime(homework.end) }}</span>
</div>
</div>
</div>
</div>

<div class="mb-8 w-full lg:flex-[3_1_0%]">
<div class="card-title">{{ t("components.hw.card.problems.text") }}</div>
<table class="table-compact mt-2 table w-full">
<thead>
<tr>
<th>{{ t("components.hw.card.problems.id") }}</th>
<th>{{ t("components.hw.card.problems.pid") }}</th>
<th>{{ t("components.hw.card.problems.name") }}</th>
<th>{{ t("components.hw.card.problems.quota") }}</th>
<th>{{ t("components.hw.card.problems.score") }}</th>
<th>{{ t("components.hw.card.problems.stats") }}</th>
<th v-if="session.isAdmin">{{ t("components.hw.card.problems.copycat") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(pid, index) in homework.problemIds">
<td>{{ index + 1 }}</td>
<td>
<router-link class="link" :to="`/course/${$route.params.name}/problem/${pid}`">
{{ pid }}
</router-link>
</td>
<td>
<ui-spinner v-if="!problems[pid.toString()]" />
<span v-else>{{ problems[pid.toString()].name }}</span>
</td>
<td>
<ui-spinner v-if="!problems[pid.toString()]" />
<span v-else-if="isQuotaUnlimited(problems[pid.toString()].quota)" class="text-sm">{{
$t("components.problem.card.unlimited")
}}</span>
<span v-else>{{ problems[pid.toString()].quota }}</span>
</td>
<td>
{{
<homework-problems v-if="isDesktop" :homework="homework" :problems="problems" />
<div v-else class="w-full py-1">
<div class="flex w-full flex-wrap justify-center gap-1 sm:justify-start">
<template v-for="pid in homework.problemIds">
<problem-info-card
class="w-full sm:w-72"
:name="problems[pid.toString()].name"
:id="pid"
:quota="problems[pid.toString()].quota"
:score="
(
homework.studentStatus[session.username] &&
homework.studentStatus[session.username][pid.toString()]
)?.score || "-"
}}
</td>
<td>
<div class="tooltip" data-tip="Stats">
<router-link
class="btn-ghost btn-xs btn"
:to="`/course/${$route.params.name}/problem/${pid}/stats`"
>
<i-uil-chart-line class="lg:h-5 lg:w-5" />
</router-link>
</div>
</td>
<td v-if="session.isAdmin">
<div class="tooltip" data-tip="Copycat">
<router-link
class="btn-ghost btn-xs btn"
:to="`/course/${$route.params.name}/problem/${pid}/copycat`"
>
<i-uil-file-exclamation-alt class="lg:h-5 lg:w-5" />
</router-link>
</div>
</td>
</tr>
</tbody>
</table>
)?.score || 0
"
:show-stats="session.isAdmin"
:show-copycat="session.isAdmin"
/>
</template>
</div>
</div>
</div>
</div>

Expand Down
81 changes: 81 additions & 0 deletions src/components/Homework/HomeworkProblems.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<script setup lang="ts">
import { useSession } from "@/stores/session";
import { useI18n } from "vue-i18n";
import type { ProblemId2Meta } from "@/composables/useProblemSelection";
import { isQuotaUnlimited } from "@/constants";

interface Props {
homework: HomeworkListItem | HomeworkPreviewForm;
problems: ProblemId2Meta;
}

defineProps<Props>();

const { t } = useI18n();
const session = useSession();
</script>

<template>
<table class="table-compact mt-2 table w-full">
<thead>
<tr>
<th>{{ t("components.hw.card.problems.id") }}</th>
<th>{{ t("components.hw.card.problems.pid") }}</th>
<th>{{ t("components.hw.card.problems.name") }}</th>
<th>{{ t("components.hw.card.problems.quota") }}</th>
<th>{{ t("components.hw.card.problems.score") }}</th>
<th>{{ t("components.hw.card.problems.stats") }}</th>
<th v-if="session.isAdmin">{{ t("components.hw.card.problems.copycat") }}</th>
</tr>
</thead>
<tbody>
<tr v-for="(pid, index) in homework.problemIds">
<td>{{ index + 1 }}</td>
<td>
<router-link class="link" :to="`/course/${$route.params.name}/problem/${pid}`">
{{ pid }}
</router-link>
</td>
<td>
<ui-spinner v-if="!problems[pid.toString()]" />
<span v-else>{{ problems[pid.toString()].name }}</span>
</td>
<td>
<ui-spinner v-if="!problems[pid.toString()]" />
<span v-else-if="isQuotaUnlimited(problems[pid.toString()].quota)" class="text-sm">{{
$t("components.problem.card.unlimited")
}}</span>
<span v-else>{{ problems[pid.toString()].quota }}</span>
</td>
<td>
{{
(
homework.studentStatus[session.username] &&
homework.studentStatus[session.username][pid.toString()]
)?.score || "-"
}}
</td>
<td>
<div class="tooltip" data-tip="Stats">
<router-link
class="btn-ghost btn-xs btn"
:to="`/course/${$route.params.name}/problem/${pid}/stats`"
>
<i-uil-chart-line class="lg:h-5 lg:w-5" />
</router-link>
</div>
</td>
<td v-if="session.isAdmin">
<div class="tooltip" data-tip="Copycat">
<router-link
class="btn-ghost btn-xs btn"
:to="`/course/${$route.params.name}/problem/${pid}/copycat`"
>
<i-uil-file-exclamation-alt class="lg:h-5 lg:w-5" />
</router-link>
</div>
</td>
</tr>
</tbody>
</table>
</template>
69 changes: 69 additions & 0 deletions src/components/Problem/ProblemInfoCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { UNLIMITED_QUOTA } from "@/constants";
import { useI18n } from "vue-i18n";

interface Props {
name: string;
id: string | number;
quota: string | number;
score?: string | number;
tags?: string[];
showStats?: boolean;
showCopycat?: boolean;
}

const { t } = useI18n();

withDefaults(defineProps<Props>(), {
showStats: true,
showCopycat: false,
});
</script>
<template>
<div class="card card-compact max-w-full bg-base-200/50 shadow-xl">
<div class="card-body">
<router-link :to="`/course/${$route.params.name}/problem/${id}`">
<h2
class="card-title underline"
:class="{
'text-success': score === 100,
}"
>
{{ name }}
</h2>
<div class="flex flex-col gap-2">
<div class="flex justify-between">
<span v-if="score !== undefined">
<span class="text-2xl font-bold"> {{ score }} </span>
<span class="text-xs">/ 100</span>
</span>
<span class="font-semibold lowercase">
{{ quota == UNLIMITED_QUOTA ? t("components.problem.infoCard.unlimited") : quota }}
{{ t("components.problem.infoCard.quota") }}
</span>
</div>
<div v-if="tags" class="flex flex-col">
<span class="font-semibold"> {{ t("components.problem.infoCard.tags") }} : </span>
<span v-for="tag in tags" :key="tag" class="badge-info badge">{{ tag }}</span>
</div>
</div>
</router-link>
<div v-if="showStats || showCopycat" class="card-actions justify-end">
<router-link
v-if="showStats"
class="btn btn-sm gap-2"
:to="`/course/${$route.params.name}/problem/${id}/stats`"
>
<i-uil-chart-line class="lg:h-5 lg:w-5" />
</router-link>
<router-link
v-if="showCopycat"
class="btn btn-sm gap-2"
:to="`/course/${$route.params.name}/problem/${id}/copycat`"
>
<i-uil-file-exclamation-alt class="lg:h-5 lg:w-5" />
</router-link>
</div>
</div>
</div>
</template>
6 changes: 6 additions & 0 deletions src/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,12 @@
},
"noContent": "no content",
"unlimited": "unlimited"
},
"infoCard": {
"score": "Score",
"tags": "Tags",
"quota": "Quota",
"unlimited": "unlimited"
}
},
"courseSideBar": {
Expand Down
6 changes: 6 additions & 0 deletions src/i18n/zh-tw.json
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,12 @@
},
"noContent": "無",
"unlimited": "無限制"
},
"infoCard": {
"score": "得分",
"tags": "標籤",
"quota": "額度",
"unlimited": "無限制"
}
},
"courseSideBar": {
Expand Down
Loading