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
17 changes: 15 additions & 2 deletions assets/vue/components/layout/SectionHeader.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup>
import { computed } from "vue"
import { useRoute } from "vue-router"
import { useCidReqStore } from "../../store/cidReq"
import { storeToRefs } from "pinia"
import StudentViewButton from "../StudentViewButton.vue"
Expand All @@ -19,11 +21,22 @@ defineProps({
},
})

const route = useRoute()
const cidReqStore = useCidReqStore()

const { course } = storeToRefs(cidReqStore)
</script>

function isTruthyQueryValue(value) {
return ["1", "true", "yes", "on"].includes(String(value || "").toLowerCase())
}

const isEmbeddedStudentView = computed(
() =>
"learnpath" === String(route.query.origin || "").toLowerCase() &&
isTruthyQueryValue(route.query.embedded) &&
isTruthyQueryValue(route.query.isStudentView),
)
</script>
<template>
<div
:class="`section-header--h${size}`"
Expand All @@ -38,7 +51,7 @@ const { course } = storeToRefs(cidReqStore)

<div class="section-header__actions">
<slot />
<StudentViewButton v-if="course && showStudentViewButton" />
<StudentViewButton v-if="course && showStudentViewButton && !isEmbeddedStudentView" />
</div>
</div>
</template>
15 changes: 14 additions & 1 deletion assets/vue/composables/userPermissions.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import api from "../config/api"
import { computed, reactive, ref, unref, watch } from "vue"
import { useSecurityStore } from "../store/securityStore"
import { usePlatformConfig } from "../store/platformConfig"
import { useRoute } from "vue-router"

