Skip to content
Merged
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: 1 addition & 1 deletion e2e/pytest.ini
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ asyncio_mode = auto
# https://playwright.dev/python/docs/test-runners#async-fixtures
asyncio_default_test_loop_scope = session
asyncio_default_fixture_loop_scope = session
norecursedirs = "tests"
norecursedirs = tests frontend
6 changes: 4 additions & 2 deletions frontend/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,10 @@
</BButton>
</BNavbar>

<BContainer class="pt-4" fluid="md">
<RouterView />
<BContainer class="py-4" fluid="md">
<div class="vstack gap-4">
<RouterView />
</div>
</BContainer>

<ErrorModal />
Expand Down
7 changes: 3 additions & 4 deletions frontend/src/components/PackageCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@
-->

<template>
<div>
<LoadingIndicator v-if="asyncStatus === 'loading'" />
<LoadingIndicator :loading="isPending">
<ErrorCard v-if="state.error" :error="state.error" />
<CollapsibleCard variant="success" v-else-if="manifest" expanded>
<BContainer class="px-0" fluid>
Expand Down Expand Up @@ -52,13 +51,13 @@
</div>
</template>
</CollapsibleCard>
</div>
</LoadingIndicator>
</template>

<script lang="ts" setup>
import { useManifestQuery } from '@/queries'

const { asyncStatus, manifest, state } = useManifestQuery()
const { isPending, manifest, state } = useManifestQuery()
</script>

<style lang="scss" scoped>
Expand Down
11 changes: 5 additions & 6 deletions frontend/src/components/attempt/AttemptCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,9 @@
</template>
</BContainer>
<ButtonGroup>
<!-- TODO: Implement clone -->
<IconButton :icon-component="IMdiContentCopy" variant="secondary" size="sm">Clone</IconButton>
<IconButton @click="cloneAttempt" :icon-component="IMdiContentCopy" variant="secondary" size="sm"
>Clone</IconButton
>
<!-- TODO: Implement export -->
<IconButton :icon-component="IMdiExport" variant="secondary" size="sm">Export</IconButton>
<IconButton @click="deleteAttempt" :icon-component="IMdiDelete" variant="danger" size="sm"
Expand Down Expand Up @@ -82,8 +83,7 @@ import { computed } from 'vue'
import { useLink } from 'vue-router'

import CollapsibleCard from '@/components/common/CollapsibleCard.vue'
import { useAttemptDisplay } from '@/composables/attempt'
import { useDeleteAttempt } from '@/composables/attempt'
import { useAttemptDisplay, useCloneAttempt, useDeleteAttempt } from '@/composables/attempt'
import type { AttemptData } from '@/types'

