feat: drag en drop werkt op mobiel

This commit is contained in:
Joyelle Ndagijimana 2025-05-17 00:04:31 +02:00
parent 45563b68ea
commit 862e72ef4a
4 changed files with 489 additions and 277 deletions

View file

@ -21,6 +21,7 @@
"@tanstack/vue-query": "^5.69.0", "@tanstack/vue-query": "^5.69.0",
"@vueuse/core": "^13.1.0", "@vueuse/core": "^13.1.0",
"axios": "^1.8.2", "axios": "^1.8.2",
"interactjs": "^1.10.27",
"oidc-client-ts": "^3.1.0", "oidc-client-ts": "^3.1.0",
"rollup": "^4.40.0", "rollup": "^4.40.0",
"uuid": "^11.1.0", "uuid": "^11.1.0",

View file

@ -1,243 +1,388 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, ref, watch } from "vue"; import {computed, ref, watch} from "vue";
import { useI18n } from "vue-i18n"; import {useI18n} from "vue-i18n";
import { useClassStudentsQuery } from "@/queries/classes"; import {useClassStudentsQuery} from "@/queries/classes";
const props = defineProps<{ const props = defineProps<{
classId: string | undefined; classId: string | undefined;
groups: object[]; groups: object[];
}>(); }>();
const emit = defineEmits(["close", "groupsUpdated", "done"]); const emit = defineEmits(["close", "groupsUpdated", "done"]);
const { t } = useI18n(); const {t} = useI18n();
interface StudentItem { interface StudentItem {
username: string; username: string;
fullName: string; fullName: string;
} }
const { data: studentsData } = useClassStudentsQuery(() => props.classId, true); const {data: studentsData} = useClassStudentsQuery(() => props.classId, true);
// Dialog states // Dialog states for group editing
const activeDialog = ref<"random" | "dragdrop" | null>(null); const activeDialog = ref<"random" | "dragdrop" | null>(null);
// Drag state // Drag state for the drag and drop
const draggedItem = ref<{groupIndex: number, studentIndex: number} | null>(null); const draggedItem = ref<{ groupIndex: number, studentIndex: number } | null>(null);
const currentGroups = ref<StudentItem[][]>([]); const currentGroups = ref<StudentItem[][]>([]);
const unassignedStudents = ref<StudentItem[]>([]); const unassignedStudents = ref<StudentItem[]>([]);
const allStudents = ref<StudentItem[]>([]); const allStudents = ref<StudentItem[]>([]);
// Random groups state // Random groups state
const groupSize = ref(1); const groupSize = ref(1);
const randomGroupsPreview = ref<StudentItem[][]>([]); const randomGroupsPreview = ref<StudentItem[][]>([]);
// Initialize data // Initialize data
watch( watch(
() => [studentsData.value, props.groups], () => [studentsData.value, props.groups],
([studentsVal, existingGroups]) => { ([studentsVal, existingGroups]) => {
if (!studentsVal) return; if (!studentsVal) return;
// Initialize all students // Initialize all students
allStudents.value = studentsVal.students.map((s) => ({ allStudents.value = studentsVal.students.map((s) => ({
username: s.username, username: s.username,
fullName: `${s.firstName} ${s.lastName}`, fullName: `${s.firstName} ${s.lastName}`,
})); }));
// Initialize groups if they exist // Initialize groups if they exist
if (existingGroups && existingGroups.length > 0) { if (existingGroups && existingGroups.length > 0) {
currentGroups.value = existingGroups.map((group) => currentGroups.value = existingGroups.map((group) =>
group.members.map(member => ({ group.members.map(member => ({
username: member.username, username: member.username,
fullName: `${member.firstName} ${member.lastName}` fullName: `${member.firstName} ${member.lastName}`
})) }))
); );
const assignedUsernames = new Set( const assignedUsernames = new Set(
existingGroups.flatMap((g) => g.members.map((m: StudentItem) => m.username)), existingGroups.flatMap((g) => g.members.map((m: StudentItem) => m.username)),
); );
unassignedStudents.value = allStudents.value.filter((s) => !assignedUsernames.has(s.username)); unassignedStudents.value = allStudents.value.filter((s) => !assignedUsernames.has(s.username));
} else {
// Default to all students unassigned
currentGroups.value = [];
unassignedStudents.value = [...allStudents.value];
}
// Initialize random preview with current groups
randomGroupsPreview.value = [...currentGroups.value];
},
{ immediate: true },
);
// Random groups functions
function generateRandomGroups() {
if (groupSize.value < 1) return;
// Shuffle students
const shuffled = [...allStudents.value].sort(() => Math.random() - 0.5);
// Create new groups
const newGroups: StudentItem[][] = [];
const groupCount = Math.ceil(shuffled.length / groupSize.value);
for (let i = 0; i < groupCount; i++) {
newGroups.push([]);
}
// Distribute students
shuffled.forEach((student, index) => {
const groupIndex = index % groupCount;
newGroups[groupIndex].push(student);
});
randomGroupsPreview.value = newGroups;
}
function saveRandomGroups() {
if (randomGroupsPreview.value.length === 0) {
alert(t("please-generate-groups-first"));
return;
}
emit(
"groupsUpdated",
randomGroupsPreview.value.map((g) => g.map((s) => s.username)),
);
activeDialog.value = null;
emit("done");
emit("close");
}
// Drag and drop functions
function addNewGroup() {
currentGroups.value.push([]);
}
function removeGroup(index: number) {
// Move students back to unassigned
unassignedStudents.value.push(...currentGroups.value[index]);
currentGroups.value.splice(index, 1);
}
// Native Drag & Drop Handlers
function handleDragStart(groupIndex: number, studentIndex: number) {
draggedItem.value = { groupIndex, studentIndex };
}
function handleDragOver(e: DragEvent, _: number) {
e.preventDefault();
e.dataTransfer!.dropEffect = "move";
}
function handleDrop(e: DragEvent, targetGroupIndex: number, targetStudentIndex?: number) {
e.preventDefault();
if (!draggedItem.value) return;
const { groupIndex: sourceGroupIndex, studentIndex: sourceStudentIndex } = draggedItem.value;
const isSameGroup = sourceGroupIndex === targetGroupIndex;
let sourceArray, targetArray;
// Determine source and target arrays
if (sourceGroupIndex === -1) {
sourceArray = unassignedStudents.value;
} else { } else {
sourceArray = currentGroups.value[sourceGroupIndex]; currentGroups.value = [];
unassignedStudents.value = [...allStudents.value];
} }
if (targetGroupIndex === -1) { randomGroupsPreview.value = [...currentGroups.value];
targetArray = unassignedStudents.value; },
} else { {immediate: true},
targetArray = currentGroups.value[targetGroupIndex]; );
}
// Remove from source /** Random groups functions */
const [movedStudent] = sourceArray.splice(sourceStudentIndex, 1); function generateRandomGroups(): void {
if (groupSize.value < 1) return;
// Add to target // Shuffle students
if (targetStudentIndex !== undefined) { const shuffled = [...allStudents.value].sort(() => Math.random() - 0.5);
targetArray.splice(targetStudentIndex, 0, movedStudent);
} else {
targetArray.push(movedStudent);
}
draggedItem.value = null; // Create new groups
const newGroups: StudentItem[][] = [];
const groupCount = Math.ceil(shuffled.length / groupSize.value);
for (let i = 0; i < groupCount; i++) {
newGroups.push([]);
} }
// Distribute students
function saveDragDrop() { shuffled.forEach((student, index) => {
if (unassignedStudents.value.length > 0) { const groupIndex = index % groupCount;
alert(t("please-assign-all-students")); newGroups[groupIndex].push(student);
return;
}
emit(
"groupsUpdated",
currentGroups.value.map((g) => g.map((s) => s.username)),
);
activeDialog.value = null;
emit("done");
emit("close");
}
// Preview current groups in the main view
const showGroupsPreview = computed(() => {
return currentGroups.value.length > 0 || unassignedStudents.value.length > 0;
}); });
function removeStudent(groupIndex, student) { randomGroupsPreview.value = newGroups;
const group = currentGroups.value[groupIndex]; }
currentGroups.value[groupIndex] = group.filter((s) => s.username !== student.username);
unassignedStudents.value.push(student); function saveRandomGroups(): void {
if (randomGroupsPreview.value.length === 0) {
alert(t("please-generate-groups-first"));
return;
} }
emit(
"groupsUpdated",
randomGroupsPreview.value.map((g) => g.map((s) => s.username)),
);
activeDialog.value = null;
emit("done");
emit("close");
}
function addNewGroup() {
currentGroups.value.push([]);
}
function removeGroup(index: number) {
// Move students back to unassigned
unassignedStudents.value.push(...currentGroups.value[index]);
currentGroups.value.splice(index, 1);
}
/** Drag and drop functions */
// Touch state interface
interface TouchState {
isDragging: boolean;
startX: number;
startY: number;
currentGroupIndex: number;
currentStudentIndex: number;
element: HTMLElement | null;
clone: HTMLElement | null;
originalRect: DOMRect | null;
hasMoved: boolean;
}
const touchState = ref<TouchState>({
isDragging: false,
startX: 0,
startY: 0,
currentGroupIndex: -1,
currentStudentIndex: -1,
element: null,
clone: null,
originalRect: null,
hasMoved: false
});
function handleTouchStart(event: TouchEvent, groupIndex: number, studentIndex: number): void {
if (event.touches.length > 1) return;
const touch = event.touches[0];
const target = event.target as HTMLElement;
// Target the chip directly instead of the draggable container
const chip = target.closest('.v-chip') as HTMLElement;
if (!chip) return;
// Get the chip's position relative to the viewport
const rect = chip.getBoundingClientRect();
touchState.value = {
isDragging: true,
startX: touch.clientX,
startY: touch.clientY,
currentGroupIndex: groupIndex,
currentStudentIndex: studentIndex,
element: chip,
clone: null,
originalRect: rect,
hasMoved: false
};
// Clone only the chip
const clone = chip.cloneNode(true) as HTMLElement;
clone.classList.add('drag-clone');
clone.style.position = 'fixed';
clone.style.zIndex = '10000';
clone.style.opacity = '0.9';
clone.style.pointerEvents = 'none';
clone.style.width = `${rect.width}px`;
clone.style.height = `${rect.height}px`;
clone.style.left = `${rect.left}px`;
clone.style.top = `${rect.top}px`;
clone.style.transform = 'scale(1.05)';
clone.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
clone.style.transition = 'transform 0.1s';
// Ensure the clone has the same chip styling
clone.style.backgroundColor = getComputedStyle(chip).backgroundColor;
clone.style.color = getComputedStyle(chip).color;
clone.style.borderRadius = getComputedStyle(chip).borderRadius;
clone.style.padding = getComputedStyle(chip).padding;
clone.style.margin = '0'; // Remove any margin
document.body.appendChild(clone);
touchState.value.clone = clone;
chip.style.visibility = 'hidden';
event.preventDefault();
event.stopPropagation();
}
function handleTouchMove(event: TouchEvent): void {
if (!touchState.value.isDragging || !touchState.value.clone || event.touches.length > 1) return;
const touch = event.touches[0];
const clone = touchState.value.clone;
const dx = Math.abs(touch.clientX - touchState.value.startX);
const dy = Math.abs(touch.clientY - touchState.value.startY);
if (dx > 5 || dy > 5) {
touchState.value.hasMoved = true;
}
clone.style.left = `${touch.clientX - clone.offsetWidth / 2}px`;
clone.style.top = `${touch.clientY - clone.offsetHeight / 2}px`;
document.querySelectorAll('.group-box').forEach(el => {
el.classList.remove('highlight');
});
const elements = document.elementsFromPoint(touch.clientX, touch.clientY);
const dropTarget = elements.find(el => el.classList.contains('group-box'));
if (dropTarget) {
dropTarget.classList.add('highlight');
}
event.preventDefault();
event.stopPropagation();
}
function handleTouchEnd(event: TouchEvent): void {
if (!touchState.value.isDragging) return;
const {
currentGroupIndex,
currentStudentIndex,
clone,
element,
hasMoved
} = touchState.value;
document.querySelectorAll('.group-box').forEach(el => {
el.classList.remove('highlight');
});
if (clone?.parentNode) {
clone.parentNode.removeChild(clone);
}
if (element) {
element.style.visibility = 'visible';
}
if (hasMoved && event.changedTouches.length > 0) {
const touch = event.changedTouches[0];
const elements = document.elementsFromPoint(touch.clientX, touch.clientY);
const dropTarget = elements.find(el => el.classList.contains('group-box'));
if (dropTarget) {
const groupBoxes = document.querySelectorAll('.group-box');
const targetGroupIndex = Array.from(groupBoxes).indexOf(dropTarget);
if (targetGroupIndex !== currentGroupIndex) {
const sourceArray = currentGroupIndex === -1
? unassignedStudents.value
: currentGroups.value[currentGroupIndex];
const targetArray = targetGroupIndex === -1
? unassignedStudents.value
: currentGroups.value[targetGroupIndex];
if (sourceArray && targetArray) {
const [movedStudent] = sourceArray.splice(currentStudentIndex, 1);
targetArray.push(movedStudent);
}
}
}
}
touchState.value = {
isDragging: false,
startX: 0,
startY: 0,
currentGroupIndex: -1,
currentStudentIndex: -1,
element: null,
clone: null,
originalRect: null,
hasMoved: false
};
event.preventDefault();
event.stopPropagation();
}
function handleDragStart(event: DragEvent, groupIndex: number, studentIndex: number): void {
draggedItem.value = {groupIndex, studentIndex};
if (event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.setData('text/plain', '');
}
}
function handleDragOver(e: DragEvent, _: number): void {
e.preventDefault();
if (e.dataTransfer) {
e.dataTransfer.dropEffect = "move";
}
}
function handleDrop(e: DragEvent, targetGroupIndex: number, targetStudentIndex?: number): void {
e.preventDefault();
if (!draggedItem.value) return;
const {groupIndex: sourceGroupIndex, studentIndex: sourceStudentIndex} = draggedItem.value;
const sourceArray = sourceGroupIndex === -1
? unassignedStudents.value
: currentGroups.value[sourceGroupIndex];
const targetArray = targetGroupIndex === -1
? unassignedStudents.value
: currentGroups.value[targetGroupIndex];
const [movedStudent] = sourceArray.splice(sourceStudentIndex, 1);
if (targetStudentIndex !== undefined) {
targetArray.splice(targetStudentIndex, 0, movedStudent);
} else {
targetArray.push(movedStudent);
}
draggedItem.value = null;
}
function saveDragDrop(): void {
if (unassignedStudents.value.length > 0) {
alert(t("please-assign-all-students"));
return;
}
emit(
"groupsUpdated",
currentGroups.value.map((g) => g.map((s) => s.username)),
);
activeDialog.value = null;
emit("done");
emit("close");
}
const showGroupsPreview = computed(() => {
return currentGroups.value.length > 0 || unassignedStudents.value.length > 0;
});
function removeStudent(groupIndex: number, student: StudentItem): void {
const group = currentGroups.value[groupIndex];
currentGroups.value[groupIndex] = group.filter((s) => s.username !== student.username);
unassignedStudents.value.push(student);
}
</script> </script>
<template> <template>
<v-card class="pa-4"> <v-card class="pa-4">
<!-- Current Groups Preview --> <!-- Current groups and unassigned students Preview -->
<div <div v-if="showGroupsPreview" class="mb-6">
v-if="showGroupsPreview"
class="mb-6"
>
<h3 class="mb-2">{{ t("current-groups") }}</h3> <h3 class="mb-2">{{ t("current-groups") }}</h3>
<div <div>
>
<div class="d-flex flex-wrap"> <div class="d-flex flex-wrap">
<label>{{currentGroups.length}}</label> <label>{{ currentGroups.length }}</label>
</div> </div>
</div> </div>
<div <div v-if="unassignedStudents.length > 0" class="mt-3">
v-if="unassignedStudents.length > 0"
class="mt-3"
>
<strong>{{ t("unassigned-students") }}:</strong> <strong>{{ t("unassigned-students") }}:</strong>
<div class="d-flex flex-wrap"> <div class="d-flex flex-wrap">
<label>{{unassignedStudents.length}}</label> <label>{{ unassignedStudents.length }}</label>
</div> </div>
</div> </div>
</div> </div>
<!-- Action Buttons --> <v-row justify="center" class="mb-4">
<v-row <v-btn color="primary" @click="activeDialog = 'random'">
justify="center"
class="mb-4"
>
<v-btn
color="primary"
@click="activeDialog = 'random'"
>
{{ t("randomly-create-groups") }} {{ t("randomly-create-groups") }}
</v-btn> </v-btn>
<v-btn <v-btn color="secondary" class="ml-4" @click="activeDialog = 'dragdrop'">
color="secondary"
class="ml-4"
@click="activeDialog = 'dragdrop'"
>
{{ t("drag-and-drop") }} {{ t("drag-and-drop") }}
</v-btn> </v-btn>
</v-row> </v-row>
<!-- Random Groups Dialog --> <!-- Random Groups selection Dialog -->
<v-dialog <v-dialog
:model-value="activeDialog === 'random'" :model-value="activeDialog === 'random'"
@update:model-value="(val) => (val ? (activeDialog = 'random') : (activeDialog = null))" @update:model-value="(val) => (val ? (activeDialog = 'random') : (activeDialog = null))"
@ -298,12 +443,8 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer/>
<v-btn <v-btn text @click="activeDialog = null">{{ t("cancel") }}</v-btn>
text
@click="activeDialog = null"
>{{ t("cancel") }}</v-btn
>
<v-btn <v-btn
color="success" color="success"
@click="saveRandomGroups" @click="saveRandomGroups"
@ -356,7 +497,10 @@
:key="student.username" :key="student.username"
class="draggable-item ma-1" class="draggable-item ma-1"
draggable="true" draggable="true"
@dragstart="handleDragStart(groupIndex, studentIndex)" @touchstart="handleTouchStart($event, groupIndex, studentIndex)"
@touchmove="handleTouchMove($event)"
@touchend="handleTouchEnd($event)"
@dragstart="handleDragStart($event, groupIndex, studentIndex)"
@dragover.prevent="handleDragOver($event, groupIndex)" @dragover.prevent="handleDragOver($event, groupIndex)"
@drop="handleDrop($event, groupIndex, studentIndex)" @drop="handleDrop($event, groupIndex, studentIndex)"
> >
@ -387,7 +531,10 @@
:key="student.username" :key="student.username"
class="draggable-item ma-1" class="draggable-item ma-1"
draggable="true" draggable="true"
@dragstart="handleDragStart(-1, studentIndex)" @touchstart="handleTouchStart($event, -1, studentIndex)"
@touchmove="handleTouchMove($event)"
@touchend="handleTouchEnd($event)"
@dragstart="handleDragStart($event, -1, studentIndex)"
@dragover.prevent="handleDragOver($event, -1)" @dragover.prevent="handleDragOver($event, -1)"
@drop="handleDrop($event, -1, studentIndex)" @drop="handleDrop($event, -1, studentIndex)"
> >
@ -399,7 +546,7 @@
</v-card-text> </v-card-text>
<v-card-actions> <v-card-actions>
<v-spacer /> <v-spacer/>
<v-btn text @click="activeDialog = null">{{ t("cancel") }}</v-btn> <v-btn text @click="activeDialog = null">{{ t("cancel") }}</v-btn>
<v-btn <v-btn
color="primary" color="primary"
@ -415,16 +562,45 @@
</template> </template>
<style scoped> <style scoped>
.group-box { .group-box {
min-height: 150px; min-height: 100px;
max-height: 300px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
background-color: #fafafa; background-color: #fafafa;
border-radius: 4px; border-radius: 4px;
} transition: all 0.2s;
}
.v-expansion-panel-text { .group-box.highlight {
max-height: 200px; background-color: #e3f2fd;
overflow-y: auto; border: 2px dashed #2196f3;
} }
.v-expansion-panel-text {
max-height: 200px;
overflow-y: auto;
}
.drag-clone {
z-index: 10000;
transform: scale(1.05);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
transition: transform 0.1s;
will-change: transform;
pointer-events: none;
display: inline-flex;
align-items: center;
justify-content: center;
border-radius: 16px;
background-color: inherit;
}
.draggable-item {
display: inline-block;
}
.draggable-item .v-chip[style*="hidden"] {
visibility: hidden;
display: inline-block;
}
</style> </style>

