Skip to content

Commit

Permalink
Merge pull request #464 from SELab-2/submission-submit-structure
Browse files Browse the repository at this point in the history
Submission structure showcase
  • Loading branch information
bsilkyn authored May 23, 2024
2 parents 6f47fe0 + 273fc54 commit 998076f
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 74 deletions.
6 changes: 4 additions & 2 deletions frontend/src/assets/lang/app/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@
"leaveGroup": "Leave group",
"create": "Create new project",
"save": "Save project",
"edit": "Save project",
"edit": "Edit project",
"name": "Project name",
"description": "Description",
"startDate": "Start project",
Expand All @@ -97,7 +97,9 @@
"title": "Structure checks",
"placeholder": "Give a name to this folder",
"cancelSelection": "Deselect {0}",
"newFolder": "New folder"
"newFolder": "New folder",
"obligatedExtensions": "Obligated extensions",
"blockedExtensions": "Blocked extensions"
},
"extraChecks": {
"title": "Automatic checks on a submission",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/assets/lang/app/nl.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,9 @@
"title": "Indieningsstructuur",
"placeholder": "Geef deze nieuwe map een naam",
"cancelSelection": "Deselecteer {0}",
"newFolder": "Nieuwe map"
"newFolder": "Nieuwe map",
"obligatedExtensions": "Verplichte extensies",
"blockedExtensions": "Niet toegelaten extensies"
},
"extraChecks": {
"title": "Automatische checks op een indiening",
Expand Down
64 changes: 64 additions & 0 deletions frontend/src/components/projects/ProjectStructure.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<script setup lang="ts">
import Tree from 'primevue/tree';
import Chip from 'primevue/chip';
import { useI18n } from 'vue-i18n';
import { type StructureCheck } from '@/types/StructureCheck.ts';
import { Project } from '@/types/Project.ts';
const { t } = useI18n();
const props = defineProps<{
structureChecks: StructureCheck[];
}>();
console.log(props.structureChecks);
</script>

<template>
<div>
<div class="p-2">
{{ t('views.projects.structureChecks.title') }}
</div>
<Tree class="w-100" selection-mode="single" :value="Project.getNodes(structureChecks)">
<template #default="{ node }">
<template v-if="node.obligated">
<div class="w-full" v-tooltip="t('views.projects.structureChecks.obligatedExtensions')">
<Chip
class="m-1"
:label="extension"
:key="extension"
v-for="extension in node.data.getObligatedExtensionList()"
/>
</div>
</template>
<template v-else-if="node.blocked">
<div class="w-full" v-tooltip="t('views.projects.structureChecks.blockedExtensions')">
<Chip
class="m-1"
:label="extension"
:key="extension"
v-for="extension in node.data.getBlockedExtensionList()"
/>
</div>
</template>
<template v-else>
<div class="flex align-items-center justify-content-between gap-3">
<span>
{{ node.label }}
</span>
</div>
</template>
</template>
</Tree>
</div>
</template>

<style scoped lang="scss">
.p-treenode-label {
width: 100%;
ul {
width: 100%;
}
}
</style>
72 changes: 6 additions & 66 deletions frontend/src/components/projects/ProjectStructureEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import Tree from 'primevue/tree';
import Button from 'primevue/button';
import Chips from 'primevue/chips';
import InputText from 'primevue/inputtext';
import { StructureCheck } from '@/types/StructureCheck.ts';
import { computed, ref } from 'vue';
import { ref } from 'vue';
import { type TreeNode } from 'primevue/treenode';
import { PrimeIcons } from 'primevue/api';
import { useI18n } from 'vue-i18n';
import { Project } from '@/types/Project.ts';
import { StructureCheck } from '@/types/StructureCheck.ts';
/* Models */
const structureChecks = defineModel<StructureCheck[]>();
Expand All @@ -21,31 +22,6 @@ const editingStructureCheck = ref<StructureCheck | null>(null);
const selectedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>([]);
/* Computed */
const nodes = computed<TreeNode[]>(() => {
const nodes: TreeNode[] = [];
if (structureChecks.value !== undefined) {
for (const [i, check] of structureChecks.value.entries()) {
const hierarchy = check.getDirectoryHierarchy();
let currentNodes = nodes;
for (const [j, part] of hierarchy.entries()) {
let node = currentNodes.find((node) => node.label === part);
if (node === undefined) {
node = newTreeNode(check, `${i}${j}`, part, j === hierarchy.length - 1);
currentNodes.push(node);
}
currentNodes = node.children ?? [];
}
}
}
return nodes;
});
/**
* Delete a structure check from the list.
*
Expand Down Expand Up @@ -119,44 +95,6 @@ function selectStructureCheck(node: TreeNode): void {
selectedStructureCheck.value = node.data;
}
}
/**
* Construct a tree node from a structure check folder path.
*
* @param check
* @param key
* @param label
* @param leaf
*/
function newTreeNode(check: StructureCheck, key: string, label: string, leaf: boolean = false): TreeNode {
const node: TreeNode = {
key,
label,
data: check,
icon: PrimeIcons.FOLDER,
check: leaf,
children: [],
};
if (leaf) {
node.children = [
{
key: key + '-obligated',
icon: PrimeIcons.CHECK_CIRCLE,
data: check,
obligated: true,
},
{
key: key + '-blocked',
icon: PrimeIcons.TIMES_CIRCLE,
data: check,
blocked: true,
},
];
}
return node;
}
</script>

<template>
Expand All @@ -166,13 +104,14 @@ function newTreeNode(check: StructureCheck, key: string, label: string, leaf: bo
selection-mode="single"
v-model:selection-keys="selectedKeys"
v-model:expanded-keys="expandedKeys"
:value="nodes"
:value="Project.getNodes(structureChecks)"
@node-select="selectStructureCheck"
>
<template #default="{ node }">
<template v-if="node.obligated">
<Chips
class="w-full"
separator=","
:model-value="node.data.getObligatedExtensionList()"
@update:model-value="node.data.setObligatedExtensionList($event)"
v-tooltip="t('views.projects.structureChecks.obligatedExtensions')"
Expand All @@ -185,6 +124,7 @@ function newTreeNode(check: StructureCheck, key: string, label: string, leaf: bo
<template v-else-if="node.blocked">
<Chips
class="w-full"
separator=","
:model-value="node.data.getBlockedExtensionList()"
@update:model-value="node.data.setBlockedExtensionList($event)"
v-tooltip="t('views.projects.structureChecks.blockedExtensions')"
Expand Down
70 changes: 70 additions & 0 deletions frontend/src/types/Project.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import moment from 'moment';
import { type TreeNode } from 'primevue/treenode';
import { PrimeIcons } from 'primevue/api';
import { Course, type CourseJSON } from './Course.ts';
import { type ExtraCheck } from './ExtraCheck.ts';
import { type Group } from './Group.ts';
Expand Down Expand Up @@ -117,6 +119,74 @@ export class Project {
);
}

/**
* Given a list of structureChecks (directory path), return a list of TreeNodes representing the tree hierarchy
* of the structure checks.
*
* @param structureChecks
*/
public static getNodes(structureChecks: StructureCheck[] | undefined): TreeNode[] {
const nodes: TreeNode[] = [];

if (structureChecks !== undefined) {
for (const [i, check] of structureChecks.entries()) {
const hierarchy = check.getDirectoryHierarchy();
let currentNodes = nodes;

for (const [j, part] of hierarchy.entries()) {
let node = currentNodes.find((node) => node.label === part);

if (node === undefined) {
node = Project.newTreeNode(check, `${i}${j}`, part, j === hierarchy.length - 1);
currentNodes.push(node);
}

currentNodes = node.children ?? [];
}
}
}

return nodes;
}

/**
* Construct a tree node from a structure check folder path.
*
* @param check
* @param key
* @param label
* @param leaf
*/
private static newTreeNode(check: StructureCheck, key: string, label: string, leaf: boolean = false): TreeNode {
const node: TreeNode = {
key,
label,
data: check,
icon: PrimeIcons.FOLDER,
check: leaf,
children: [],
};

if (leaf) {
node.children = [
{
key: key + '-obligated',
icon: PrimeIcons.CHECK_CIRCLE,
data: check,
obligated: true,
},
{
key: key + '-blocked',
icon: PrimeIcons.TIMES_CIRCLE,
data: check,
blocked: true,
},
];
}

return node;
}

/**
* Convert a project object to a project instance.
*
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/views/projects/roles/StudentProjectView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ChooseGroupCard from '@/components/projects/groups/GroupChooseCard.vue';
import JoinedGroupCard from '@/components/projects/groups/GroupJoinedCard.vue';
import SubmissionCard from '@/components/submissions/SubmissionCard.vue';
import ProjectInfo from '@/components/projects/ProjectInfo.vue';
import ProjectStructure from '@/components/projects/ProjectStructure.vue';
import Title from '@/views/layout/Title.vue';
import Loading from '@/components/Loading.vue';
import { ref } from 'vue';
Expand All @@ -15,6 +16,8 @@ import { useSubmission } from '@/composables/services/submission.service.ts';
import { useProject } from '@/composables/services/project.service.ts';
import { useRoute } from 'vue-router';
import { type Project } from '@/types/Project.ts';
import { useStructureCheck } from '@/composables/services/structure_check.service.ts';
import Divider from 'primevue/divider';
/* Props */
defineProps<{
Expand All @@ -25,6 +28,7 @@ defineProps<{
const { params } = useRoute();
const { group, groups, getGroupByStudentProject, getGroupsByProject } = useGroup();
const { students, getStudentsByGroup, studentJoinGroup, studentLeaveGroup } = useStudents();
const { structureChecks, getStructureCheckByProject } = useStructureCheck();
const { project, getProjectByID } = useProject();
const { submissions, getSubmissionByGroup } = useSubmission();
Expand Down Expand Up @@ -90,6 +94,7 @@ async function getStudentData(project: Project): Promise<void> {
*/
async function getProjectData(project: Project): Promise<void> {
await getGroupsByProject(project.id);
await getStructureCheckByProject(project.id);
if (groups.value !== null) {
project.groups = groups.value;
Expand Down Expand Up @@ -124,6 +129,8 @@ watchImmediate(
<div class="col-12 md:col-8">
<ProjectInfo class="mb-3" :project="project" />
<div v-if="project" v-html="project.description" />
<Divider />
<ProjectStructure v-if="structureChecks" :structure-checks="structureChecks" />
</div>
<div class="col-12 md:col-4">
<template v-if="!loadingGroup">
Expand Down
20 changes: 15 additions & 5 deletions frontend/src/views/submissions/SubmissionsView.vue
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
<script setup lang="ts">
import BaseLayout from '@/views/layout/base/BaseLayout.vue';
import Title from '@/views/layout/Title.vue';
import Button from 'primevue/button';
import FileUpload from 'primevue/fileupload';
import { PrimeIcons } from 'primevue/api';
import Loading from '@/components/Loading.vue';
import { useI18n } from 'vue-i18n';
import { onMounted, ref } from 'vue';
import { useProject } from '@/composables/services/project.service.ts';
import { useRoute } from 'vue-router';
import FileUpload from 'primevue/fileupload';
import { PrimeIcons } from 'primevue/api';
import AllSubmission from '@/components/submissions/AllSubmission.vue';
import ProjectStructure from '@/components/projects/ProjectStructure.vue';
import { useGroup } from '@/composables/services/group.service.ts';
import { useSubmission } from '@/composables/services/submission.service.ts';
import { useStructureCheck } from '@/composables/services/structure_check.service.ts';
import { useMessagesStore } from '@/store/messages.store.ts';
import BaseLayout from '@/views/layout/base/BaseLayout.vue';
import Title from '@/views/layout/Title.vue';
const { t } = useI18n();
const route = useRoute();
const { project, getProjectByID } = useProject();
const { group, getGroupByID } = useGroup();
const { submission, submissions, createSubmission, getSubmissionByGroup } = useSubmission();
const { structureChecks, getStructureCheckByProject } = useStructureCheck();
const { addSuccessMessage, addErrorMessage } = useMessagesStore();
/* State */
Expand All @@ -32,7 +35,7 @@ const files = ref<File[]>([]);
const onUpload = async (callback: () => void): Promise<void> => {
if (group.value !== null) {
try {
await createSubmission(files.value as File[], group.value.id);
await createSubmission(files.value as File[], group.value.id, false);
addSuccessMessage(t('toasts.messages.success'), t('toasts.messages.submissions.create.success'));
if (submission.value != null) {
Expand Down Expand Up @@ -72,6 +75,9 @@ onMounted(async () => {
await getProjectByID(route.params.projectId as string);
await getGroupByID(route.params.groupId as string);
await getSubmissionByGroup(route.params.groupId as string);
if (project.value !== null) {
await getStructureCheckByProject(project.value.id);
}
});
</script>

Expand All @@ -83,6 +89,10 @@ onMounted(async () => {
<div class="col-12 md:col-6">
<div class="flex-column">
<!-- Project info column -->
<!-- Submission structure -->
<div v-if="structureChecks">
<ProjectStructure :structure-checks="structureChecks" />
</div>
<!-- Submission upload -->
<div class="py-2">
<h2>{{ t('views.submissions.submit') }}</h2>
Expand Down

0 comments on commit 998076f

Please sign in to comment.