const {
Expand All @@ -99,10 +99,9 @@ const {
}>()

const attemptLocation = { name: 'question-attempt', params: { questionId, attemptId } } as const

const deleteAttempt = useDeleteAttempt(questionId, attemptId)
const cloneAttempt = useCloneAttempt(questionId, attemptId)
const { isActive: isCurrentPreviewActive } = useLink({ to: attemptLocation })
const { isScored, displayScore, displayStatus } = useAttemptDisplay(attemptData)

const cardComponent = computed(() => (collapsible ? CollapsibleCard : BCard))
</script>
50 changes: 21 additions & 29 deletions frontend/src/components/attempt/AttemptList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,36 @@
-->

<template>
<LoadingIndicator v-if="asyncStatus === 'loading'" />
<ErrorCard v-if="error" :error="error" />
<CollapsibleCard v-else expanded>
<template #button-title>Saved attempts ({{ attemptCount }})</template>
<AttemptCard
v-for="[attemptId, attemptData] in Object.entries(attempts)"
class="attempt-card"
:id="`attempt-${questionId}-${attemptId}`"
:key="attemptId"
:question-id="questionId"
:attempt-id="attemptId"
:attempt-data="attemptData"
/>
<BAlert v-if="attemptCount === 0" :model-value="true" class="mb-0" variant="info"
>This question has no attempts yet.</BAlert
<LoadingIndicator :loading="isPending">
<ErrorCard v-if="error" :error="error" />
<ListView
v-else
:items="attempts"
text-empty="This question has no attempts yet."
title="Saved attempts"
deferred-item-model="attempt"
>
</CollapsibleCard>
<template #item="{ item, id }">
<AttemptCard
:id="`attempt-${questionId}-${id}`"
:question-id="questionId"
:attempt-id="id"
:attempt-data="item as AttemptData"
/>
</template>
</ListView>
</LoadingIndicator>
</template>

<script lang="ts" setup>
import { computed } from 'vue'

import AttemptCard from '@/components/attempt/AttemptCard.vue'
import { useAttemptListQuery } from '@/queries'
import type { AttemptData } from '@/types'

const { questionId } = defineProps<{ questionId: string }>()

const { asyncStatus, error, data: listData } = useAttemptListQuery(questionId)

const { error, data: listData, isPending } = useAttemptListQuery(questionId)
const attempts = computed(() => listData.value ?? {})
const attemptCount = computed(() => Object.keys(attempts.value).length)
</script>

<style lang="scss" scoped>
.attempt-card {
margin-bottom: $spacer;

&:last-of-type {
margin-bottom: 0;
}
}
</style>
111 changes: 61 additions & 50 deletions frontend/src/components/attempt/AttemptPreview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,57 +5,68 @@
-->

<template>
<LoadingIndicator v-if="asyncStatus === 'loading'" />
<ErrorCard v-if="error" :error="error" />
<template v-else>
<AttemptRenderErrors v-if="renderErrors" :render-errors="renderErrors" />
<BContainer fluid>
<BRow>
<BCol class="px-0" cols="12" md="8" order-md="2">
<AttemptIframe v-if="iframeSrcDoc" :src-doc="iframeSrcDoc" ref="attemptIframeRef" />
</BCol>
<BCol class="px-0 mb-3" cols="12" md="4" order-md="1">
<BRow align-v="center">
<BCol cols="6" md="12">
<h3 class="mb-md-5">
<BBadge variant="info">{{
displayScore ? `Score: ${displayScore}` : 'Not yet scored'
}}</BBadge>
</h3>
</BCol>
<BCol class="px-0" cols="6" md="12">
<IconButton
:icon-component="IMdiEdit"
:to="{ name: 'question-edit', params: { questionId } }"
variant="link"
>Edit question</IconButton
>
</BCol>
</BRow>
</BCol>
</BRow>
</BContainer>
<ButtonGroup class="mb-4">
<IconButton :icon-component="IMdiContentSave" @click="save" variant="primary">Save</IconButton>
<IconButton :icon-component="IMdiContentSaveMove" @click="saveAndSubmit" variant="secondary"
>Save and submit</IconButton
>
<IconButton :disabled="isRestartDisabled" :icon-component="IMdiRestart" @click="restart" variant="warning"
>Restart</IconButton
>
<IconButton :disabled="isRescoreDisabled" :icon-component="IMdiScore" @click="score" variant="info"
>Re-score</IconButton
>
</ButtonGroup>
<DisplayOptions class="mb-4" />
<AttemptCard
v-if="attemptData"
:attempt-data="attemptData"
:question-id="questionId"
:attempt-id="attemptId"
collapsible
variant="info"
/>
<LoadingIndicator :loading="isPending">
<AttemptRenderErrors v-if="renderErrors" :render-errors="renderErrors" />
<BContainer fluid>
<BRow>
<BCol class="px-0" cols="12" md="8" order-md="2">
<AttemptIframe v-if="iframeSrcDoc" :src-doc="iframeSrcDoc" ref="attemptIframeRef" />
</BCol>
<BCol class="px-0 mb-3" cols="12" md="4" order-md="1">
<BRow align-v="center">
<BCol cols="6" md="12">
<h3 class="mb-md-5">
<BBadge variant="info">{{
displayScore ? `Score: ${displayScore}` : 'Not yet scored'
}}</BBadge>
</h3>
</BCol>
<BCol class="px-0" cols="6" md="12">
<IconButton
:icon-component="IMdiEdit"
:to="{ name: 'question-edit', params: { questionId } }"
variant="link"
>Edit question</IconButton
>
</BCol>
</BRow>
</BCol>
</BRow>
</BContainer>
<ButtonGroup>
<IconButton :icon-component="IMdiContentSave" @click="save" variant="primary">Save</IconButton>
<IconButton :icon-component="IMdiContentSaveMove" @click="saveAndSubmit" variant="secondary"
>Save and submit</IconButton
>
<IconButton
:disabled="isRestartDisabled"
:icon-component="IMdiRestart"
@click="restart"
variant="warning"
>Restart</IconButton
>
<IconButton :disabled="isRescoreDisabled" :icon-component="IMdiScore" @click="score" variant="info"
>Re-score</IconButton
>
</ButtonGroup>
</LoadingIndicator>

<LoadingIndicator :loading="isPending">
<DisplayOptions />
</LoadingIndicator>

<LoadingIndicator :loading="isPending">
<AttemptCard
v-if="attemptData"
:attempt-data="attemptData"
:question-id="questionId"
:attempt-id="attemptId"
collapsible
variant="info"
/>
</LoadingIndicator>
</template>
</template>

Expand All @@ -79,9 +90,9 @@ const { questionId, attemptId } = defineProps<{
}>()

const {
asyncStatus,
attemptData,
error,
isPending,
iframeSrcDoc,
renderErrors,
score,
Expand Down
24 changes: 24 additions & 0 deletions frontend/src/components/common/BackLink.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!--
This file is part of the QuestionPy SDK. (https://questionpy.org)
The QuestionPy SDK is free software released under terms of the MIT license. See LICENSE.md.
(c) Technische Universität Berlin, innoCampus <info@isis.tu-berlin.de>
-->

<template>
<IconButton :icon-component="IMdiArrowLeft" :to="to" class="link ps-0" variant="link"><slot /></IconButton>
</template>

<script setup lang="ts">
import IMdiArrowLeft from '~icons/mdi/arrow-left'
import type { RouteLocationRaw } from 'vue-router'

defineProps<{
to: RouteLocationRaw
}>()
</script>

<style lang="scss" scoped>
.link {
margin-bottom: -$spacer;
}
</style>
10 changes: 9 additions & 1 deletion frontend/src/components/common/FormGroup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
-->

<template>
<BFormGroup class="mb-3" label-cols-sm="3" content-cols-sm="9">
<BFormGroup :disabled="hasPendingOperations" class="mb-3" label-cols-sm="3" content-cols-sm="9">
<slot />
</BFormGroup>
</template>

<script lang="ts" setup>
import { storeToRefs } from 'pinia'

import usePendingOperationsStore from '@/stores/usePendingOperationsStore'

const { hasPendingOperations } = storeToRefs(usePendingOperationsStore())
</script>
11 changes: 9 additions & 2 deletions frontend/src/components/common/IconButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,32 @@
-->

<template>
<BButton class="d-flex text-nowrap gap-2 align-items-center" v-bind="buttonProps">
<BButton :disabled="disabled" class="d-flex text-nowrap gap-2 align-items-center" v-bind="buttonProps">
<component :is="iconComponent" />
<slot />
</BButton>
</template>

<script lang="ts" setup>
import { storeToRefs } from 'pinia'
import { computed } from 'vue'
import type { BButtonProps } from 'bootstrap-vue-next'
import type { Component } from 'vue'

import usePendingOperationsStore from '@/stores/usePendingOperationsStore'

const { hasPendingOperations } = storeToRefs(usePendingOperationsStore())

const props = defineProps<
BButtonProps & {
iconComponent?: Component
}
>()

const buttonProps = computed(() => {
const { iconComponent, ...rest } = props
const { iconComponent, disabled, ...rest } = props
return rest
})

const disabled = computed(() => props.disabled || hasPendingOperations.value)
</script>
Loading