View file

@ -344,69 +344,71 @@ async function saveChanges(): Promise<void> {
md="6" md="6"
class="responsive-col" class="responsive-col"
> >
<v-table class="table"> <div class="table-container">
<thead> <v-table class="table">
<tr> <thead>
<th class="header">{{ t("group") }}</th> <tr>
<th class="header">{{ t("progress") }}</th> <th class="header">{{ t("group") }}</th>
<th class="header">{{ t("submission") }}</th> <th class="header">{{ t("progress") }}</th>
<th class="header"> <th class="header">{{ t("submission") }}</th>
<v-btn <th class="header">
@click="editGroups = true" <v-btn
variant="text" @click="editGroups = true"
> variant="text"
<v-icon>mdi-pencil</v-icon> >
</v-btn> <v-icon>mdi-pencil</v-icon>
</th> </v-btn>
</tr> </th>
</thead> </tr>
<tbody> </thead>
<tr <tbody>
v-for="g in allGroups" <tr
:key="g.originalGroupNo" v-for="g in allGroups"
> :key="g.originalGroupNo"
<td> >
<v-btn <td>
@click="openGroupDetails(g)" <v-btn
variant="text" @click="openGroupDetails(g)"
> variant="text"
{{ g.name }} >
<v-icon end>mdi-menu-right</v-icon> {{ g.name }}
</v-btn> <v-icon end>mdi-menu-right</v-icon>
</td> </v-btn>
</td>
<td> <td>
<GroupProgressRow <GroupProgressRow
:group-number="g.originalGroupNo" :group-number="g.originalGroupNo"
:learning-path="learningPath" :learning-path="learningPath"
:language="lang" :language="lang"
:assignment-id="assignmentId" :assignment-id="assignmentId"
:class-id="classId" :class-id="classId"
/> />
</td> </td>
<td> <td>
<GroupSubmissionStatus <GroupSubmissionStatus
:group="g" :group="g"
:assignment-id="assignmentId" :assignment-id="assignmentId"
:class-id="classId" :class-id="classId"
:language="lang" :language="lang"
:go-to-group-submission-link="goToGroupSubmissionLink" :go-to-group-submission-link="goToGroupSubmissionLink"
/> />
</td> </td>
<!-- Edit icon --> <!-- Edit icon -->
<td> <td>
<v-btn <v-btn
@click="" @click=""
variant="text" variant="text"
> >
<v-icon color="red">mdi-delete</v-icon> <v-icon color="red">mdi-delete</v-icon>
</v-btn> </v-btn>
</td> </td>
</tr> </tr>
</tbody> </tbody>
</v-table> </v-table>
</div>
</v-col> </v-col>
</v-row> </v-row>
<v-dialog <v-dialog
@ -510,6 +512,17 @@ main {
margin-left: 30px; margin-left: 30px;
} }
.table-container {
width: 100%;
overflow-x: visible;
}
.table {
width: 100%;
min-width: auto;
table-layout: auto;
}
@media screen and (max-width: 850px) { @media screen and (max-width: 850px) {
h1 { h1 {
text-align: center; text-align: center;
@ -540,6 +553,12 @@ main {
.table { .table {
width: 100%; width: 100%;
display: block;
overflow-x: auto;
}
.table-container {
overflow-x: auto;
} }
.responsive-col { .responsive-col {

16
package-lock.json generated
View file

@ -101,6 +101,7 @@
"@tanstack/vue-query": "^5.69.0", "@tanstack/vue-query": "^5.69.0",
"@vueuse/core": "^13.1.0", "@vueuse/core": "^13.1.0",
"axios": "^1.8.2", "axios": "^1.8.2",
"interactjs": "^1.10.27",
"oidc-client-ts": "^3.1.0", "oidc-client-ts": "^3.1.0",
"rollup": "^4.40.0", "rollup": "^4.40.0",
"uuid": "^11.1.0", "uuid": "^11.1.0",
@ -1596,6 +1597,12 @@
"url": "https://github.com/sponsors/nzakas" "url": "https://github.com/sponsors/nzakas"
} }
}, },
"node_modules/@interactjs/types": {
"version": "1.10.27",
"resolved": "https://registry.npmjs.org/@interactjs/types/-/types-1.10.27.tgz",
"integrity": "sha512-BUdv0cvs4H5ODuwft2Xp4eL8Vmi3LcihK42z0Ft/FbVJZoRioBsxH+LlsBdK4tAie7PqlKGy+1oyOncu1nQ6eA==",
"license": "MIT"
},
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "11.1.3", "version": "11.1.3",
"resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.3.tgz", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-11.1.3.tgz",
@ -6830,6 +6837,15 @@
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/interactjs": {
"version": "1.10.27",
"resolved": "https://registry.npmjs.org/interactjs/-/interactjs-1.10.27.tgz",
"integrity": "sha512-y/8RcCftGAF24gSp76X2JS3XpHiUvDQyhF8i7ujemBz77hwiHDuJzftHx7thY8cxGogwGiPJ+o97kWB6eAXnsA==",
"license": "MIT",
"dependencies": {
"@interactjs/types": "1.10.27"
}
},
"node_modules/interpret": { "node_modules/interpret": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",