Skip to content

Commit

Permalink
Merge pull request #29 from Normal-OJ/feat/forms
Browse files Browse the repository at this point in the history
Homework Creation/Edition Pages
Bogay authored Jan 8, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature.
2 parents b6dcdbe + 96b515b commit d81bdca
Showing 12 changed files with 474 additions and 216 deletions.
16 changes: 9 additions & 7 deletions src/auto/components.d.ts
Original file line number Diff line number Diff line change
@@ -4,24 +4,27 @@

declare module 'vue' {
export interface GlobalComponents {
AnnouncementCard: typeof import('./../components/Announcement/AnnouncementCard.vue')['default']
AnnouncementForm: typeof import('./../components/Announcement/AnnouncementForm.vue')['default']
CodeEditor: typeof import('./../components/CodeEditor.vue')['default']
CourseSideBar: typeof import('./../components/CourseSideBar.vue')['default']
CourseTopBar: typeof import('./../components/CourseTopBar.vue')['default']
DueCountdown: typeof import('./../components/DueCountdown.vue')['default']
HomeworkCard: typeof import('./../components/HomeworkCard.vue')['default']
HomeworkForm: typeof import('./../components/HomeworkForm.vue')['default']
HomeworkCard: typeof import('./../components/Homework/HomeworkCard.vue')['default']
HomeworkForm: typeof import('./../components/Homework/HomeworkForm.vue')['default']
IUilAngleDoubleLeft: typeof import('~icons/uil/angle-double-left')['default']
IUilAngleDoubleRight: typeof import('~icons/uil/angle-double-right')['default']
IUilAngleDown: typeof import('~icons/uil/angle-down')['default']
IUilBars: typeof import('~icons/uil/bars')['default']
IUilBookAlt: typeof import('~icons/uil/book-alt')['default']
IUilChartLine: typeof import('~icons/uil/chart-line')['default']
IUilCheck: typeof import('~icons/uil/check')['default']
IUilCheckCircle: typeof import('~icons/uil/check-circle')['default']
IUilEdit: typeof import('~icons/uil/edit')['default']
IUilExclamationOctagon: typeof import('~icons/uil/exclamation-octagon')['default']
IUilFacebook: typeof import('~icons/uil/facebook')['default']
IUilFileExclamationAlt: typeof import('~icons/uil/file-exclamation-alt')['default']
IUilFileUploadAlt: typeof import('~icons/uil/file-upload-alt')['default']
IUilGithub: typeof import('~icons/uil/github')['default']
IUilHome: typeof import('~icons/uil/home')['default']
IUilLeftArrowToLeft: typeof import('~icons/uil/left-arrow-to-left')['default']
IUilMapMarkerInfo: typeof import('~icons/uil/map-marker-info')['default']
IUilMoon: typeof import('~icons/uil/moon')['default']
IUilPlusCircle: typeof import('~icons/uil/plus-circle')['default']
@@ -35,12 +38,11 @@ declare module 'vue' {
LoginSection: typeof import('./../components/LoginSection.vue')['default']
MarkdownRenderer: typeof import('./../components/MarkdownRenderer.vue')['default']
PaginationButtons: typeof import('./../components/PaginationButtons.vue')['default']
PostCard: typeof import('./../components/PostCard.vue')['default']
PostForm: typeof import('./../components/PostForm.vue')['default']
ProblemAllowedLanguageSelector: typeof import('./../components/Problem/Forms/ProblemAllowedLanguageSelector.vue')['default']
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']
ProblemMultiSelect: typeof import('./../components/Homework/Fields/ProblemMultiSelect.vue')['default']
ProblemTestdataDescriptionModal: typeof import('./../components/Problem/Forms/ProblemTestdataDescriptionModal.vue')['default']
SampleCodeBlock: typeof import('./../components/SampleCodeBlock.vue')['default']
SideBar: typeof import('./../components/SideBar.vue')['default']
55 changes: 55 additions & 0 deletions src/components/Homework/Fields/ProblemMultiSelect.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script setup lang="ts">
import { Listbox, ListboxButton, ListboxOptions, ListboxOption } from "@headlessui/vue";
interface Props {
modelValue: number[];
problems: { text: string; value: string }[];
}
defineProps<Props>();
defineEmits<{
(e: "update:model-value", newValue: number[]): void;
}>();
</script>

<template>
<Listbox :model-value="modelValue" multiple @update:model-value="(v) => $emit('update:model-value', v)">
<div class="relative mt-1">
<ListboxButton class="input-bordered input w-full max-w-xs text-left">
<span class="block truncate">{{ modelValue.join(", ") }}</span>
<span class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2">
<i-uil-angle-down class="h-5 w-5 text-gray-400" aria-hidden="true" />
</span>
</ListboxButton>

<transition
leave-active-class="transition duration-100 ease-in"
leave-from-class="opacity-100"
leave-to-class="opacity-0"
>
<ListboxOptions
class="absolute mt-1 max-h-80 w-full overflow-auto rounded-md bg-base-100 py-1 text-base shadow-lg sm:text-sm"
>
<ListboxOption
v-slot="{ active, selected }"
v-for="{ text, value } in problems"
:key="text"
:value="Number(value)"
as="template"
>
<li :class="[active && 'bg-base-300', 'cursor- default relative select-none py-2 pl-10 pr-4']">
<span :class="[selected ? 'font-medium' : 'font-normal', 'block truncate']">
{{ text }}
</span>
<span
v-if="selected"
class="absolute inset-y-0 left-0 flex items-center pl-3 text-base-content"
>
<i-uil-check class="h-5 w-5" aria-hidden="true" />
</span>
</li>
</ListboxOption>
</ListboxOptions>
</transition>
</div>
</Listbox>
</template>
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<script setup lang="ts">
import { computed } from "vue";
import { useSession } from "../stores/session";
import { formatTime } from "../utils/formatTime";
import { useSession } from "../../stores/session";
import { formatTime } from "../../utils/formatTime";
interface Props {
homework: HomeworkListItem;
homework: HomeworkListItem | HomeworkPreviewForm;
problems: Record<string, { name: string | "-"; quota: number | "-" }>;
preview?: boolean;
}
@@ -111,7 +111,7 @@ const state = computed(() => {
<td>
<div class="tooltip" data-tip="Stats">
<router-link
class="btn btn-ghost btn-xs"
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" />
@@ -121,7 +121,7 @@ const state = computed(() => {
<td v-if="session.isAdmin">
<div class="tooltip" data-tip="Copycat">
<router-link
class="btn btn-ghost btn-xs"
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" />
135 changes: 135 additions & 0 deletions src/components/Homework/HomeworkForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
<script setup lang="ts">
import { toRef, computed } from "vue";
import useVuelidate from "@vuelidate/core";
import { required, maxLength, minValue, helpers } from "@vuelidate/validators";
import dayjs from "dayjs";
interface Props {
form: HomeworkForm;
problemSelections: { text: string; value: string }[];
isLoading: boolean;
}
const props = defineProps<Props>();
const rules = {
name: { required, maxLength: maxLength(64) },
markdown: { maxLength: maxLength(10000) },
problemIds: { required },
start: { required },
end: {
required,
minValue: helpers.withMessage(
"End time must be greater than Start time",
minValue(toRef(props.form, "start")),
),
},
};
const v$ = useVuelidate(rules, props.form);
const emit = defineEmits<{
(e: "update", key: keyof HomeworkForm, value: HomeworkForm[typeof key]): void;
(e: "submit"): void;
}>();
const startDateTime = computed(() => dayjs(props.form.start * 1000).format("YYYY-MM-DD\THH:mm"));
const endDateTime = computed(() => dayjs(props.form.end * 1000).format("YYYY-MM-DD\THH:mm"));
function handleStartDateTimeInput(event: any) {
updateForm("start", dayjs(event.target.value).valueOf() / 1000);
}
function handleEndDateTimeInput(event: any) {
updateForm("end", dayjs(event.target.value).valueOf() / 1000);
}
function updateForm(key: keyof HomeworkForm, value: HomeworkForm[typeof key]) {
emit("update", key, value);
v$.value[key].$touch();
}
async function submit() {
const isFormCorrect = await v$.value.$validate();
if (isFormCorrect) {
emit("submit");
}
}
</script>

<template>
<div class="grid grid-cols-1 gap-y-4 lg:grid-cols-2">
<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Name</span>
</label>
<input
type="text"
:class="['input-bordered input w-full max-w-xs', v$.name.$error && 'input-error']"
:value="form.name"
@input="updateForm('name', ($event.target as HTMLInputElement).value)"
/>
<label class="label" v-show="v$.name.$error">
<span class="label-text-alt text-error" v-text="v$.name.$errors[0]?.$message" />
</label>
</div>

<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Problems</span>
</label>
<problem-multi-select
:model-value="form.problemIds"
:problems="problemSelections"
@update:model-value="(v) => updateForm('problemIds', v)"
/>
<label class="label" v-show="v$.problemIds.$error">
<span class="label-text-alt text-error" v-text="v$.problemIds.$errors[0]?.$message" />
</label>
</div>

<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">From</span>
</label>
<input
type="datetime-local"
class="input-bordered input w-full max-w-xs"
:value="startDateTime"
@change="handleStartDateTimeInput"
/>
<label class="label" v-show="v$.start.$error">
<span class="label-text-alt text-error" v-text="v$.start.$errors[0]?.$message" />
</label>
</div>

<div class="form-control w-full max-w-xs">
<label class="label">
<span class="label-text">Due</span>
</label>
<input
type="datetime-local"
class="input-bordered input w-full max-w-xs"
:value="endDateTime"
@change="handleEndDateTimeInput"
/>
<label class="label" v-show="v$.end.$error">
<span class="label-text-alt text-error" v-text="v$.end.$errors[0]?.$message" />
</label>
</div>

<div class="form-control w-full lg:col-span-2">
<label class="label">
<span class="label-text">Description</span>
</label>
<textarea
class="textarea-bordered textarea h-24"
:value="form.markdown"
@input="updateForm('markdown', ($event.target as HTMLTextAreaElement).value)"
/>
<label class="label" v-show="v$.markdown.$error">
<span class="label-text-alt text-error" v-text="v$.markdown.$errors[0]?.$message" />
</label>
</div>
</div>
<div class="mt-4 flex justify-end">
<button :class="['btn-success btn', isLoading && 'loading']" @click="submit">
<i-uil-file-upload-alt class="mr-1 lg:h-5 lg:w-5" /> Submit
</button>
</div>
</template>
123 changes: 0 additions & 123 deletions src/components/HomeworkForm.vue

This file was deleted.

Loading

0 comments on commit d81bdca

Please sign in to comment.