// ─── Shared reactive cache ────────────────────────────────────────────────────
// Keyed by buildCacheKey(); invalidated when student view is toggled.
Expand Down Expand Up @@ -78,13 +79,25 @@ export function useIsAllowedToEdit({
} = {}) {
const cidReqStore = useCidReqStore()
const platformConfigStore = usePlatformConfig()
const route = useRoute()
const { course, session } = storeToRefs(cidReqStore)

const isEmbeddedStudentView = computed(() => {
const truthyValues = ["1", "true", "yes", "on"]
const origin = String(route.query.origin || "").toLowerCase()
const embedded = truthyValues.includes(String(route.query.embedded || "").toLowerCase())
const studentView = truthyValues.includes(String(route.query.isStudentView || "").toLowerCase())

return origin === "learnpath" && embedded && studentView
})

const key = computed(() =>
buildCacheKey(tutor, coach, sessionCoach, checkStudentView, course.value?.id, session.value?.id),
)

const isAllowedToEdit = computed(() => permissionCache.get(key.value) ?? false)
const isAllowedToEdit = computed(() =>
isEmbeddedStudentView.value ? false : (permissionCache.get(key.value) ?? false),
)

// Trigger initial fetch (no-op if a result is already cached or pending)
void fetchPermission(tutor, coach, sessionCoach, checkStudentView, course.value?.id, session.value?.id)
Expand Down
14 changes: 13 additions & 1 deletion assets/vue/services/forumService.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
import baseService from "./baseService"

function getRuntimeViewParams() {
if (typeof window === "undefined") {
return {}
}

const searchParams = new URLSearchParams(window.location.search)
const isStudentView = searchParams.get("isStudentView")

return isStudentView === null ? {} : { isStudentView }
}

function cleanParams(params = {}) {
const query = {}
const mergedParams = { ...getRuntimeViewParams(), ...params }

for (const [key, value] of Object.entries(params)) {
for (const [key, value] of Object.entries(mergedParams)) {
if (value !== undefined && value !== null && String(value) !== "") {
query[key] = value
}
Expand Down
35 changes: 9 additions & 26 deletions assets/vue/views/assignments/AssignmentDetail.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
>
{{ t("Loading...") }}
</div>

<div
v-else
class="space-y-6"
Expand All @@ -24,7 +23,6 @@
type="black"
@click="goBack"
/>

<template v-if="forceStudentView && !isAfterEndDate && canSubmitMore">
<BaseButton
v-if="allowTextFlag && !allowFileFlag"
Expand Down Expand Up @@ -57,8 +55,7 @@
/>
</template>
</template>

<template v-else-if="isTeacherUI">
<template v-else-if="isTeacherUI && !forceStudentView">
<BaseButton
:label="t('Add document')"
icon="file-add"
Expand Down Expand Up @@ -120,7 +117,6 @@
/>
</template>
</div>

<div
v-if="forceStudentView && isAfterEndDate"
class="text-red-600 border border-red-300 p-4 rounded bg-red-50"
Expand All @@ -136,7 +132,6 @@
v-html="assignment.description"
/>
</div>

<div
v-if="addedDocuments.length"
class="bg-gray-10 border-t border-gray-25 p-4 mt-0"
Expand All @@ -157,7 +152,6 @@
</li>
</ul>
</div>

<div>
<StudentSubmissionList
v-if="forceStudentView"
Expand All @@ -176,7 +170,6 @@
</div>
</div>
</template>

<script setup>
import { ref, computed, onMounted } from "vue"
import { useRoute, useRouter } from "vue-router"
Expand All @@ -186,12 +179,10 @@ import { useSecurityStore } from "../../store/securityStore"
import { usePlatformConfig } from "../../store/platformConfig"
import cStudentPublicationService from "../../services/cstudentpublication"
import { useNotification } from "../../composables/notification"

import BaseButton from "../../components/basecomponents/BaseButton.vue"
import SectionHeader from "../../components/layout/SectionHeader.vue"
import StudentSubmissionList from "../../components/assignments/StudentSubmissionList.vue"
import TeacherSubmissionList from "../../components/assignments/TeacherSubmissionList.vue"

const { t } = useI18n()
const { cid, sid, gid } = getCourseContext()
const route = useRoute()
Expand All @@ -202,10 +193,14 @@ const notification = useNotification()

const assignmentId = parseInt(route.params.id, 10)
const fromLearnpath = route.query.origin === "learnpath"
const requestedStudentView = computed(() =>
["1", "true", "yes", "on"].includes(String(route.query.isStudentView || "").toLowerCase()),
)

const isTeacherUI = computed(() => securityStore.isCurrentTeacher)

const forceStudentView = computed(() => !isTeacherUI.value || platformConfigStore.isStudentViewActive)
const forceStudentView = computed(
() => requestedStudentView.value || !isTeacherUI.value || platformConfigStore.isStudentViewActive,
)

const assignment = ref(null)
const addedDocuments = ref([])
Expand All @@ -216,7 +211,6 @@ const oneSubmissionPerUser = computed(
() => platformConfigStore.getSetting("work.allow_only_one_student_publication_per_user") === "true",
)
const canSubmitMore = computed(() => !oneSubmissionPerUser.value || studentSubmissionCount.value === 0)

function buildCidParams() {
return {
cid,
Expand All @@ -230,7 +224,6 @@ function fromApiLocal(str) {
const s = String(str).includes("T") ? String(str) : String(str).replace(" ", "T")
return new Date(s)
}

const expiresOnDate = computed(() => fromApiLocal(assignment.value?.assignment?.expiresOn))
const endsOnDate = computed(() => fromApiLocal(assignment.value?.assignment?.endsOn))
const isAfterEndDate = computed(() => (endsOnDate.value ? new Date() > endsOnDate.value : false))
Expand All @@ -239,7 +232,6 @@ const isAfterDeadline = isAfterEndDate
const allowTextFlag = computed(
() => assignment.value?.allowTextAssignment === 0 || assignment.value?.allowTextAssignment === 1,
)

const allowFileFlag = computed(
() => assignment.value?.allowTextAssignment === 0 || assignment.value?.allowTextAssignment === 2,
)
Expand All @@ -255,7 +247,6 @@ async function loadAddedDocuments() {
console.warn("[AssignmentDetail] Failed to load added documents", e)
}
}

onMounted(async () => {
assignment.value = await cStudentPublicationService.getAssignmentMetadata(assignmentId, cid, sid, gid)
await loadAddedDocuments()
Expand All @@ -264,12 +255,12 @@ onMounted(async () => {
function goBack() {
router.push({ name: "AssignmentsList", query: { cid, sid, gid } })
}

function goToSubmit(flags) {
router.push({
name: "AssignmentSubmit",
params: { id: assignmentId, node: route.params.node },
query: {
...route.query,
cid,
sid,
gid,
Expand All @@ -278,7 +269,6 @@ function goToSubmit(flags) {
},
})
}

function uploadMyAssignment(flags) {
router.push({
name: "AssignmentSubmit",
Expand All @@ -287,6 +277,7 @@ function uploadMyAssignment(flags) {
node: route.params.node,
},
query: {
...route.query,
cid,
sid,
gid,
Expand All @@ -300,7 +291,6 @@ function addDocument() {
if (!isTeacherUI.value) return
router.push({ name: "AssignmentAddDocument", params: { id: assignmentId }, query: { cid, sid, gid } })
}

function addUsers() {
if (!isTeacherUI.value) return
router.push({ name: "AssignmentAddUser", params: { id: assignmentId }, query: { cid, sid, gid } })
Expand All @@ -314,12 +304,10 @@ function editAssignment() {
query: { ...route.query, from: "AssignmentDetail", node: route.params.node },
})
}

function showUnsubmittedUsers() {
if (!isTeacherUI.value) return
router.push({ name: "AssignmentMissing", params: { id: assignmentId }, query: { cid, sid, gid } })
}

async function exportPdf() {
if (!isTeacherUI.value) return
try {
Expand All @@ -335,7 +323,6 @@ async function exportPdf() {
notification.showErrorNotification(t("Failed to export PDF"))
}
}

async function downloadAssignments() {
if (!isTeacherUI.value) return
try {
Expand All @@ -351,7 +338,6 @@ async function downloadAssignments() {
notification.showErrorNotification(t("Failed to download package"))
}
}

async function uploadCorrections() {
if (!isTeacherUI.value) return
const input = document.createElement("input")
Expand All @@ -361,7 +347,6 @@ async function uploadCorrections() {
input.addEventListener("change", async () => {
const file = input.files?.[0]
if (!file) return

try {
const res = await cStudentPublicationService.uploadCorrectionsPackage(assignmentId, file)
notification.showSuccessNotification(
Expand All @@ -375,15 +360,13 @@ async function uploadCorrections() {

input.click()
}

async function deleteAllCorrections() {
if (!isTeacherUI.value) return
if (!confirm(t("Are you sure you want to delete all corrections?"))) return

try {
await cStudentPublicationService.deleteAllCorrections(assignmentId, cid, sid)
notification.showSuccessNotification(t("All corrections deleted"))

assignment.value = await cStudentPublicationService.getAssignmentMetadata(assignmentId, cid, sid, gid)
submissionListKey.value++
} catch {
Expand Down
Loading
Loading