diff --git a/.gitignore b/.gitignore
index 9efcf43f4..9ad03aedd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,7 @@ webpack-stats.json
# Installed Widgets
public/widget/**/*
+public/site_img/**/*
spec/widgets/*
# Any widget zip packages
diff --git a/app/api/serializers.py b/app/api/serializers.py
index f4936e6dd..510f23272 100644
--- a/app/api/serializers.py
+++ b/app/api/serializers.py
@@ -15,6 +15,8 @@
Lti,
Notification,
ObjectPermission,
+ SiteImage,
+ SiteMessage,
UserExtraAttempts,
UserSettings,
Widget,
@@ -112,13 +114,17 @@ def validate(self, data):
if not user:
raise serializers.ValidationError("User ID invalid.")
- valid_keys = ["useGravatar", "notify", "theme", "beardMode"]
+ valid_keys = ["useGravatar", "profileImage", "notify", "theme", "beardMode"]
for key, value in data["profile_fields"].items():
if key not in valid_keys:
raise serializers.ValidationError(
f"Invalid profile field provided: {key}"
)
+ if key == "profileImage" and not isinstance(value, int):
+ raise serializers.ValidationError(
+ f"Invalid value for {key}, must be integer."
+ )
if key == "theme" and value not in ["dark", "light", "os"]:
raise serializers.ValidationError(
f"Invalid value for darkMode: {value}"
@@ -875,3 +881,87 @@ class PlayStorageSaveSerializer(serializers.Serializer):
queryset=LogPlay.objects.all(), required=True
)
logs = serializers.JSONField()
+
+
+class SiteImageSerializer(serializers.ModelSerializer):
+ image = serializers.ImageField(write_only=True, required=True)
+ image_type = serializers.ChoiceField(
+ choices=SiteImage.ImageType.choices, required=True
+ )
+
+ class Meta:
+ model = SiteImage
+ fields = ["id", "image_type", "image_path", "image"]
+ read_only_fields = ["id", "image_path"]
+
+ def create(self, validated_data):
+ import os
+ import uuid
+
+ from django.conf import settings
+ from PIL import Image
+
+ image_file = validated_data.pop("image")
+ image_type = validated_data.get("image_type")
+
+ ext = os.path.splitext(image_file.name)[1]
+ filename = f"{image_type.lower()}_{uuid.uuid4()}{ext}"
+
+ site_images_dir = settings.DIRS.get(
+ "site_images", os.path.join(settings.BASE_DIR, "staticfiles", "site_img")
+ )
+ os.makedirs(site_images_dir, exist_ok=True)
+
+ file_path = os.path.join(site_images_dir, filename)
+
+ # Resize profile images to max 960px on any side
+ if image_type == SiteImage.ImageType.PROFILE_IMAGE:
+ img = Image.open(image_file)
+
+ # Convert RGBA to RGB if necessary (for JPEG compatibility)
+ if img.mode in ("RGBA", "LA", "P"):
+ background = Image.new("RGB", img.size, (255, 255, 255))
+ background.paste(
+ img, mask=img.split()[-1] if img.mode == "RGBA" else None
+ )
+ img = background
+
+ max_dimension = 640
+ width, height = img.size
+
+ if width > max_dimension or height > max_dimension:
+ if width > height:
+ new_width = max_dimension
+ new_height = int((max_dimension / width) * height)
+ else:
+ new_height = max_dimension
+ new_width = int((max_dimension / height) * width)
+
+ img = img.resize((new_width, new_height), Image.LANCZOS)
+
+ img.save(file_path, quality=90, optimize=True)
+ else:
+ # Save the file as-is for non-profile images
+ with open(file_path, "wb+") as destination:
+ for chunk in image_file.chunks():
+ destination.write(chunk)
+
+ validated_data["image_path"] = f"/site_img/{filename}"
+
+ return super().create(validated_data)
+
+
+class SiteMessageSerializer(serializers.ModelSerializer):
+
+ message_text = serializers.CharField(required=True)
+ message_type = serializers.ChoiceField(
+ choices=SiteMessage.MessageType.choices, required=True
+ )
+
+ start_at = serializers.DateTimeField(required=False, allow_null=True)
+ end_at = serializers.DateTimeField(required=False, allow_null=True)
+
+ class Meta:
+ model = SiteMessage
+ fields = ["id", "message_type", "message_text", "start_at", "end_at"]
+ read_only_fields = ["id"]
diff --git a/app/api/tests/test_site_views.py b/app/api/tests/test_site_views.py
new file mode 100644
index 000000000..a3fab5689
--- /dev/null
+++ b/app/api/tests/test_site_views.py
@@ -0,0 +1,181 @@
+import io
+import tempfile
+
+from api.tests.base import MateriaTestCase
+from core.models import SiteImage
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.core.files.uploadedfile import SimpleUploadedFile
+from PIL import Image
+from rest_framework import status
+from rest_framework.test import APIClient
+
+
+class SiteImageViewSetTestCase(MateriaTestCase):
+ @classmethod
+ def setUpTestData(cls):
+ super().setUpTestData()
+
+ cls.regular_user = User.objects.create_user(
+ username="regular_site",
+ email="regular_site@example.com",
+ password="testpass123",
+ )
+
+ cls.superuser = User.objects.create_superuser(
+ username="admin_site",
+ email="admin_site@example.com",
+ password="testpass123",
+ )
+
+ cls.profile_image = SiteImage.objects.create(
+ image_type=SiteImage.ImageType.PROFILE_IMAGE,
+ image_path="/site_img/profile_seed.png",
+ )
+ cls.catalog_banner = SiteImage.objects.create(
+ image_type=SiteImage.ImageType.CATALOG_BANNER,
+ image_path="/site_img/banner_seed.png",
+ )
+
+ def setUp(self):
+ self.client = APIClient()
+
+ @staticmethod
+ def make_uploaded_image(filename="test.png", size=(20, 20)):
+ image_stream = io.BytesIO()
+ image = Image.new("RGB", size, color=(73, 109, 137))
+ image.save(image_stream, format="PNG")
+ image_stream.seek(0)
+
+ return SimpleUploadedFile(
+ name=filename,
+ content=image_stream.read(),
+ content_type="image/png",
+ )
+
+
+class TestSiteImageList(SiteImageViewSetTestCase):
+ def test_unauthenticated_can_list(self):
+ response = self.client.get("/api/site-images/")
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ returned_ids = [item["id"] for item in response.data]
+ self.assertIn(self.profile_image.id, returned_ids)
+ self.assertIn(self.catalog_banner.id, returned_ids)
+
+ def test_regular_user_can_list(self):
+ self.client.force_authenticate(user=self.regular_user)
+ response = self.client.get("/api/site-images/")
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ returned_ids = [item["id"] for item in response.data]
+ self.assertIn(self.profile_image.id, returned_ids)
+ self.assertIn(self.catalog_banner.id, returned_ids)
+
+ def test_superuser_can_list(self):
+ self.client.force_authenticate(user=self.superuser)
+ response = self.client.get("/api/site-images/")
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ returned_ids = [item["id"] for item in response.data]
+ self.assertIn(self.profile_image.id, returned_ids)
+ self.assertIn(self.catalog_banner.id, returned_ids)
+
+ def test_list_can_filter_by_type(self):
+ response = self.client.get(
+ "/api/site-images/", {"type": SiteImage.ImageType.PROFILE_IMAGE}
+ )
+
+ self.assertEqual(response.status_code, status.HTTP_200_OK)
+ self.assertGreater(len(response.data), 0)
+ self.assertTrue(
+ all(
+ item["image_type"] == SiteImage.ImageType.PROFILE_IMAGE
+ for item in response.data
+ )
+ )
+ returned_ids = [item["id"] for item in response.data]
+ self.assertIn(self.profile_image.id, returned_ids)
+ self.assertNotIn(self.catalog_banner.id, returned_ids)
+
+
+class TestSiteImageCreate(SiteImageViewSetTestCase):
+ def setUp(self):
+ super().setUp()
+
+ self.temp_dir = tempfile.TemporaryDirectory()
+ self.addCleanup(self.temp_dir.cleanup)
+
+ self.original_site_images_dir = settings.DIRS.get("site_images")
+ settings.DIRS["site_images"] = self.temp_dir.name
+ self.addCleanup(self._restore_site_images_dir)
+
+ def _restore_site_images_dir(self):
+ if self.original_site_images_dir is None:
+ settings.DIRS.pop("site_images", None)
+ else:
+ settings.DIRS["site_images"] = self.original_site_images_dir
+
+ def test_superuser_can_create(self):
+ self.client.force_authenticate(user=self.superuser)
+ before_count = SiteImage.objects.count()
+
+ payload = {
+ "image_type": SiteImage.ImageType.PROFILE_IMAGE,
+ "image": self.make_uploaded_image(),
+ }
+ response = self.client.post("/api/site-images/", payload, format="multipart")
+
+ self.assertEqual(response.status_code, status.HTTP_201_CREATED)
+ self.assertEqual(SiteImage.objects.count(), before_count + 1)
+
+ def test_regular_user_cannot_create(self):
+ self.client.force_authenticate(user=self.regular_user)
+ before_count = SiteImage.objects.count()
+
+ payload = {
+ "image_type": SiteImage.ImageType.CATALOG_BANNER,
+ "image": self.make_uploaded_image(filename="banner.png"),
+ }
+ response = self.client.post("/api/site-images/", payload, format="multipart")
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ self.assertEqual(SiteImage.objects.count(), before_count)
+
+ def test_unauthenticated_cannot_create(self):
+ before_count = SiteImage.objects.count()
+
+ payload = {
+ "image_type": SiteImage.ImageType.CATALOG_BANNER,
+ "image": self.make_uploaded_image(filename="banner_unauth.png"),
+ }
+ response = self.client.post("/api/site-images/", payload, format="multipart")
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ self.assertEqual(SiteImage.objects.count(), before_count)
+
+
+class TestSiteImageDestroy(SiteImageViewSetTestCase):
+ def test_superuser_can_delete(self):
+ target = SiteImage.objects.create(
+ image_type=SiteImage.ImageType.CATALOG_BANNER,
+ image_path="/site_img/delete_me.png",
+ )
+ self.client.force_authenticate(user=self.superuser)
+
+ response = self.client.delete(f"/api/site-images/{target.id}/")
+
+ self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
+ self.assertFalse(SiteImage.objects.filter(id=target.id).exists())
+
+ def test_non_privileged_user_cannot_delete(self):
+ target = SiteImage.objects.create(
+ image_type=SiteImage.ImageType.CATALOG_BANNER,
+ image_path="/site_img/keep_me.png",
+ )
+ self.client.force_authenticate(user=self.regular_user)
+
+ response = self.client.delete(f"/api/site-images/{target.id}/")
+
+ self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
+ self.assertTrue(SiteImage.objects.filter(id=target.id).exists())
diff --git a/app/api/urls/api_urls.py b/app/api/urls/api_urls.py
index aadd51bfb..5a5a57a81 100644
--- a/app/api/urls/api_urls.py
+++ b/app/api/urls/api_urls.py
@@ -7,6 +7,7 @@
playstorage,
scores,
sessions,
+ site,
users,
widget_instances,
widgets,
@@ -26,6 +27,8 @@
)
router.register(r"notifications", notifications.NotificationsViewSet)
router.register(r"extra-attempts", extra_attempts.UserExtraAttemptsViewSet)
+router.register(r"site-images", site.SiteImageViewSet)
+router.register(r"site-messages", site.SiteMessageViewSet)
urlpatterns = [
path("", include(router.urls)),
diff --git a/app/api/views/site.py b/app/api/views/site.py
new file mode 100644
index 000000000..b647696b1
--- /dev/null
+++ b/app/api/views/site.py
@@ -0,0 +1,77 @@
+from api.permissions import IsSuperuser
+from api.serializers import SiteImageSerializer, SiteMessageSerializer
+from core.models import SiteImage, SiteMessage
+from django.db.models import Q
+from django.utils import timezone
+from rest_framework import status, viewsets
+from rest_framework.permissions import AllowAny
+from rest_framework.response import Response
+
+
+class SiteImageViewSet(viewsets.ModelViewSet):
+
+ queryset = SiteImage.objects.all()
+ serializer_class = SiteImageSerializer
+ http_method_names = ["get", "post", "delete"]
+
+ def get_permissions(self):
+ if self.action in ["list", "retrieve"]:
+ permission_classes = [AllowAny]
+ else:
+ permission_classes = [IsSuperuser]
+ return [permission() for permission in permission_classes]
+
+ def get_queryset(self):
+ queryset = SiteImage.objects.all()
+ image_type = self.request.query_params.get("type", None)
+
+ if image_type:
+ queryset = queryset.filter(image_type=image_type)
+
+ return queryset
+
+ def create(self, request, *args, **kwargs):
+ serializer = self.get_serializer(data=request.data)
+ serializer.is_valid(raise_exception=True)
+ self.perform_create(serializer)
+
+ headers = self.get_success_headers(serializer.data)
+ return Response(
+ serializer.data, status=status.HTTP_201_CREATED, headers=headers
+ )
+
+
+class SiteMessageViewSet(viewsets.ModelViewSet):
+
+ queryset = SiteMessage.objects.all()
+ serializer_class = SiteMessageSerializer
+ http_method_names = ["get", "post", "delete"]
+
+ def get_permissions(self):
+ if self.action in ["list", "retrieve"]:
+ permission_classes = [AllowAny]
+ else:
+ permission_classes = [IsSuperuser]
+ return [permission() for permission in permission_classes]
+
+ def get_queryset(self):
+ queryset = SiteMessage.objects.all()
+ msg_type = self.request.query_params.get("type", None)
+ msg_types = self.request.query_params.get("types", None)
+ include_expired = self.request.query_params.get("include_expired", "false")
+
+ if msg_types:
+ queryset = queryset.filter(message_type__in=msg_types.split(","))
+
+ if msg_type:
+ queryset = queryset.filter(message_type=msg_type)
+
+ include_expired = str(include_expired).lower() in ["1", "true"]
+ if not include_expired:
+ now = timezone.now()
+ queryset = queryset.filter(
+ (Q(start_at__isnull=True) | Q(start_at__lte=now))
+ & (Q(end_at__isnull=True) | Q(end_at__gte=now))
+ )
+
+ return queryset
diff --git a/app/api/views/users.py b/app/api/views/users.py
index b07c86f27..f32e1764f 100644
--- a/app/api/views/users.py
+++ b/app/api/views/users.py
@@ -152,7 +152,7 @@ def me(self, request):
serializer = self.get_serializer(request.user)
return Response(serializer.data)
- @action(detail=True, methods=["put"])
+ @action(detail=True, methods=["patch"])
def profile_fields(self, request, pk=None):
user = self.get_object()
serializer = UserMetadataSerializer(data=request.data)
@@ -165,11 +165,6 @@ def profile_fields(self, request, pk=None):
for key, value in validated.items():
profile_fields[key] = value
- # if key == "darkMode":
- # cache_key = f'user_dark_mode_{request.user.id}'
- # logger.error(f"located darkMode key for user {request.user.id} and deleting cache !!!")
- # cache.delete(cache_key)
-
user_profile.profile_fields = profile_fields
user_profile.save()
diff --git a/app/api/views/widget_instances.py b/app/api/views/widget_instances.py
index 5d3b41193..58f713f63 100644
--- a/app/api/views/widget_instances.py
+++ b/app/api/views/widget_instances.py
@@ -635,4 +635,12 @@ def undelete(self, request, pk=None):
instance.is_deleted = False
instance.save()
+ for shared_user_perm in instance.permissions.all():
+ Notification.create_instance_notification(
+ from_user=self.request.user,
+ to_user=shared_user_perm.user,
+ instance=instance,
+ mode="restored",
+ )
+
return Response({"success": True})
diff --git a/app/core/migrations/0029_siteimage.py b/app/core/migrations/0029_siteimage.py
new file mode 100644
index 000000000..65e64fd6d
--- /dev/null
+++ b/app/core/migrations/0029_siteimage.py
@@ -0,0 +1,125 @@
+# Generated by Django 5.0.1 on 2026-04-30 18:28
+
+import os
+import shutil
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+def populate_default_profile_images(apps, schema_editor):
+ """
+ Adds default profile images to the SiteImage ORM
+ """
+ SiteImage = apps.get_model("core", "SiteImage")
+
+ base_dir = settings.APP_PATH
+ source_dir = os.path.join(
+ base_dir, "staticfiles", "img", "default", "profile_images"
+ )
+ dest_dir = os.path.join(base_dir, "staticfiles", "site_img")
+
+ os.makedirs(dest_dir, exist_ok=True)
+
+ default_images = [
+ "aura_kogneato.png",
+ "dm_kogneato.png",
+ "gambit_kogneato.png",
+ "in_kogneato.png",
+ "kogneato_the_riveter.png",
+ "metal_kogneato.png",
+ "morpheus_kogneato.png",
+ "shakespeare_kogneato.png",
+ ]
+
+ images_to_create = []
+
+ for filename in default_images:
+ source_path = os.path.join(source_dir, filename)
+ dest_path = os.path.join(dest_dir, filename)
+ image_path = f"/site_img/{filename}"
+
+ if SiteImage.objects.filter(image_path=image_path).exists():
+ continue
+
+ if not os.path.exists(dest_path):
+ if os.path.exists(source_path):
+ shutil.copy2(source_path, dest_path)
+ else:
+ continue
+
+ images_to_create.append(
+ SiteImage(image_type="PROFILE_IMAGE", image_path=image_path)
+ )
+
+ if images_to_create:
+ SiteImage.objects.bulk_create(images_to_create)
+
+
+def remove_default_profile_images(apps, schema_editor):
+ """
+ Removes default images from the staticfiles/site_img directory.
+ """
+ base_dir = settings.APP_PATH
+ dest_dir = os.path.join(base_dir, "staticfiles", "site_img")
+
+ default_images = [
+ "aura_kogneato.png",
+ "dm_kogneato.png",
+ "gambit_kogneato.png",
+ "in_kogneato.png",
+ "kogneato_the_riveter.png",
+ "metal_kogneato.png",
+ "morpheus_kogneato.png",
+ "shakespeare_kogneato.png",
+ ]
+
+ for filename in default_images:
+ dest_path = os.path.join(dest_dir, filename)
+ if os.path.exists(dest_path):
+ try:
+ os.remove(dest_path)
+ except FileNotFoundError:
+ pass
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("core", "0028_remove_logplay_log_play_is_complete_and_more"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="SiteImage",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "image_type",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("NO_TYPE", "No Type"),
+ ("PROFILE_IMAGE", "Profile Image"),
+ ("CATALOG_BANNER", "Catalog Banner"),
+ ],
+ default="NO_TYPE",
+ max_length=26,
+ null=True,
+ ),
+ ),
+ ("image_path", models.CharField(max_length=255)),
+ ],
+ ),
+ migrations.RunPython(
+ populate_default_profile_images, remove_default_profile_images
+ ),
+ ]
diff --git a/app/core/migrations/0030_sitemessage.py b/app/core/migrations/0030_sitemessage.py
new file mode 100644
index 000000000..0e518ed81
--- /dev/null
+++ b/app/core/migrations/0030_sitemessage.py
@@ -0,0 +1,46 @@
+# Generated by Django 5.0.1 on 2026-06-09 15:27
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("core", "0029_siteimage"),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name="SiteMessage",
+ fields=[
+ (
+ "id",
+ models.BigAutoField(
+ auto_created=True,
+ primary_key=True,
+ serialize=False,
+ verbose_name="ID",
+ ),
+ ),
+ (
+ "message_type",
+ models.CharField(
+ blank=True,
+ choices=[
+ ("NO_TYPE", "No Type"),
+ ("SITE_NOTIFICATION", "Site Notification"),
+ ("SITE_ALERT", "Site Alert"),
+ ("CATALOG_HEADER", "Catalog Header"),
+ ("CATALOG_TEXT", "Catalog Text"),
+ ],
+ default="NO_TYPE",
+ max_length=26,
+ null=True,
+ ),
+ ),
+ ("message_text", models.TextField()),
+ ("start_at", models.DateTimeField(default=None, null=True)),
+ ("end_at", models.DateTimeField(default=None, null=True)),
+ ],
+ ),
+ ]
diff --git a/app/core/mixins.py b/app/core/mixins.py
index 96737b44c..1af293522 100644
--- a/app/core/mixins.py
+++ b/app/core/mixins.py
@@ -5,6 +5,7 @@
from core.utils.context_util import ContextUtil
from django.contrib.auth.mixins import AccessMixin
from django.http import Http404, HttpRequest, HttpResponse
+from pylti1p3.exception import LtiException
logger = logging.getLogger(__name__)
@@ -118,7 +119,14 @@ def dispatch(self, request, *args, **kwargs):
if not instance:
raise Http404("A widget instance with this ID does not exist.")
is_embedded = kwargs.get("is_embed", False)
- validation = self.get_validation(request, instance)
+ try:
+ validation = self.get_validation(request, instance)
+ except LtiException:
+ from core.services.widget_play_services import (
+ WidgetPlayValidationService,
+ )
+
+ validation = WidgetPlayValidationService.INVALID_RECOVERY_TOKEN
request._widget_play_state = {
"instance": instance,
diff --git a/app/core/models.py b/app/core/models.py
index 79f752d6b..5dcdd4244 100644
--- a/app/core/models.py
+++ b/app/core/models.py
@@ -707,7 +707,12 @@ def save(self, *args, **kwargs):
)
# Send email, if not sent already
- self.send_email()
+ try:
+ self.send_email()
+ except Exception:
+ logger.error(
+ "Failed to send email for user %s:", self.to_id.id, exc_info=True
+ )
@classmethod
def create_instance_notification(
@@ -762,6 +767,8 @@ def create_instance_notification(
f"The widget is currently being used within a course in your LMS."
)
action = "access_request"
+ case "restored":
+ content = f"{user_link} restored {widget_type} widget '{widget_name}'."
case _:
return None
@@ -888,6 +895,48 @@ class Meta:
db_table = "question"
+class SiteImage(models.Model):
+
+ class ImageType(models.TextChoices):
+ NO_TYPE = "NO_TYPE", gettext_lazy("No Type")
+ PROFILE_IMAGE = "PROFILE_IMAGE", gettext_lazy("Profile Image")
+ # LIBRARY_BANNER = "LIBRARY_BANNER", gettext_lazy("Library Banner")
+ CATALOG_BANNER = "CATALOG_BANNER", gettext_lazy("Catalog Banner")
+
+ image_type = models.CharField(
+ max_length=26,
+ blank=True,
+ null=True,
+ choices=ImageType.choices,
+ default=ImageType.NO_TYPE,
+ )
+
+ image_path = models.CharField(max_length=255)
+
+
+class SiteMessage(models.Model):
+
+ class MessageType(models.TextChoices):
+ NO_TYPE = "NO_TYPE", gettext_lazy("No Type")
+ SITE_NOTIFICATION = "SITE_NOTIFICATION", gettext_lazy("Site Notification")
+ SITE_ALERT = "SITE_ALERT", gettext_lazy("Site Alert")
+ CATALOG_HEADER = "CATALOG_HEADER", gettext_lazy("Catalog Header")
+ CATALOG_TEXT = "CATALOG_TEXT", gettext_lazy("Catalog Text")
+
+ message_type = models.CharField(
+ max_length=26,
+ blank=True,
+ null=True,
+ choices=MessageType.choices,
+ default=MessageType.NO_TYPE,
+ )
+
+ message_text = models.TextField()
+
+ start_at = models.DateTimeField(default=None, null=True)
+ end_at = models.DateTimeField(default=None, null=True)
+
+
class UserExtraAttempts(models.Model):
@staticmethod
def get_cur_semester():
@@ -1537,10 +1586,39 @@ def get_profile_fields(self):
self.profile_fields = updated_fields
self.save()
+ profile_images = SiteImage.objects.filter(
+ image_type=SiteImage.ImageType.PROFILE_IMAGE
+ )
+
+ if "profileImage" not in self.profile_fields or self.profile_fields[
+ "profileImage"
+ ] not in profile_images.values_list("id", flat=True):
+
+ random_profile_image = profile_images.order_by("?").first()
+ self.profile_fields["profileImage"] = (
+ random_profile_image.id if random_profile_image else -1
+ )
+ self.save()
+
return self.profile_fields
def initialize_profile_fields(self):
- self.profile_fields = {**self.DEFAULT_PROFILE_FIELDS}
+
+ random_profile_image_id = (
+ SiteImage.objects.filter(image_type=SiteImage.ImageType.PROFILE_IMAGE)
+ .order_by("?")
+ .values_list("id", flat=True)
+ .first()
+ )
+
+ if random_profile_image_id is None:
+ random_profile_image_id = -1
+
+ self.profile_fields = {
+ **self.DEFAULT_PROFILE_FIELDS,
+ "profileImage": random_profile_image_id,
+ }
+
self.save()
@@ -1553,4 +1631,8 @@ def create_user_settings(sender, instance, created, **kwargs):
@receiver(post_save, sender=User)
def save_user_settings(sender, instance, **kwargs):
- instance.profile_settings.save()
+ try:
+ instance.profile_settings.save()
+ except UserSettings.DoesNotExist:
+ settings = UserSettings.objects.create(user=instance)
+ settings.initialize_profile_fields()
diff --git a/app/core/services/play_data_exporter_service.py b/app/core/services/play_data_exporter_service.py
index 1bbbedcfd..a216028c5 100644
--- a/app/core/services/play_data_exporter_service.py
+++ b/app/core/services/play_data_exporter_service.py
@@ -281,10 +281,16 @@ def _export_full_event_log(
csv_questions.append(csv_question)
# Grab out the keys of options, add them if not already in the list
- for key in question_row_data["options"].keys():
- if key in csv_options:
- continue
- csv_options.append(key)
+ try:
+ for key in question_row_data["options"].keys():
+ if key in csv_options:
+ continue
+ csv_options.append(key)
+ except AttributeError:
+ # If a widget engine sets a question's 'options' to a list, it'll
+ # be difficult to predictably traverse for keys like we do with
+ # objects above - just ignore them for now, revisit if need be
+ pass
# Grab out each individual answer
for answer in question_row_data["answers"]:
diff --git a/app/core/services/user_service.py b/app/core/services/user_service.py
index b515c0442..d5b064789 100644
--- a/app/core/services/user_service.py
+++ b/app/core/services/user_service.py
@@ -11,7 +11,14 @@ def get_avatar_url(user: User) -> str:
profile_settings = user.profile_settings
use_gravatar = profile_settings.get_profile_fields().get("useGravatar", False)
if not use_gravatar:
- return f"{settings.STATIC_URL}img/default-avatar.jpg"
+ profile_id = profile_settings.get_profile_fields().get("profileImage")
+ if not profile_id or profile_id == -1:
+ return f"{settings.STATIC_URL}img/default-avatar.jpg"
+
+ from core.models import SiteImage
+
+ avatar = SiteImage.objects.filter(id=profile_id).first()
+ return avatar.image_path
clean_email = user.email.strip().lower().encode("utf-8")
hash_email = hashlib.md5(clean_email).hexdigest()
diff --git a/app/core/services/widget_play_services.py b/app/core/services/widget_play_services.py
index 524832fbf..4f23db41a 100644
--- a/app/core/services/widget_play_services.py
+++ b/app/core/services/widget_play_services.py
@@ -82,6 +82,7 @@ class WidgetPlayValidationService:
INVALID_DRAFT_NOT_PLAYABLE = "draft_not_playable"
INVALID_RETIRED_WIDGET = "widget_retired"
INVALID_NO_ATTEMPTS = "no_attempts"
+ INVALID_RECOVERY_TOKEN = "bad_recovery_token"
VALID_WITH_PRE_EMBED = "pre_embed"
VALID = "valid"
diff --git a/app/core/views/admin.py b/app/core/views/admin.py
index f0da39d38..4a3d999af 100644
--- a/app/core/views/admin.py
+++ b/app/core/views/admin.py
@@ -1,10 +1,10 @@
import os
+from core.utils.context_util import ContextUtil
+from core.utils.validator_util import ValidatorUtil
from django.conf import settings
from django.contrib.auth.decorators import login_required, user_passes_test
from django.shortcuts import render
-from core.utils.context_util import ContextUtil
-from core.utils.validator_util import ValidatorUtil
@login_required
@@ -51,6 +51,19 @@ def user(request):
return render(request, "react.html", context)
+@login_required
+@user_passes_test(lambda u: u.is_superuser)
+def site(request):
+ context = ContextUtil.create(
+ title="Site Admin",
+ js_resources=settings.JS_GROUPS["site_admin"],
+ css_resources=settings.CSS_GROUPS["site_admin"],
+ request=request,
+ )
+
+ return render(request, "react.html", context)
+
+
# class AdminViews(TemplateView):
# def widget(request):
# context = {"title": "Welcome to Materia", "bundle_name": "catalog"}
diff --git a/app/core/views/widget.py b/app/core/views/widget.py
index 95fd8fc97..ed4634434 100644
--- a/app/core/views/widget.py
+++ b/app/core/views/widget.py
@@ -129,8 +129,11 @@ def get_validation(self, request, instance):
)
elif LTILaunchService.is_recovery_launch(request):
- play = LogPlay.objects.get(pk=request.GET.get("token"))
- context_id = play.context_id
+ play = LogPlay.objects.filter(pk=request.GET.get("token")).first()
+ if play:
+ context_id = play.context_id
+ else:
+ raise LtiException("Invalid token for context id recovery")
# Check if this instance is a guest/demo instance
has_guest_access = instance.guest_access
@@ -475,6 +478,9 @@ def _create_player_context(
is_preview: bool = False,
is_embedded: bool = False,
):
+ if validation == WidgetPlayValidationService.INVALID_RECOVERY_TOKEN:
+ return _create_lti_error_page(request, "error_recovery_token")
+
# Check if embed only widget
if validation == WidgetPlayValidationService.INVALID_EMBEDDED_ONLY:
return _create_embedded_only_page(request, instance)
@@ -696,6 +702,20 @@ def _create_lti_success_page(
)
+def _create_lti_error_page(request: HttpRequest, error_type: str):
+ return ContextUtil.create(
+ title="Widget Embed Error",
+ page_type="lti-error",
+ js_globals={
+ "TITLE": "There was a problem with this embedded content.",
+ "ERROR_TYPE": error_type,
+ },
+ js_resources=settings.JS_GROUPS["lti-error"],
+ css_resources=settings.CSS_GROUPS["lti"],
+ request=request,
+ )
+
+
# Utils functions
def _generate_widget_login_messages(user: User, instance: WidgetInstance) -> dict:
instance_availability = instance.availability_status()
diff --git a/app/lti/services/launch.py b/app/lti/services/launch.py
index 467e1b147..f43928bbe 100644
--- a/app/lti/services/launch.py
+++ b/app/lti/services/launch.py
@@ -278,7 +278,7 @@ def get_launch_from_play(play_id: str) -> LtiPlayState:
"""
Returns the associated LtiPlayState model instance for a given play ID.
"""
- play = LogPlay.objects.get(pk=play_id)
+ play = LogPlay.objects.filter(pk=play_id).first()
if play:
launch = LtiPlayState.objects.filter(play_id=play.id).first()
return launch
diff --git a/app/lti/views/init.py b/app/lti/views/init.py
index 42fe227de..907ce53d4 100644
--- a/app/lti/views/init.py
+++ b/app/lti/views/init.py
@@ -2,12 +2,26 @@
from django.conf import settings
from lti_tool.views import OIDCLoginInitView
+from pylti1p3.exception import OIDCException
logger = logging.getLogger(__name__)
class MateriaOIDCLoginInitView(OIDCLoginInitView):
+ def get(self, request, *args, **kwargs):
+ """
+ Overrides OIDCLoginInitView's `get` method to intercept and handle OIDCExceptions.
+ The intended behavior is to handle situations where a LTI registration has been disabled.
+ """
+ registration_uuid = kwargs.get("registration_uuid")
+ try:
+ return self.get_oidc_response(request, registration_uuid, request.GET)
+ except OIDCException:
+ from lti.views.lti import error_page as lti_error_page
+
+ return lti_error_page(request, "error_registration_disabled")
+
def get_redirect_url(self, target_link_uri: str) -> str:
"""
Overrides OIDCLoginInitView's `get_redirect_url` method, as we only have one whitelisted launch URI: /ltilaunch/
diff --git a/app/materia/settings/base.py b/app/materia/settings/base.py
index 899d9e198..f59447e81 100644
--- a/app/materia/settings/base.py
+++ b/app/materia/settings/base.py
@@ -35,6 +35,7 @@
"media_uploads": os.path.realpath(
os.path.join(APP_PATH, "media", "uploads")
), # + os.sep,
+ "site_images": os.path.realpath(os.path.join(APP_PATH, "staticfiles", "site_img")),
"widgets": os.path.realpath(
os.path.join(APP_PATH, "staticfiles", "widget")
), # + os.sep
diff --git a/app/materia/settings/css.py b/app/materia/settings/css.py
index 28890c76a..5868326a9 100644
--- a/app/materia/settings/css.py
+++ b/app/materia/settings/css.py
@@ -19,6 +19,7 @@
"media": [CSS_BASEURL + "media.css"],
"support": [CSS_BASEURL + "support.css"],
"user_admin": [CSS_BASEURL + "user-admin.css"],
+ "site_admin": [CSS_BASEURL + "site-admin.css"],
"no-permission": [CSS_BASEURL + "no-permission.css"],
"pre-embed": [CSS_BASEURL + "pre-embed-placeholder.css"],
"lti": [
diff --git a/app/materia/settings/js.py b/app/materia/settings/js.py
index c27678de5..06e949883 100644
--- a/app/materia/settings/js.py
+++ b/app/materia/settings/js.py
@@ -22,6 +22,7 @@
"media": [JS_BASEURL + "media.js"],
"widget_admin": [JS_BASEURL + "widget-admin.js"],
"user_admin": [JS_BASEURL + "user-admin.js"],
+ "site_admin": [JS_BASEURL + "site-admin.js"],
"instance_admin": [JS_BASEURL + "support.js"],
"no-permission": [JS_BASEURL + "no-permission.js"],
"no-attempts": [JS_BASEURL + "no-attempts.js"],
diff --git a/app/materia/urls.py b/app/materia/urls.py
index 6fa480a5e..8f6a24970 100644
--- a/app/materia/urls.py
+++ b/app/materia/urls.py
@@ -20,6 +20,7 @@
from core.views import main as core_views
from core.views import profile as profile_views
from core.views.admin import instance as instance_admin
+from core.views.admin import site as site_admin
from core.views.admin import user as user_admin
from core.views.admin import widget as widget_admin
from core.views.catalog import CatalogView
@@ -156,6 +157,7 @@
path("users/login", login_views.login, name="login"),
path("login/", login_views.login, name="login"),
path("admin/widget", widget_admin, name="widget admin"),
+ path("admin/site", site_admin, name="site admin"),
path("admin/instance", instance_admin, name="instance admin"),
path("admin/user", user_admin, name="user admin"),
path("admin/", admin.site.urls),
diff --git a/app/requirements.txt b/app/requirements.txt
index d4ca72825..8ac68ba06 100644
--- a/app/requirements.txt
+++ b/app/requirements.txt
@@ -13,7 +13,7 @@ jmespath==1.0.1
mimetypes-magic==0.4.30
mysqlclient==2.2.6
openai==1.65.2
-pillow==11.1.0
+pillow==12.2.0
python-dateutil==2.9.0
pytz==2024.1
PyYAML==6.0.1
@@ -22,7 +22,7 @@ sentry-sdk[django]~=2.50.0
setuptools==69.0.3
six==1.17.0
sqlparse==0.4.4
-urllib3==2.3.0
+urllib3==2.7.0
wheel==0.42.0
whitenoise==6.9.0
django-sendgrid-v5==1.3.0
diff --git a/docker/config/nginx/sites-enabled/site.dev.conf b/docker/config/nginx/sites-enabled/site.dev.conf
index b208b9a2a..1be195103 100644
--- a/docker/config/nginx/sites-enabled/site.dev.conf
+++ b/docker/config/nginx/sites-enabled/site.dev.conf
@@ -69,4 +69,8 @@ server {
location /widget/ {
try_files $uri =404;
}
+
+ location /site_img/ {
+ try_files $uri =404;
+ }
}
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 23bc49288..ae4dd29a9 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -15,6 +15,7 @@ services:
- ../public:/var/www/html/staticfiles:ro
- ../theme/img:/var/www/html/staticfiles/theme/img:ro
- ../public/widget:/var/www/html/staticfiles/widget/:ro
+ - ../public/site_img:/var/www/html/staticfiles/site_img/:ro
- ./config/nginx/sites-enabled/site.dev.conf:/etc/nginx/conf.d/site.conf:ro
networks:
- frontend
@@ -47,6 +48,8 @@ services:
- ../app:/var/www/html
- uploaded_media:/var/www/html/media/
- ../public/widget:/var/www/html/staticfiles/widget/:rw
+ - ../public/img/default/profile_images:/var/www/html/staticfiles/img/default/profile_images/:rw
+ - ../public/site_img:/var/www/html/staticfiles/site_img/:rw
command: /wait_for_it.sh mysql:3306 -t 15 -- gunicorn materia.wsgi:application --bind 0.0.0.0:8001 --reload --workers 4 --threads 2 --timeout 150 --log-level info --access-logfile - --error-logfile -
mysql:
@@ -78,7 +81,7 @@ services:
- backend
redis:
- image: redis:3.2
+ image: redis:8.8.0
ports:
- "6379:6379"
networks:
diff --git a/package.json b/package.json
index ea0b5f76b..e53055fa7 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"uuid": "^14.0.0"
},
"devDependencies": {
- "@babel/core": "^7.10.4",
+ "@babel/core": "^7.29.6",
"@babel/preset-env": "^7.10.4",
"@babel/preset-react": "^7.10.4",
"@cfaester/enzyme-adapter-react-18": "^0.8.0",
@@ -66,7 +66,7 @@
"webpack": "^5.104.1",
"webpack-bundle-tracker": "^0.4.3",
"webpack-cli": "^5.0.1",
- "webpack-dev-server": "^5.2.1",
+ "webpack-dev-server": "^5.2.5",
"webpack-manifest-plugin": "^5.0.0",
"webpack-remove-empty-scripts": "1.0.1",
"webpack-strip-block": "^0.3.0"
diff --git a/public/img/default/profile_images/aura_kogneato.png b/public/img/default/profile_images/aura_kogneato.png
new file mode 100644
index 000000000..3cb68bb75
Binary files /dev/null and b/public/img/default/profile_images/aura_kogneato.png differ
diff --git a/public/img/default/profile_images/dm_kogneato.png b/public/img/default/profile_images/dm_kogneato.png
new file mode 100644
index 000000000..805599307
Binary files /dev/null and b/public/img/default/profile_images/dm_kogneato.png differ
diff --git a/public/img/default/profile_images/gambit_kogneato.png b/public/img/default/profile_images/gambit_kogneato.png
new file mode 100644
index 000000000..ece5627f3
Binary files /dev/null and b/public/img/default/profile_images/gambit_kogneato.png differ
diff --git a/public/img/default/profile_images/in_kogneato.png b/public/img/default/profile_images/in_kogneato.png
new file mode 100644
index 000000000..d6154c44e
Binary files /dev/null and b/public/img/default/profile_images/in_kogneato.png differ
diff --git a/public/img/default/profile_images/kogneato_the_riveter.png b/public/img/default/profile_images/kogneato_the_riveter.png
new file mode 100644
index 000000000..89f2270d4
Binary files /dev/null and b/public/img/default/profile_images/kogneato_the_riveter.png differ
diff --git a/public/img/default/profile_images/metal_kogneato.png b/public/img/default/profile_images/metal_kogneato.png
new file mode 100644
index 000000000..21b7d900c
Binary files /dev/null and b/public/img/default/profile_images/metal_kogneato.png differ
diff --git a/public/img/default/profile_images/morpheus_kogneato.png b/public/img/default/profile_images/morpheus_kogneato.png
new file mode 100644
index 000000000..7604aa7ef
Binary files /dev/null and b/public/img/default/profile_images/morpheus_kogneato.png differ
diff --git a/public/img/default/profile_images/shakespeare_kogneato.png b/public/img/default/profile_images/shakespeare_kogneato.png
new file mode 100644
index 000000000..48d2203a7
Binary files /dev/null and b/public/img/default/profile_images/shakespeare_kogneato.png differ
diff --git a/requirements-dev.txt b/requirements-dev.txt
index b28adc17b..6f29f2d20 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -1,18 +1,19 @@
-black==24.4.2
+black==26.3.1
cfgv==3.4.0
click==8.1.7
distlib==0.3.8
-filelock==3.15.4
+filelock==3.29.0
flake8==7.1.0
identify==2.6.0
mccabe==0.7.0
mypy-extensions==1.0.0
nodeenv==1.9.1
packaging==24.1
-pathspec==0.12.1
+pathspec==1.1.1
platformdirs==4.2.2
pre-commit==3.8.0
pycodestyle==2.12.0
pyflakes==3.2.0
+pytokens==0.4.1
PyYAML==6.0.1
-virtualenv==20.26.3
+virtualenv==20.36.1
diff --git a/src/components/catalog.jsx b/src/components/catalog.jsx
index 557488726..9348f4d36 100644
--- a/src/components/catalog.jsx
+++ b/src/components/catalog.jsx
@@ -50,7 +50,7 @@ const Catalog = ({widgets = [], isLoading = true}) => {
results = results.filter(w => {
const {features, supported_data, accessibility_keyboard, accessibility_reader} = w.meta_data
return state.activeFilters.every(f =>{
- if (features.includes(f) || supported_data.includes(f)) return true
+ if (features?.includes(f) || supported_data?.includes(f)) return true
if (accessibility_keyboard && f === 'Keyboard Accessible') return true
if (accessibility_reader && f === 'Screen Reader Accessible') return true
diff --git a/src/components/guide-page.jsx b/src/components/guide-page.jsx
index bb0e84c86..354ce2ab6 100644
--- a/src/components/guide-page.jsx
+++ b/src/components/guide-page.jsx
@@ -1,4 +1,4 @@
-import React, { useState, useEffect} from 'react'
+import React, { useState, useEffect, useRef } from 'react'
import Header from './header'
import './guide-page.scss'
import { waitForWindow } from '../util/wait-for-window'
@@ -11,6 +11,8 @@ const GuidePage = () => {
const [hasCreatorGuide, setHasCreatorGuide] = useState(null)
const [docPath, setDocPath] = useState(null)
+ const iframeRef = useRef(null)
+
useEffect(() => {
waitForWindow(['NAME', 'TYPE', 'HAS_PLAYER_GUIDE', 'HAS_CREATOR_GUIDE', 'DOC_PATH']).then(() => {
setName(window.NAME)
@@ -18,6 +20,12 @@ const GuidePage = () => {
setHasPlayerGuide(window.HAS_PLAYER_GUIDE)
setHasCreatorGuide(window.HAS_CREATOR_GUIDE)
setDocPath(window.DOC_PATH)
+
+ if (document.body.classList.contains('darkMode') && iframeRef.current) {
+ iframeRef.current.addEventListener('load', () => {
+ iframeRef.current.contentWindow.document.body.classList.add('darkMode')
+ })
+ }
})
})
@@ -35,7 +43,7 @@ const GuidePage = () => {
-
+
)
diff --git a/src/components/header.jsx b/src/components/header.jsx
index e077f8353..6e417a8cd 100644
--- a/src/components/header.jsx
+++ b/src/components/header.jsx
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'
import { useQuery } from 'react-query'
-import { apiGetUser, apiUserVerify } from '../util/api'
+import { apiGetUser, apiUserVerify, apiGetSiteMessages } from '../util/api'
import Notifications from './notifications'
const Header = ({
@@ -13,6 +13,9 @@ const Header = ({
const [verified, setVerified] = useState(false)
const [permLevel, setPermLevel] = useState('anonymous')
+ const [headerNotification, setHeaderNotification] = useState(null)
+ const [headerAlert, setHeaderAlert] = useState(null)
+
const { data: userPerms } = useQuery({
queryKey: 'isLoggedIn',
queryFn: apiUserVerify,
@@ -26,6 +29,22 @@ const Header = ({
enabled: !!verified
})
+ const {data: siteMessages } = useQuery({
+ queryKey: ['site-messages', 'notification', 'alert'],
+ queryFn: () => apiGetSiteMessages(['SITE_NOTIFICATION', 'SITE_ALERT']),
+ staleTime: Infinity,
+ retry: false
+ })
+
+ useEffect(() => {
+ if (siteMessages != undefined) {
+ siteMessages.forEach((msg) => {
+ if (msg.message_type == 'SITE_NOTIFICATION') setHeaderNotification(msg.message_text)
+ else if (msg.message_type == 'SITE_ALERT') setHeaderAlert(msg.message_text)
+ })
+ }
+ },[siteMessages])
+
useEffect(() => {
if (userData != undefined) {
setUser(userData)
@@ -56,7 +75,7 @@ const Header = ({
let elevatedPermsNavRender = null
if (permLevel == 'super_user') {
elevatedPermsNavRender = (
-
+
Admin
@@ -68,6 +87,9 @@ const Header = ({
Instances
+
+ Site
+
Django Admin
@@ -77,7 +99,7 @@ const Header = ({
}
else if (permLevel == 'support_user') {
elevatedPermsNavRender = (
-
+
Support
@@ -168,20 +190,32 @@ const Header = ({
)
}
+ let siteNotificationRender = null
+
+ if (headerNotification != null) {
+ siteNotificationRender = (
+
+
+ {headerNotification}
+
+ )
+ }
+
+ let siteAlertRender = null
+ if (headerAlert != null) {
+ siteAlertRender = (
+
+
+ {headerAlert}
+
+ )
+ }
+
return (
)
diff --git a/src/components/hooks/useUpdateUserSettings.jsx b/src/components/hooks/useUpdateUserSettings.jsx
index 13cbaed03..9fa56e162 100644
--- a/src/components/hooks/useUpdateUserSettings.jsx
+++ b/src/components/hooks/useUpdateUserSettings.jsx
@@ -18,7 +18,7 @@ export default function useUpdateUserSettings() {
return { prior }
},
onSuccess: (data, variables, context) => {
- variables.successFunc()
+ variables.successFunc({...data.profile_fields})
queryClient.invalidateQueries('user')
},
diff --git a/src/components/include.scss b/src/components/include.scss
index 90a7d2927..91c16a281 100644
--- a/src/components/include.scss
+++ b/src/components/include.scss
@@ -2,16 +2,22 @@
$color-features: #0093e7;
$color-features-active: #0357a5;
$color-red: #ca3b3b;
+$color-red-bg: #eb5858;
+$color-red-bg-dark: #8d0808;
+$color-light-red: #f26666;
$color-yellow: #ffd439;
$color-yellow-hover: #ffc904;
$color-yellow-bg: #ffeca8;
+$color-yellow-bg-dark: #8c6e04;
$color-orange: #ffba5d;
+$color-dark-orange: #864c00;
$color-green: #c5dd60;
$color-purple: #b944cc;
$very-light-gray: #ececec;
$light-gray: #dadada;
$gray: #9a9a9a;
+$moderate-gray: #797979;
$extremely-dark-gray: #333;
$color-background-dark: #21232a;
@@ -49,6 +55,15 @@ body.darkMode {
background-color: $color-background-dark;
}
+section,
+header,
+nav,
+article,
+aside,
+footer {
+ display: block;
+}
+
.action_button {
padding: 7px 23px 8px 23px;
background-color: $color-yellow;
@@ -146,14 +161,18 @@ a {
}
header {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-between;
+ align-items: flex-end;
position: relative;
z-index: 1000;
- padding: 35px 0 0 136px;
+ // padding: 35px 0 0 136px;
margin: 0 0 25px 0;
- display: block;
- height: 49px;
+ // height: 49px;
&.logged_in {
background: #fff url(/static/img/header_border_alt.png) 0% 100% repeat-x;
@@ -166,11 +185,9 @@ header {
}
h1.logo {
- position: absolute;
- left: 0;
- top: 0;
+ display: inline-block;
margin: 0;
- overflow: hidden;
+ padding: 0;
a {
display: block;
@@ -183,19 +200,44 @@ header {
}
}
+ section.site-notification, section.site-alert {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 0.5em;
+ width: calc(100vw - 3em);
+ padding: 1em 1em 1em 2em;
+
+ font-weight: 400;
+ }
+
+ section.site-notification {
+ background: $color-yellow-bg;
+ }
+
+ section.site-alert {
+ color: #fff;
+ background: $color-red-bg;
+
+ img {
+ filter: invert(1);
+ }
+ }
+
+
loginLink {
top: auto;
}
.profile-bar
{
- position: absolute;
- top: 40px;
- right: 30px;
display: flex;
flex-direction: row;
gap: 10px;
- font-size: 14px;
+
+ margin-left: auto;
+ margin-right: 1em;
+ font-size: 1em;
.profile-bar-options
{
@@ -304,7 +346,7 @@ header {
nav {
display: block;
- margin-top: 16px;
+ margin-right: auto;
ul {
li {
@@ -361,18 +403,45 @@ header {
background-color: #ffffff;
padding: 15px 15px;
position: absolute;
- top: 100%;
- left: 0;
+ top: calc(100% + 1px);
+ left: calc(-0.5em - 1px);
border: 1px solid #d3d3d3;
border-radius: 5px;
+ border-top-left-radius: 0;
margin-top: 5px;
gap: 15px;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
+
+ &:before {
+ content: '';
+ position: absolute;
+ height: calc(1em + 10px);
+ top: calc(-1em - 11px - 0.5em);
+ left: -1px;
+ padding: 0.5em 0.5em 0 0.5em;
+ color: #ffa52b;
+ background-color: #fff;
+ border: 1px solid #d3d3d3;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ border-bottom: none;
+ }
+
+ li a {
+ border-bottom: solid 2px rgba(0,0,0,0);
+ }
+ }
+
+ &.support ul:before {
+ content: 'Support';
+ }
+
+ &.admin ul:before {
+ content: 'Admin';
}
&:hover > span {
display: inline-block;
- height: 27px;
&.admin {
padding-right: 165px;
@@ -389,12 +458,11 @@ header {
li {
display: flex;
- padding: 0;
align-items: center;
- height: 20px;
a {
text-wrap: nowrap;
+ padding: 0;
}
}
}
@@ -758,6 +826,19 @@ header {
color: red;
}
}
+
+ section.site-notification {
+ color: #fff;
+ background: $color-yellow-bg-dark;
+
+ img {
+ filter: invert(1);
+ }
+ }
+
+ section.site-alert {
+ background: $color-red-bg-dark;
+ }
}
// mobile header
@@ -765,24 +846,23 @@ header {
header {
min-height: 49px;
height: auto;
- // overflow: hidden;
padding: 0;
- margin-bottom: 8px;
+ margin-bottom: 1em;
h1.logo {
display: inline-block;
position: static;
+ align-self: flex-start;
}
// mobile hamburger menu
#mobile-menu-toggle {
display: block;
- position: absolute;
- right: 15px;
- top: 30px;
height: 35px;
width: 37px;
border: none;
+ margin-bottom: 3px;
+ margin-right: 0.5em;
padding: 2px 5px;
border-radius: 3px;
text-align: left;
@@ -792,14 +872,6 @@ header {
cursor: pointer;
&.expanded {
- ~ nav {
- visibility: visible;
- max-height: 175px;
- margin-bottom: 25px;
- opacity: 1;
- width: 100%;
- box-sizing: border-box;
- }
div {
transform: rotate(50deg) translate(6px, -8px);
@@ -844,9 +916,9 @@ header {
}
.profile-bar {
- gap: 20px;
- right: 75px;
- top: 30px;
+ height: 40px;
+ align-items: center;
+ margin: 0 0.5em 0 0;
.desktop-notifications {
display: none;
@@ -870,12 +942,16 @@ header {
}
nav {
- visibility: hidden;
+ position: relative;
+ left: 80px;
+ display: grid;
+ grid-template-rows: 0fr;
+ overflow: hidden;
opacity: 0;
- max-height: 0;
+ margin: 1em 0 45px auto;
padding: 0 20px;
text-align: right;
- transition: all ease 500ms;
+ transition: grid-template-rows 500ms ease, opacity 250ms ease;
ul {
li {
@@ -883,10 +959,16 @@ header {
padding: 5px;
&.nav_expandable {
- &:hover ul li {
- display: inline-block;
- padding: 0 10px 5px;
- height: auto;
+
+ &:hover {
+ ul {
+ display: block;
+
+ li {
+ display: block;
+ padding: 5px;
+ }
+ }
}
&:hover > span {
@@ -900,19 +982,37 @@ header {
}
ul {
- top: 0;
- bottom: 0;
- right: 75px;
- left: auto;
- border-bottom: none;
- border-left: none;
- padding: 5px;
+ position: static;
+ display: block;
+ padding: 0 0 0 15px;
+ box-shadow: none;
+ border: none;
+
+ &:before {
+ display: none;
+ }
+
+ &:hover {
+ position: static;
+ display: block;
+
+ li {
+ display: block;
+ padding: 5px;
+ }
+ }
+ // top: 0;
+ // bottom: 0;
+ // right: 75px;
+ // left: auto;
+ // border-bottom: none;
+ // border-left: none;
+ // padding: 5px;
}
}
.logout a {
display: inline;
- font-size: 17px;
position: static;
}
@@ -924,7 +1024,21 @@ header {
}
}
}
+
}
+
+ nav > ul {
+ min-height: 0;
+ overflow: hidden;
+ }
+
+ nav:has(~ .expanded) {
+ grid-template-rows: 1fr;
+ opacity: 1;
+ padding-bottom: 10px;
+ // margin-bottom: 40px;
+ }
+
.notifContainer
{
position: static;
@@ -948,11 +1062,6 @@ header {
display: none;
}
- #loginLink {
- top: 7.5px;
- position: absolute;
- right: 0;
- }
}
}
@@ -1007,23 +1116,23 @@ nav {
}
}
-h1.logo {
- position: absolute;
- left: 0;
- top: 0;
- margin: 0;
- overflow: hidden;
-
- a {
- display: block;
- width: 100px;
- height: 0;
- padding: 60px 0 0 10px;
- margin: 10px 0 0 10px;
- background: url('/static/img/materia-logo-default-lightmode.svg') 50% 100% no-repeat;
- background-size: 80px auto;
- }
-}
+// h1.logo {
+// position: absolute;
+// left: 0;
+// top: 0;
+// margin: 0;
+// overflow: hidden;
+
+// a {
+// display: block;
+// width: 100px;
+// height: 0;
+// padding: 60px 0 0 10px;
+// margin: 10px 0 0 10px;
+// background: url('/static/img/materia-logo-default-lightmode.svg') 50% 100% no-repeat;
+// background-size: 80px auto;
+// }
+// }
.darkMode h1.logo {
a {
@@ -1106,15 +1215,6 @@ h1.logo {
}
}
-section,
-header,
-nav,
-article,
-aside,
-footer {
- display: block;
-}
-
.cancel_button {
color: #555;
text-decoration: underline;
diff --git a/src/components/lti/error-general.jsx b/src/components/lti/error-general.jsx
index e4f3a79b5..4cf4c4c6d 100644
--- a/src/components/lti/error-general.jsx
+++ b/src/components/lti/error-general.jsx
@@ -2,104 +2,121 @@ import React, { useEffect, useState } from 'react'
import SupportInfo from '../support-info'
const ErrorGeneral = () => {
- const [title, setTitle] = useState('')
- const [errorType, setErrorType] = useState('')
+ const [title, setTitle] = useState('')
+ const [errorType, setErrorType] = useState('')
- useEffect(() => {
- if (window.TITLE) {
- setTitle(window.TITLE)
- }
- }, [window.TITLE])
+ useEffect(() => {
+ if (window.TITLE) {
+ setTitle(window.TITLE)
+ }
+ }, [window.TITLE])
- useEffect(() => {
- if (window.ERROR_TYPE) {
- setErrorType(window.ERROR_TYPE)
- }
- }, [window.ERROR_TYPE])
+ useEffect(() => {
+ if (window.ERROR_TYPE) {
+ setErrorType(window.ERROR_TYPE)
+ }
+ }, [window.ERROR_TYPE])
- let content = null;
+ let content = null;
- switch (errorType)
- {
- case 'error_unknown_assignment':
- content =
-
- Error type: Unknown Assignment
- This Materia assignment hasn't been setup correctly in the LMS.
- Your instructor will need to complete the setup process.
-
- break;
- case 'error_unknown_user':
- content =
-
- Error type: Unknown User
- Materia cannot determine who you are using the information provided by the LMS.
- This may occur if you are using a non-standard account or if the LMS is missing information about who you are.
- If this message persists, contact support.
-
- break;
- case 'error_launch_validation':
- content =
-
- Error type: Launch Validation Failed
- The launch information provided by the LMS failed validation.
- Try accessing the tool again without using the back button. If prompted to re-submit page data, try leaving the page and re-selecting the activity.
- If this message persists, contact support.
-
- break;
- case 'error_autoplay_misconfigured':
- content =
-
- Error type: Autoplay Misconfigured
- This Materia assignment hasn't been setup correctly in the system.
- Non-autoplaying widgets can not be used as graded assignments.
-
- break;
- case 'error_lti_guest_mode':
- content =
-
- Error type: Guest Mode Enabled
- This assignment has guest mode enabled.
- This assignment can only record scores anonymously and therefore cannot be played as an embedded assignment.
- Your instructor will need to disable guest mode or provide a link to play as a guest.
-
- break;
- case 'error_invalid_oauth_request':
- content =
-
- Error type: Invalid Oauth Request
- Something went wrong when Materia tried to authenticate you with information from the LMS.
- We recommend contacting support:
-
- break;
- case 'error_launch_recovery':
- content =
-
- Error type: Launch Recovery Failure
- Materia couldn't complete this operation because of a session caching issue.
- This almost certainly isn't because of anything you did. If possible, please report the issue to support.
-
- break;
- default:
- content =
-
- Error type: General Error
- An error occurred.
- If you need help accessing this tool, contact support.
-
- break;
- }
+ switch (errorType)
+ {
+ case 'error_unknown_assignment':
+ content =
+
+ Error type: Unknown Assignment
+ This Materia assignment hasn't been setup correctly in the LMS.
+ Your instructor will need to complete the setup process.
+
+ break;
+ case 'error_unknown_user':
+ content =
+
+ Error type: Unknown User
+ Materia cannot determine who you are using the information provided by the LMS.
+ This may occur if you are using a non-standard account or if the LMS is missing information about who you are.
+ If this message persists, contact support.
+
+ break;
+ case 'error_launch_validation':
+ content =
+
+ Error type: Launch Validation Failed
+ The launch information provided by the LMS failed validation.
+ Try accessing the tool again without using the back button. If prompted to re-submit page data, try leaving the page and re-selecting the activity.
+ If this message persists, contact support.
+
+ break;
+ case 'error_autoplay_misconfigured':
+ content =
+
+ Error type: Autoplay Misconfigured
+ This Materia assignment hasn't been setup correctly in the system.
+ Non-autoplaying widgets can not be used as graded assignments.
+
+ break;
+ case 'error_lti_guest_mode':
+ content =
+
+ Error type: Guest Mode Enabled
+ This assignment has guest mode enabled.
+ This assignment can only record scores anonymously and therefore cannot be played as an embedded assignment.
+ Your instructor will need to disable guest mode or provide a link to play as a guest.
+
+ break;
+ case 'error_invalid_oauth_request':
+ content =
+
+ Error type: Invalid Oauth Request
+ Something went wrong when Materia tried to authenticate you with information from the LMS.
+ We recommend contacting support:
+
+ break;
+ case 'error_launch_recovery':
+ content =
+
+ Error type: Launch Recovery Failure
+ Materia couldn't complete this operation because of a session caching issue.
+ This almost certainly isn't because of anything you did. If possible, please report the issue to support.
+
+ break;
+ case 'error_recovery_token':
+ content =
+
+ Error type: Invalid Recovery Token
+ Materia couldn't recover launch data from the token provided.
+ The URL for this activity may have been malformed. If possible, try launching the activity from your LMS again.
+ If the issue persists, contact support.
+
+ break;
+ case 'error_registration_disabled':
+ content =
+
+ Error type: Registration Disabled
+ LTI launches from this tool are disabled.
+ Your institution may have provided guidance as to why you cannot launch Materia from this tool. Contact support for more information.
+
+ break;
+ default:
+ content =
+
+ Error type: General Error
+ An error occurred.
+ If you need help accessing this tool, contact support.
+
+ break;
+ }
- return <>
-
+ return <>
+
- {content}
+ {content}
-
- >
+
+ >
}
export default ErrorGeneral
diff --git a/src/components/modal.jsx b/src/components/modal.jsx
index 3b6f84361..50fc0f91e 100644
--- a/src/components/modal.jsx
+++ b/src/components/modal.jsx
@@ -33,7 +33,7 @@ const Modal = (props) => {
X
+ onClick={props.onClose}>✕
{props.children}
diff --git a/src/components/my-widgets-collaborate-dialog.jsx b/src/components/my-widgets-collaborate-dialog.jsx
index a5fef91b2..558d218d6 100644
--- a/src/components/my-widgets-collaborate-dialog.jsx
+++ b/src/components/my-widgets-collaborate-dialog.jsx
@@ -257,7 +257,8 @@ const MyWidgetsCollaborateDialog = ({onClose, inst, myPerms, otherUserPerms, set
onChange={(e) => setState({...state, searchText: e.target.value})}
className='user-add'
type='text'
- placeholder="Enter a Materia user's name or e-mail"/>
+ placeholder="Enter a user's name or e-mail"/>
+ Only individuals who have previously used Materia will show up in search.
{ searchResultsRender }
)
@@ -273,33 +274,41 @@ const MyWidgetsCollaborateDialog = ({onClose, inst, myPerms, otherUserPerms, set
mainContentRender =
if (containsUser) {
- const mainContentElements = Array.from(state.updatedAllUserPerms).map(([userId, userPerms]) => {
+
+ const mainContentElements = []
+ let userContentElement = null
+
+ Array.from(state.updatedAllUserPerms).forEach(([userId, userPerms]) => {
if (userPerms.remove === true) return
let user = collabUsers[userId]
- if (!user)
- {
- return
- }
-
- user.is_owner = user.id === inst.user_id;
-
- return updatePerms(userId, perms)}
- readOnly={myPerms?.can?.share === false}
- />
+ if (!user) return
+
+ user.is_owner = user.id === inst.user_id
+ const rowElement = (
+ updatePerms(userId, perms)}
+ readOnly={myPerms?.can?.share === false}
+ />
+ )
+
+ if (currentUser.id === user.id) userContentElement = rowElement
+ else mainContentElements.push(rowElement)
})
mainContentRender = (
<>
- { mainContentElements }
+
+ { userContentElement }
+
+ { mainContentElements.length > 0 ? mainContentElements : No other users have access to your widget. }
>
)
}
@@ -342,12 +351,14 @@ const MyWidgetsCollaborateDialog = ({onClose, inst, myPerms, otherUserPerms, set
{/* Calendar portal used to bring calendar popup out of access-list to avoid cutting off the overflow */}
- Users with full access can edit or copy this widget and can
+ Users with full access can edit this widget and can
add or remove people in this list.
{onlyOneFullPermHolder && myPerms.accessLevel == access.FULL && (
-
- {'\u00A0'}Note: There must be at least one user with full access.
-
+
+
+ { '\u00A0'}Note: There must be at least one user with full access.
+
+
)}
diff --git a/src/components/my-widgets-collaborate-dialog.scss b/src/components/my-widgets-collaborate-dialog.scss
index 97bc25c62..5d66e12dc 100644
--- a/src/components/my-widgets-collaborate-dialog.scss
+++ b/src/components/my-widgets-collaborate-dialog.scss
@@ -9,11 +9,13 @@
font-weight: 400;
.title {
+ width: 100%;
+ left: -10px;
margin: 0;
- padding: 0;
+ padding: 0 10px;
font-size: 1.3em;
- color: #555;
- border-bottom: #999 dotted 1px;
+ color: $extremely-dark-gray;
+ border-bottom: $light-gray solid 1px;
padding-bottom: 20px;
margin-bottom: 20px;
position: relative;
@@ -27,15 +29,24 @@
position: relative;
display: flex;
align-items: center;
- justify-content: center;
+ justify-content: space-between;
+ flex-wrap: wrap;
width: 100%;
margin-bottom: 20px;
text-align: left;
.collab-input-label {
- font-size: 19px;
+ font-size: 1em;
+ font-weight: 400;
margin-right: auto;
}
+
+ .collab-input-disclaimer {
+ flex-basis: 100%;
+ margin: 0.5em 0.5em 0.5em 0;
+ font-size: 0.7em;
+ text-align: right;
+ }
}
.user-add {
@@ -43,9 +54,10 @@
flex-grow: 2;
height: 30px;
margin-left: 1em;
- padding-left: 0.5em;
+ padding: 0.25em 0.5em;
border: solid 1px #c9c9c9;
- font-size: 16px;
+ border-radius: 0.3em;
+ font-size: 0.9em;
}
.shareNotAllowed {
@@ -135,7 +147,7 @@
.access-list {
min-height: 250px;
max-height: 420px;
- padding: 0;
+ padding: 1em 0 1em 0;
margin-top: 0px;
overflow: auto;
@@ -148,6 +160,20 @@
align-items: center;
justify-content: center;
}
+
+ span.not-shared {
+ display: block;
+ margin-top: 2em;
+ text-align: center;
+
+ font-size: 0.9em;
+ font-weight: 700;
+ color: $moderate-gray;
+ }
+
+ .deleted {
+ display: none;
+ }
}
.btn-box {
@@ -167,50 +193,73 @@
color: #575757;
font-size: 14px;
margin-top: 10px;
+
+ span {
+ display: block;
+ margin-top: 0.5em;
+ }
}
.warning {
- font-weight: bold;
+ font-weight: 400;
}
.access-list {
- .deleted {
- display: none !important;
+
+ header.access-list-header {
+ width: auto;
+ height: auto;
+ min-height: 0;
+ margin: 0 0 1em 0;
+ padding: 0 1rem;
+ font-weight: 700;
+ font-size: 0.7em;
+ color: $moderate-gray;
+ text-transform: uppercase;
+ background: none;
}
.user-perm {
display: flex;
align-items: center;
position: relative;
- margin: 25px 10px;
+ margin: 1.5em 1em 1em 1em;
- &::after {
- content: ' ';
- width: 70%;
- margin-left: 15%; // (100% - 70%) / 2
- display: block;
- border-bottom: 1px solid #d2d2d2;
- position: absolute;
- bottom: -12.5px;
+ &.current-user {
+ padding: 0 1.25em 1em 0;
+ margin-bottom: 1.5em 1em 0 1em;
+ border-bottom: solid 1px $light-gray;
}
&.provisional {
flex-wrap: wrap;
div.provisional-access-container {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: center;
flex-grow: 4;
+ margin-top: 1em;
padding: 0.5em;
font-size: 0.8em;
+ background: #fff;
+ color: $color-dark-orange;
+ border: solid 1px $color-yellow-bg;
+ border-radius: 0.5em;
span {
display: block;
+ max-width: 60%;
margin-left: 0.5em;
}
button.action_button {
+ width: fit-content;
+ height: fit-content;
margin: 0.5em;
- font-size: 16px;
+ font-size: 1em;
}
}
}
@@ -220,24 +269,22 @@
}
.demote-dialog {
+ position: absolute;
+ z-index: 10000;
+ margin-left: 25%;
+
border-radius: 4px;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
padding: 1em;
- width: 310px;
font-family: 'Lucida Grande', sans-serif;
- font-size: 9pt;
+ font-size: 0.75em;
color: black;
text-align: center;
background: #fcdbdb;
- height: 40px;
- z-index: 10000;
- position: absolute;
- margin-left: 125px;
.arrow {
background: url(/static/img/pink-arrow-left.png) no-repeat 0 center;
width: 13px;
- //height: 23px;
display: inline-block;
top: 0;
left: -13px;
@@ -259,7 +306,7 @@
.no-button {
color: #555;
text-decoration: underline;
- font-size: 12pt;
+ font-size: 1.1em;
cursor: pointer;
}
@@ -267,7 +314,8 @@
background: #e10000;
border-color: #747474;
color: #ffffff;
- padding: 3px 15px;
+ padding: 0.5em 1.5em;
+ font-size: 1.1em;
}
.yes-button:hover {
@@ -277,14 +325,14 @@
}
.remove {
- display: flex;
- justify-content: center;
- align-items: center;
- margin-right: 0.25em;
- padding: 0 0.25em;
- color: #bfbfbf;
+ width: 1.25em;
+ height: 1.25em;
+ align-self: flex-start;
+ margin-left: 0.25em;
+ padding: 0;
+ color: $moderate-gray;
text-decoration: none;
- font-size: 24px;
+ font-size: 1.25em;
user-select: none;
border: none;
background: transparent;
@@ -323,18 +371,14 @@
&.user-match-student:after {
content: 'Student';
- position: absolute;
- top: -12px;
- left: 0;
+ display: block;
font-size: 11px;
color: gray;
}
&.user-match-owner:after {
content: 'Owner';
- position: absolute;
- top: -12px;
- left: 0;
+ display: block;
font-size: 11px;
color: gray;
}
@@ -343,16 +387,23 @@
.options {
margin-left: auto;
- margin-right: 10%;
+ margin-right: 0;
text-align: left;
select {
display: inline-block;
- padding: 0.25em;
+ padding: 0.5em 1em;
margin-bottom: 5px;
cursor: pointer;
- border-width: 1px;
+ border: solid 1px $gray;
+ border-radius: 0.5em;
+
+ background: #fff;
+
+ &:disabled {
+ cursor: auto;
+ }
}
.expires {
@@ -436,6 +487,42 @@
}
}
}
+
+ .options-for-self {
+ display: flex;
+ gap: 1em;
+ align-items: center;
+ justify-content: flex-end;
+ margin-left: auto;
+ margin-right: 0;
+
+ .self-status {
+ color: $gray;
+ font-size: 0.8em;
+ font-weight: 400;
+ }
+
+ .leave {
+ background: #fff;
+ color: $color-red;
+ border: solid 1px $color-light-red;
+ font-size: 0.9em;
+ padding: 0.5em 0.8em;
+ cursor: pointer;
+
+ &:disabled {
+ color: $gray;
+ background: $very-light-gray;
+ border-color: $gray;
+ cursor: auto;
+ }
+
+ &:hover &:not(:disabled) {
+ background: $color-light-red;
+ color: #fff;
+ }
+ }
+ }
}
}
}
@@ -483,11 +570,25 @@
}
.access-list {
+
+ header.access-list-header {
+ color: $light-gray;
+ }
+
.user-perm {
&::after {
border-bottom: 1px solid #181920;
}
+ .self-status {
+ color: $light-gray;
+ }
+
+ button.leave {
+ color: #fff;
+ background-color: $color-red;
+ }
+
.demote-dialog {
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.3);
color: black;
diff --git a/src/components/my-widgets-collaborate-user-row.jsx b/src/components/my-widgets-collaborate-user-row.jsx
index 48e5f3bfb..84c8bd40b 100644
--- a/src/components/my-widgets-collaborate-user-row.jsx
+++ b/src/components/my-widgets-collaborate-user-row.jsx
@@ -5,8 +5,8 @@ import DatePicker from 'react-datepicker'
import './my-widgets-collaborate-dialog.scss'
const accessLevels = {
- [access.VISIBLE]: { value: access.VISIBLE, text: 'View Scores' },
- [access.FULL]: { value: access.FULL, text: 'Full' }
+ [access.VISIBLE]: { value: access.VISIBLE, text: 'Can View Scores' },
+ [access.FULL]: { value: access.FULL, text: 'Full Access' }
}
const initRowState = () => {
@@ -94,7 +94,7 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onlyOneFullPer
- Are you sure you want to limit your access?
+ Are you sure you want to remove your access?
No
Yes
@@ -145,6 +145,57 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onlyOneFullPer
}
}
+ let optionsContent = null
+ if (!isCurrentUser) {
+ const disableRemoveOptions = (onlyOneFullPermHolder && (perms.accessLevel === access.FULL))
+ if (myPerms.accessLevel === access.FULL) {
+ optionsContent = (
+ <>
+
+
+ { selectOptionElements }
+
+
+ Expires:
+ { expirationSettingRender }
+
+
+ {
+ disableRemoveOptions ? <>> : (
+
+ ⨯
+
+ )
+ }
+ >
+ )
+ }
+
+ } else {
+ optionsContent = (
+
+ { perms.accessLevel == access.FULL ? 'Full Access' : 'Can View Scores' }
+
+ Leave
+
+
+ )
+ }
+
let provisionalAccess = null
if ((state.contexts != null || state.provisionalAccessRemoved == true) && !readOnly ) {
provisionalAccess = (
@@ -152,8 +203,8 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onlyOneFullPer
{ state.provisionalAccessRemoved == false ? (
<>
- This user has provisional access due to the widget being embedded in their course. They can only see scores associated
- with that course. Selecting Unrestrict Access will allow them to view all scores the widget has collected.
+ Provisional Access: This user can view scores from a course the widget was embedded in. They can only see scores associated
+ with that course. Unrestricted Access will allow them to view all scores the widget has collected.
Unrestrict Access
@@ -167,16 +218,7 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onlyOneFullPer
}
return (
-
-
- ⨯
-
+
@@ -186,20 +228,8 @@ const CollaborateUserRow = ({user, perms, myPerms, isCurrentUser, onlyOneFullPer
{ selfDemoteWarningRender }
-
-
- { selectOptionElements }
-
-
- Expires:
- { expirationSettingRender }
-
-
+ { optionsContent }
+
{ provisionalAccess }
)
diff --git a/src/components/profile-page.scss b/src/components/profile-page.scss
index de6139840..c9afc7b40 100644
--- a/src/components/profile-page.scss
+++ b/src/components/profile-page.scss
@@ -10,7 +10,7 @@
flex-direction: row;
align-items: stretch;
gap: 2em;
- margin: 15px 0 0 0;
+ margin: 15px 0 2em 0;
min-height: 400px;
font-weight: 400;
@@ -359,6 +359,7 @@
padding: 0 2em 1.5em 0;
header {
+ display: block;
position: static;
height: auto;
padding: 1em 0 0 0;
@@ -374,7 +375,7 @@
cursor: pointer;
}
- label {
+ label, p.content {
font-size: 14px;
}
@@ -439,6 +440,126 @@
}
}
}
+
+ section.icon-status-container {
+ position: relative;
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ min-height: 3em;
+ padding: 0.5em 0.5em 0.5em 5em;
+
+ border-radius: 0.5em;
+
+ img {
+ position: absolute;
+ left: 0.5em;
+ top: 50%;
+ width: 3em;
+ height: auto;
+ margin-top: -1.5em;
+
+ border-radius: 0.25em;
+ }
+
+ p.exp {
+ margin: 0;
+ }
+ }
+
+ ul.user-icon-select {
+ margin-bottom: 1em;
+ }
+
+ ul.profile-image-gallery {
+ list-style-type: none;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 0.5em;
+ max-width: 700px;
+
+ li {
+ display: inline-block;
+
+ button {
+ position: relative;
+ padding: 0;
+ border: none;
+ background: none;
+ cursor: pointer;
+
+ &.selected {
+ &:after {
+ position: absolute;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ content: "Selected";
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+
+ background: rgba(41, 91, 153, 0.85);
+
+ color: #fff;
+ font-size: 1em;
+
+ border-radius: 0.33em;
+ }
+ }
+
+ &:before {
+ position: absolute;
+ top: 0;
+ left: 0;
+ display: block;
+ content: "";
+ width: 100%;
+ height: 100%;
+ border-radius: 0.5em;
+ }
+
+ &:hover {
+ &:before {
+ background: linear-gradient(to top, $color-features 0%, rgba(255, 255, 255, 0) 40%);
+ }
+ }
+ }
+
+ img {
+ width: 100px;
+ height: auto;
+ border-radius: 0.5em;
+ }
+ }
+
+ &.inactive {
+
+ button {
+
+ &:hover {
+ cursor: default;
+
+ &:before {
+ background: none;
+ }
+ }
+
+ &.selected {
+ &:before, &:after {
+ display: none;
+ }
+ }
+ }
+
+ img {
+ filter: saturate(0);
+ }
+ }
+
+ }
}
} // end settings
@@ -620,6 +741,15 @@
font-weight: 700;
}
}
+
+ section.icon-status-container {
+ background: $color-input-box-bg-dark;
+ border-color: $color-input-box-border-dark;
+
+ p.exp strong {
+ color: #fff;
+ }
+ }
} // end settings
} // end section.page
}
diff --git a/src/components/settings-page.jsx b/src/components/settings-page.jsx
index 3aca4ac78..4be239a95 100644
--- a/src/components/settings-page.jsx
+++ b/src/components/settings-page.jsx
@@ -1,7 +1,7 @@
import React, { useState, useRef, useEffect } from 'react'
import { useQuery } from 'react-query'
import LoadingIcon from './loading-icon'
-import {apiGetUser} from '../util/api'
+import { apiGetUser, apiGetSiteImages } from '../util/api'
import useUpdateUserSettings from './hooks/useUpdateUserSettings'
import Header from './header'
import './profile-page.scss'
@@ -21,10 +21,11 @@ const SettingsPage = () => {
const [state, setState] = useState({
notify: false,
useGravatar: false,
- theme: 'light'
+ theme: 'light',
+ profileImage: -1
})
- const { data: currentUser, isFetching} = useQuery({
+ const { data: currentUser, isLoading} = useQuery({
queryKey: ['user', 'me'],
queryFn: ({ queryKey }) => {
const [_key, user] = queryKey
@@ -44,79 +45,96 @@ const SettingsPage = () => {
})
useEffect(() => {
- if (mounted && ! isFetching && currentUser) {
+ if (mounted && ! isLoading && currentUser) {
mounted.current = true
setState({
notify: currentUser.profile_fields.notify,
useGravatar: currentUser.profile_fields.useGravatar,
- theme: currentUser.profile_fields.theme
+ theme: currentUser.profile_fields.theme,
+ profileImage: currentUser.profile_fields.profileImage
})
}
return () => {
mounted.current = false
}
- },[isFetching])
+ },[isLoading])
+
+ const { data: profileImages, isFetching: isFetchingProfileImages} = useQuery({
+ queryKey: ['profile-images'],
+ queryFn: () => apiGetSiteImages('profile'),
+ staleTime: Infinity,
+ })
const mutateUserSettings = useUpdateUserSettings()
const _updateEmailPref = event => {
- setState({...state, notify: !state.notify})
-
+ _patchSubmitSettings('notify', !state.notify)
}
const _updateIconPref = pref => {
- setState({...state, useGravatar: pref})
+ _patchSubmitSettings('useGravatar', pref)
+ }
+
+ const _selectProfilePicture = (e) => {
+ const imageId = parseInt(e.target.getAttribute('data-image-id'))
+ _patchSubmitSettings('profileImage',imageId)
}
const _updateThemePref = (e) => {
const newTheme = e.target.value
- setState(prev => ({ ...prev, theme: newTheme }))
+ _patchSubmitSettings('theme', newTheme)
window.theme = newTheme
}
- const _submitSettings = () => {
+ const _onUpdateSuccess = (data) => {
+ if (data) {
+ setState(prev => ({ ...data }))
+ }
+
+ // Immediately apply/revoke theme to body
+ if (data.theme === 'dark') {
+ document.body.classList.add('darkMode')
+ } else if (data.theme === 'light') {
+ document.body.classList.remove('darkMode')
+ } else if (data.theme === 'os') {
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
+ if (prefersDark) {
+ document.body.classList.add('darkMode')
+ } else {
+ document.body.classList.remove('darkMode')
+ }
+ }
+ }
+
+ const _onUpdateFailure = (err) => {
+ if (err.message == 'Invalid Login') {
+ setAlertDialog({
+ enabled: true,
+ message: 'You must be logged in to view your settings.',
+ title: 'Login Required',
+ fatal: true,
+ enableLoginButton: true
+ })
+ } else if (err.message == 'Unauthorized') {
+ setAlertDialog({
+ enabled: true,
+ message: 'You do not have permission to view this page.',
+ title: 'Action Failed',
+ fatal: err.halt,
+ enableLoginButton: false
+ })
+ }
+ setError((err.message || 'Error') + ': Failed to update settings.')
+ }
+
+ const _patchSubmitSettings = (key, value) => {
mutateUserSettings.mutate({
user_id: currentUser.id,
profile_fields: {
- notify: state.notify,
- useGravatar: state.useGravatar,
- theme: state.theme
- },
- successFunc: () => {
- // Immediately apply/revoke theme to body
- if (state.theme === 'dark') {
- document.body.classList.add('darkMode')
- } else if (state.theme === 'light') {
- document.body.classList.remove('darkMode')
- } else if (state.theme === 'os') {
- const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
- if (prefersDark) {
- document.body.classList.add('darkMode')
- } else {
- document.body.classList.remove('darkMode')
- }
- }
+ [key]: value
},
- errorFunc: (err) => {
- if (err.message == 'Invalid Login') {
- setAlertDialog({
- enabled: true,
- message: 'You must be logged in to view your settings.',
- title: 'Login Required',
- fatal: true,
- enableLoginButton: true
- })
- } else if (err.message == 'Unauthorized') {
- setAlertDialog({
- enabled: true,
- message: 'You do not have permission to view this page.',
- title: 'Action Failed',
- fatal: err.halt,
- enableLoginButton: false
- })
- }
- setError((err.message || 'Error') + ': Failed to update settings.')
- }
+ successFunc: _onUpdateSuccess,
+ errorFunc: _onUpdateFailure
})
}
@@ -142,7 +160,25 @@ const SettingsPage = () => {
}
let mainContentRender =
- if ( !isFetching && currentUser ) {
+ if ( !isLoading && currentUser ) {
+
+ let profileImageList = []
+ if (profileImages && !isFetchingProfileImages) {
+ profileImageList = profileImages.map((image, index) => {
+ return (
+
+
+
+
+
+ )
+ })
+ }
+
mainContentRender = (
@@ -186,7 +222,7 @@ const SettingsPage = () => {
- User Icon
+ User Icon Selection
+
Theme
-
- Save
-
{errorRender}
diff --git a/src/components/site-admin-page.jsx b/src/components/site-admin-page.jsx
new file mode 100644
index 000000000..79d43ae13
--- /dev/null
+++ b/src/components/site-admin-page.jsx
@@ -0,0 +1,319 @@
+import { useQuery } from 'react-query'
+import { apiUploadSiteImage, apiGetSiteImages, apiDeleteSiteImage, apiGetSiteMessages, apiUploadSiteMessage, apiDeleteSiteMessage } from '../util/api'
+import React, { useState, useRef, useEffect } from 'react'
+import Header from './header'
+import './site-admin-page.scss'
+
+
+const SiteAdminPage = () => {
+
+ const [pageState, setPageState] = useState({
+ mode: 'image'
+ })
+
+ const [imageState, setImageState] = useState({
+ imageUploadNotice: '',
+ isUploadingImage: false,
+ imageUploadError: false,
+ profileImages: []
+ })
+
+ const [messageState, setMessageState] = useState({
+ messageUploadNotice: '',
+ isUploadingMessage: false,
+ messageUploadError: false,
+ messages: []
+ })
+
+ const handleImageUpload = async (event) => {
+ event.preventDefault()
+ const file = event.target[1].files[0]
+ const imgType = event.target[0].value
+ if (!file) setImageState(imageState => ({...imageState, imageUploadError: true, imageUploadNotice: 'You must select a file first.'}))
+ else {
+ setImageState(imageState => ({...imageState, isUploadingImage: true}))
+ apiUploadSiteImage(imgType, file)
+ .then(res => {
+ refetchProfileImages()
+ setImageState((imageState) => ({
+ ...imageState,
+ imageUploadNotice: 'Image uploaded successfully.'
+ }))
+ })
+ .catch(err => {
+ setImageState((imageState) => ({
+ ...imageState,
+ isUploadingImage: false,
+ imageUploadError: true,
+ imageUploadNotice: 'An error occurred while uploading the image.'
+ }))
+ })
+ }
+ }
+
+ const handleMessageUpload = async (event) => {
+ event.preventDefault()
+
+ const type = event.target.message_type.value
+ const content = event.target.message_content.value
+ const start_time = event.target.start_time.value || null
+ const end_time = event.target.end_time.value || null
+
+ apiUploadSiteMessage(type, content, start_time, end_time)
+ .then(res => {
+ refetchSiteMessages()
+ setMessageState((messageState) => ({
+ ...messageState,
+ messageUploadNotice: 'Message submitted successfully.'
+ }))
+ })
+
+ }
+
+ const handleImageRemoveRequest = async (event) => {
+ const id = event.target.dataset.ormid
+
+ apiDeleteSiteImage(id).then(res => {
+ console.log('delete image request response!', res)
+ refetchProfileImages()
+ })
+ }
+
+ const handleMessageClearRequest = async (event) => {
+ const id = event.target.dataset.ormid
+
+ apiDeleteSiteMessage(id).then(res => {
+ console.log('delete message request response!', res)
+ refetchSiteMessages()
+ })
+
+ }
+
+ const {data: profileImages, refetch: refetchProfileImages } = useQuery({
+ queryKey: ['profile-images'],
+ queryFn: () => apiGetSiteImages('profile'),
+ enabled: pageState.mode == 'image',
+ staleTime: Infinity,
+ retry: false
+ })
+
+ const {data: siteMessages, refetch: refetchSiteMessages } = useQuery({
+ queryKey: ['site-messages', 'all'],
+ queryFn: () => apiGetSiteMessages([], true),
+ enabled: pageState.mode == 'message',
+ staleTime: Infinity,
+ retry: false
+ })
+
+ useEffect(() => {
+ const handleHashChange = () => {
+ if (window.location.hash === '#images') {
+ setPageState((pageState) => ({ ...pageState, mode: 'image' }))
+ }
+ if (window.location.hash === '#messages') {
+ setPageState((pageState) => ({ ...pageState, mode: 'message' }))
+ }
+ }
+
+ handleHashChange()
+ window.addEventListener('hashchange', handleHashChange)
+
+ return () => {
+ window.removeEventListener('hashchange', handleHashChange)
+ }
+ }, [])
+
+ useEffect(() => {
+ if (profileImages != undefined) {
+ setImageState((imageState) => ({
+ ...imageState,
+ isUploadingImage: false,
+ imageUploadError: false,
+ profileImages: profileImages,
+ }))
+ }
+ },[profileImages])
+
+ useEffect(() => {
+ if (siteMessages != undefined) {
+ setMessageState((messageState) => ({
+ ...messageState,
+ isUploadingMessage: false,
+ messageUploadError: false,
+ messages: siteMessages
+ }))
+ }
+ },[siteMessages])
+
+ let contentRender = null
+ if (pageState.mode == 'image') {
+
+ let profileGalleryRender = null
+ if ( !!imageState.profileImages) {
+ const profileImageList = imageState.profileImages.map((img, index) => {
+ return (
+
+
+
+ Remove
+
+
+ )
+ })
+ profileGalleryRender = (
+
+ )
+ }
+
+ contentRender = (
+ <>
+ Image Management
+
+
+
+ {imageState.imageUploadNotice}
+
+
+
+ Profile Images
+ {profileGalleryRender}
+
+
+ >
+ )
+ }
+ else if (pageState.mode == 'message') {
+
+ let siteMessageListRender = null
+ if (!!messageState.messages) {
+ const messageList = messageState.messages.map((msg, index) => {
+ return (
+
+
+ {msg.message_text}
+
+ Start time:
+ {`${ !!msg.start_at ? msg.start_at : 'Not Set'}`}
+ End at:
+ {`${ !!msg.end_at ? msg.end_at : 'Not Set'}`}
+
+
+ Clear
+
+
+ )
+ })
+
+ siteMessageListRender = (
+
+ )
+ }
+
+ contentRender = (
+ <>
+ Site Messaging Management
+
+
+ Current Messaging
+ { siteMessageListRender }
+
+
+
+ >
+ )
+ }
+
+ return (
+ <>
+
+
+ >
+ )
+}
+
+export default SiteAdminPage
diff --git a/src/components/site-admin-page.scss b/src/components/site-admin-page.scss
new file mode 100644
index 000000000..a53651c01
--- /dev/null
+++ b/src/components/site-admin-page.scss
@@ -0,0 +1,381 @@
+@import 'include.scss';
+
+.support-page {
+ background-color: #fff;
+ position: relative;
+ top: 40px;
+ margin: 10px auto;
+ padding: 0px;
+ display: block;
+ border: 1px solid #ccc;
+ box-shadow: 0 0 10px #aaa;
+ width: 720px;
+
+ font-weight: 400;
+
+ .page {
+ padding-bottom: 3em;
+
+ .loading {
+ position: relative;
+ height: 40px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ margin: 5px 0;
+
+ .loading-text {
+ margin-left: 50px;
+ }
+ }
+
+ .top {
+ position: relative;
+ padding: 10px;
+ width: calc(100% - 20px);
+
+ color: #ffffff;
+ background: $gray;
+
+ h1 {
+ margin: 0;
+ padding: 5px 0;
+ }
+
+ h2 {
+ font-size: 14px;
+ margin: 0;
+ padding: 5px 0;
+ }
+ }
+
+ nav {
+ display: flex;
+ flex-direction: row;
+ gap: 0.5em;
+
+ padding: 0.5em 0.5em 0 0.5em;
+ background: $light-gray;
+
+ a.nav_button {
+ display: inline-block;
+ padding: 0.5em 1em;
+
+ background: #fff;
+
+ color: #000;
+ font-weight: 400;
+ font-size: 0.9em;
+
+ border-top-left-radius: 0.5em;
+ border-top-right-radius: 0.5em;
+
+ &.selected {
+ border-bottom: solid 2px $color-features;
+ }
+ }
+ }
+
+ h2 {
+ margin: 15px;
+ }
+
+ .admin-subsection {
+ padding: 15px;
+
+ .top {
+ width: calc(100% + 10px);
+ left: -15px;
+ }
+
+ .management-subsection {
+ padding: 1em 0;
+ border-bottom: solid 1px $light-gray;
+
+ &:last-child {
+ border-bottom: none;
+ }
+
+ ul.profile-images {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 1em;
+
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+
+ li {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: center;
+ gap: 0.5em;
+
+ img {
+ width: 180px;
+ height: auto;
+ border-radius: 0.25em;
+ border: solid 1px rgb(200,200,200);
+ }
+ }
+ }
+
+ ul.site-messages {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em;
+ list-style-type: none;
+ margin: 0;
+ padding: 0;
+
+ li {
+ position: relative;
+ margin: 1.5em 0 1em 0;
+ padding: 0.5em 0.5em 0.5em 1em;
+ background: $very-light-gray;
+ border-radius: 0.5em;
+ border-top-left-radius: 0;
+
+ header {
+ position: absolute;
+ top: -2em;
+ left: 0;
+ height: 1em;
+ margin: 0;
+ padding: 0.5em 1em;
+
+ font-size: 0.8em;
+ font-weight: 700;
+
+ background: $very-light-gray;
+ border-top-left-radius: 0.5em;
+ border-top-right-radius: 0.5em;
+ }
+
+ h5 {
+ font-size: 1.1em;
+ font-weight: 400;
+ margin: 0.5em 0;
+ padding: 0;
+ }
+
+ span, dt {
+ font-size: 0.75em;
+ font-weight: 700;
+ margin-bottom: 0.5em;
+ }
+
+ dd {
+ margin: 0 0 0.5em 0;
+ }
+
+ button {
+ margin-top: 1em;
+ }
+ }
+ }
+
+ form {
+ display: flex;
+ justify-content: flex-start;
+ align-items: center;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 1em;
+ }
+
+ .notice {
+ display: block;
+ margin: 1em 0 0.25em 0;
+ font-size: 1.1em;
+ font-weight: 400;
+
+ &.error {
+ font-weight: 700;
+ color: $color-red;
+ }
+ }
+
+ label {
+ font-size: 0.9em;
+ font-weight: 700;
+ }
+
+ // select.message_uploader_select {
+
+ // }
+
+ textarea.message_uploader_input {
+ display: block;
+ width: 90%;
+ min-height: 5em;
+ margin: 1em 10% 1em 0;
+ padding: 0.5em;
+
+ border: solid 1px $gray;
+ border-radius: 0.5em;
+
+ font-family: 'Lato', arial, sans-serif;
+ }
+
+ section.form-subsection {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ align-items: flex-start;
+ gap: 0.5em;
+ width: 100%;
+ margin-bottom: 0.5em;
+
+ input.message_datetime {
+ display: block;
+ }
+ }
+ }
+ }
+ }
+
+ div.error {
+ width: calc(100% - 20px);
+ left: 0;
+ padding: 10px;
+
+ color: #fff;
+ background: $color-red;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ }
+}
+
+.darkMode .support-page {
+ background-color: #21232a;
+ border: 1px solid #181920;
+ box-shadow: 0 0 10px #08090c;
+
+ .page {
+ #breadcrumb-container {
+ background: #21232a;
+ border: 1px solid #181920;
+
+ .breadcrumb {
+ a {
+ color: #91c8fc;
+ }
+ }
+
+ svg {
+ fill: #d8d8d8;
+ }
+ }
+
+ .top {
+ color: #ffffff;
+ background: #1d1f25;
+ }
+
+ .search {
+ input.user_search {
+ color: #fff;
+ background-color: $color-input-box-border-dark;
+ border: solid 1px $color-input-box-border-dark;
+ }
+ }
+
+ .search_match {
+ background: #1d1f25;
+ border: 1px solid #181920;
+ }
+
+ .search_error {
+ color: red;
+ }
+
+ .search_list {
+ .search_match.deleted {
+ background-color: rgba(0, 0, 0, 0.2);
+ }
+
+ ul li.type {
+ color: #d8d8d8;
+ }
+
+ ul li.deleted {
+ color: red;
+ }
+ }
+
+ .admin-subsection {
+ &.overview {
+
+ input {
+ color: #fff;
+ background: $color-input-box-bg-dark;
+ border: solid 1px $color-input-box-border-dark;
+ }
+
+ .right-justify {
+ .apply-changes {
+ .error-text {
+ color: red;
+ }
+
+ .success-text {
+ color: green;
+ }
+ }
+ }
+ }
+
+ &.instances {
+ ul {
+ li.instance {
+ div.widget-title {
+ background: #4c4e58;
+
+ span.incomplete-status-holder {
+ color: #555;
+ }
+ }
+
+ &.expanded {
+ div.widget-title {
+ background: #295b99;
+ }
+
+ .info-holder {
+ background: #1b304b;
+ }
+
+ .inst-info {
+ .scores {
+ background: #21232a;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .inst-action-buttons {
+ button:disabled {
+ background: #ffba5d7a;
+ }
+ }
+
+ }
+
+ .page > ul {
+ > li {
+ background-color: #21232a;
+ border: 1px solid #181920;
+ }
+ }
+}
+
+.clickable {
+ cursor: pointer;
+}
diff --git a/src/components/widget-player.jsx b/src/components/widget-player.jsx
index c45c611f4..3ae3be578 100644
--- a/src/components/widget-player.jsx
+++ b/src/components/widget-player.jsx
@@ -10,6 +10,7 @@ import LoadingIcon from './loading-icon'
import './widget-player.scss'
const HEARTBEAT_INTERVAL = 15000 // 15 seconds for each heartbeat
+const MIN_WIDGET_HEIGHT = 640
const initLogs = () => ({ play: [], storage: [] })
@@ -122,6 +123,9 @@ const WidgetPlayer = ({instanceId, playId, minHeight=0, minWidth=0,showFooter=tr
const scoreScreenUrlRef = useRef(null)
const darkModeRef = useRef(false)
+ const urlParams = new URLSearchParams(window.location.search)
+ const hasLti = urlParams.has('token')
+
/*********************** queries ***********************/
const { data: inst } = useQuery({
@@ -368,7 +372,7 @@ const WidgetPlayer = ({instanceId, playId, minHeight=0, minWidth=0,showFooter=tr
}
else if( ! ['react-devtools-content-script', 'react-devtools-bridge', 'react-devtools-inject-backend'].includes(e.data.source)) {
throw new Error(
- `Post message Origin does not match. Expected: ${expectedOrigin}, Actual: ${origin}`
+ `Post message Origin does not match. Expected: ${window.BASE_URL} || ${window.STATIC_CROSSDOMAIN}, Actual: ${origin}`
)
}
}
@@ -381,6 +385,12 @@ const WidgetPlayer = ({instanceId, playId, minHeight=0, minWidth=0,showFooter=tr
setStartTime(new Date().getTime())
_sendToWidget('initWidget', [qset, convertedInstance, window.BASE_URL, window.MEDIA_URL])
setPlayState('playing')
+
+ // if embedded in an LTI context, preemptively set the height of the player frame
+ if (hasLti) {
+ const initHeight = inst.widget.height != 0 ? inst.widget.height : MIN_WIDGET_HEIGHT
+ _setHeight(initHeight)
+ }
}
}
@@ -544,6 +554,17 @@ const WidgetPlayer = ({instanceId, playId, minHeight=0, minWidth=0,showFooter=tr
const min_h = inst.widget.height
let desiredHeight = Math.max(h, min_h)
setAttributes((oldData) => ({...oldData, height: `${desiredHeight}px`}))
+
+ // The presence of the token param indicates an LTI play. Send the frameResize postMessage to the parent frame (the LMS)
+ if (hasLti) {
+ window.parent.postMessage(
+ {
+ subject: 'lti.frameResize',
+ height: desiredHeight + 60, // add 60 to desiredHeight for footer
+ },
+ '*'
+ )
+ }
}
const _setVerticalScroll = location => {
diff --git a/src/components/widget-player.scss b/src/components/widget-player.scss
index bb12ac1dd..637fb3411 100644
--- a/src/components/widget-player.scss
+++ b/src/components/widget-player.scss
@@ -7,22 +7,24 @@ body {
}
#app {
+ display: flex;
+ flex-direction: column;
+
position: relative;
height: calc(100% - 42px);
}
section.widget {
display: none;
- position: absolute;
- left: 20px;
- right: 20px;
- bottom: 20px;
- top: 95px;
+ height: 100%;
min-height: 400px;
min-width: 400px;
+ padding: 0 1em;
+
+
&.preview {
- top: 150px;
+ padding: 2.5em 1em 0 1em;
.center {
top: -44px;
diff --git a/src/site-admin.js b/src/site-admin.js
new file mode 100644
index 000000000..40fea5f04
--- /dev/null
+++ b/src/site-admin.js
@@ -0,0 +1,13 @@
+import React from 'react'
+import {createRoot} from 'react-dom/client'
+import { QueryClient, QueryClientProvider, QueryCache } from 'react-query'
+import SiteAdminPage from './components/site-admin-page'
+
+const queryCache = new QueryCache()
+export const queryClient = new QueryClient({ queryCache })
+
+const root = createRoot(document.getElementById('app'));
+root.render(
+
+
+ )
diff --git a/src/util/api.js b/src/util/api.js
index 73c0144ed..f03b2cae1 100644
--- a/src/util/api.js
+++ b/src/util/api.js
@@ -609,7 +609,7 @@ export const apiGetUserPlaySessions = (user, pageParam = 1, admin_activity = fal
}
export const apiUpdateUserSettings = (settings) => {
- return handleRequest(methods.PUT, `/api/users/${settings.user_id}/profile_fields/`, settings)
+ return handleRequest(methods.PATCH, `/api/users/${settings.user_id}/profile_fields/`, settings)
}
export const apiGetUserRoles = (id) => {
@@ -798,3 +798,62 @@ export const readFromStorage = () => {
}
}, [])
}
+
+export const apiGetSiteImages = (type) => {
+
+ switch (type) {
+ case 'profile':
+ type = 'PROFILE_IMAGE'
+ break
+ default:
+ break
+ }
+
+ return handleRequest(methods.GET, `/api/site-images/?type=${type}`)
+}
+
+export const apiDeleteSiteImage = (id) => {
+ return handleRequest(methods.DELETE, `/api/site-images/${id}/`)
+}
+
+export const apiUploadSiteImage = (type, file) => {
+ const formData = new FormData()
+ formData.append('image', file)
+ formData.append('image_type', type)
+ return handleRequest(methods.POST, `/api/site-images/`, {}, { headers: { 'X-CSRFToken': getCSRFToken(), }, body: formData })
+}
+
+export const apiGetSiteMessages = (types, include_all=false) => {
+
+ let path = '/api/site-messages/'
+
+ if (types.length > 1) {
+ path = `${path}?types=${types.join(',')}`
+ }
+
+ if (types.length === 1) {
+ path = `/api/site-messages/?type=${types[0]}`
+ }
+
+ if (include_all) {
+ path = `${path}${path.includes('?') ? '&' : '?'}include_expired=true`
+ }
+
+ return handleRequest(methods.GET, path)
+
+}
+
+export const apiUploadSiteMessage = (type, content, start_time, end_time) => {
+ const formData = new FormData()
+ formData.append('message_type', type)
+ formData.append('message_text', content)
+
+ if (start_time != null) formData.append('start_at', start_time)
+ if (end_time != null) formData.append('end_at', end_time)
+
+ return handleRequest(methods.POST, `/api/site-messages/`, {}, { headers: { 'X-CSRFToken': getCSRFToken(), }, body: formData })
+}
+
+export const apiDeleteSiteMessage = (id) => {
+ return handleRequest(methods.DELETE, `/api/site-messages/${id}/`)
+}
diff --git a/yarn.lock b/yarn.lock
index a987e206a..1d690fd92 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -7,28 +7,12 @@
resolved "https://registry.yarnpkg.com/@adobe/css-tools/-/css-tools-4.4.1.tgz#2447a230bfe072c1659e6815129c03cf170710e3"
integrity sha512-12WGKBQzjUAI4ayyF4IAtfw2QR/IDoqk6jTddXDhtYTJF9ASmoE1zst7cVtP0aL/F1jUJL5r+JxKXKEgHNbEUQ==
-"@ampproject/remapping@^2.2.0":
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
- integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7"
+ integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==
dependencies:
- "@jridgewell/gen-mapping" "^0.3.0"
- "@jridgewell/trace-mapping" "^0.3.9"
-
-"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.10.4", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.23.5":
- version "7.23.5"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244"
- integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==
- dependencies:
- "@babel/highlight" "^7.23.4"
- chalk "^2.4.2"
-
-"@babel/code-frame@^7.28.6", "@babel/code-frame@^7.29.0":
- version "7.29.0"
- resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.0.tgz#7cd7a59f15b3cc0dcd803038f7792712a7d0b15c"
- integrity sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==
- dependencies:
- "@babel/helper-validator-identifier" "^7.28.5"
+ "@babel/helper-validator-identifier" "^7.29.7"
js-tokens "^4.0.0"
picocolors "^1.1.1"
@@ -37,44 +21,39 @@
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98"
integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==
-"@babel/core@^7.10.4", "@babel/core@^7.11.6", "@babel/core@^7.12.3":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.9.tgz#b028820718000f267870822fec434820e9b1e4d1"
- integrity sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==
+"@babel/compat-data@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629"
+ integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==
+
+"@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.29.6":
+ version "7.29.6"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.6.tgz#2f2c2ca1728ae73c9b68ece91d1ae6dee18ff83a"
+ integrity sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA==
dependencies:
- "@ampproject/remapping" "^2.2.0"
- "@babel/code-frame" "^7.23.5"
- "@babel/generator" "^7.23.6"
- "@babel/helper-compilation-targets" "^7.23.6"
- "@babel/helper-module-transforms" "^7.23.3"
- "@babel/helpers" "^7.23.9"
- "@babel/parser" "^7.23.9"
- "@babel/template" "^7.23.9"
- "@babel/traverse" "^7.23.9"
- "@babel/types" "^7.23.9"
+ "@babel/code-frame" "^7.29.0"
+ "@babel/generator" "^7.29.6"
+ "@babel/helper-compilation-targets" "^7.28.6"
+ "@babel/helper-module-transforms" "^7.28.6"
+ "@babel/helpers" "^7.29.2"
+ "@babel/parser" "^7.29.3"
+ "@babel/template" "^7.28.6"
+ "@babel/traverse" "^7.29.0"
+ "@babel/types" "^7.29.0"
+ "@jridgewell/remapping" "^2.3.5"
convert-source-map "^2.0.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.3"
semver "^6.3.1"
-"@babel/generator@^7.23.6", "@babel/generator@^7.7.2":
- version "7.23.6"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e"
- integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==
+"@babel/generator@^7.29.0", "@babel/generator@^7.29.6", "@babel/generator@^7.7.2":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
+ integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==
dependencies:
- "@babel/types" "^7.23.6"
- "@jridgewell/gen-mapping" "^0.3.2"
- "@jridgewell/trace-mapping" "^0.3.17"
- jsesc "^2.5.1"
-
-"@babel/generator@^7.29.0":
- version "7.29.1"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.1.tgz#d09876290111abbb00ef962a7b83a5307fba0d50"
- integrity sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==
- dependencies:
- "@babel/parser" "^7.29.0"
- "@babel/types" "^7.29.0"
+ "@babel/parser" "^7.29.7"
+ "@babel/types" "^7.29.7"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
@@ -93,14 +72,14 @@
dependencies:
"@babel/types" "^7.22.15"
-"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6":
- version "7.23.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991"
- integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==
+"@babel/helper-compilation-targets@^7.22.15", "@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.23.6", "@babel/helper-compilation-targets@^7.28.6":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042"
+ integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==
dependencies:
- "@babel/compat-data" "^7.23.5"
- "@babel/helper-validator-option" "^7.23.5"
- browserslist "^4.22.2"
+ "@babel/compat-data" "^7.29.7"
+ "@babel/helper-validator-option" "^7.29.7"
+ browserslist "^4.24.0"
lru-cache "^5.1.1"
semver "^6.3.1"
@@ -157,13 +136,6 @@
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674"
integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==
-"@babel/helper-hoist-variables@^7.22.5":
- version "7.22.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
- integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
- dependencies:
- "@babel/types" "^7.22.5"
-
"@babel/helper-member-expression-to-functions@^7.22.15", "@babel/helper-member-expression-to-functions@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366"
@@ -186,18 +158,7 @@
"@babel/traverse" "^7.28.6"
"@babel/types" "^7.28.6"
-"@babel/helper-module-transforms@^7.23.3":
- version "7.23.3"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1"
- integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==
- dependencies:
- "@babel/helper-environment-visitor" "^7.22.20"
- "@babel/helper-module-imports" "^7.22.15"
- "@babel/helper-simple-access" "^7.22.5"
- "@babel/helper-split-export-declaration" "^7.22.6"
- "@babel/helper-validator-identifier" "^7.22.20"
-
-"@babel/helper-module-transforms@^7.28.6":
+"@babel/helper-module-transforms@^7.23.3", "@babel/helper-module-transforms@^7.28.6":
version "7.28.6"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e"
integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==
@@ -262,31 +223,31 @@
dependencies:
"@babel/types" "^7.22.5"
-"@babel/helper-string-parser@^7.23.4":
- version "7.23.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83"
- integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==
-
-"@babel/helper-string-parser@^7.27.1":
- version "7.27.1"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz#54da796097ab19ce67ed9f88b47bb2ec49367687"
- integrity sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==
-
-"@babel/helper-validator-identifier@^7.22.20":
- version "7.22.20"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
- integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
+"@babel/helper-string-parser@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f"
+ integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==
"@babel/helper-validator-identifier@^7.28.5":
version "7.28.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz#010b6938fab7cb7df74aa2bbc06aa503b8fe5fb4"
integrity sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==
+"@babel/helper-validator-identifier@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2"
+ integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==
+
"@babel/helper-validator-option@^7.22.15", "@babel/helper-validator-option@^7.23.5":
version "7.23.5"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307"
integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==
+"@babel/helper-validator-option@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz#cf315be940213b354eb4abcc0bd01ebe3f73bc2a"
+ integrity sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==
+
"@babel/helper-wrap-function@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.20.tgz#15352b0b9bfb10fc9c76f79f6342c00e3411a569"
@@ -296,35 +257,20 @@
"@babel/template" "^7.22.15"
"@babel/types" "^7.22.19"
-"@babel/helpers@^7.23.9":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d"
- integrity sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==
+"@babel/helpers@^7.29.2":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
+ integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==
dependencies:
- "@babel/template" "^7.23.9"
- "@babel/traverse" "^7.23.9"
- "@babel/types" "^7.23.9"
+ "@babel/template" "^7.29.7"
+ "@babel/types" "^7.29.7"
-"@babel/highlight@^7.23.4":
- version "7.23.4"
- resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b"
- integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.29.0", "@babel/parser@^7.29.3", "@babel/parser@^7.29.7":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
+ integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==
dependencies:
- "@babel/helper-validator-identifier" "^7.22.20"
- chalk "^2.4.2"
- js-tokens "^4.0.0"
-
-"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.23.9":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b"
- integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==
-
-"@babel/parser@^7.28.6", "@babel/parser@^7.29.0":
- version "7.29.3"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.3.tgz#116f70a77958307fceac27747573032f8a62f88e"
- integrity sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==
- dependencies:
- "@babel/types" "^7.29.0"
+ "@babel/types" "^7.29.7"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3":
version "7.23.3"
@@ -1041,39 +987,14 @@
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.27.1.tgz#9fce313d12c9a77507f264de74626e87fd0dc541"
integrity sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==
-"@babel/template@^7.22.15", "@babel/template@^7.23.9", "@babel/template@^7.3.3":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a"
- integrity sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==
+"@babel/template@^7.22.15", "@babel/template@^7.28.6", "@babel/template@^7.29.7", "@babel/template@^7.3.3":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
+ integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==
dependencies:
- "@babel/code-frame" "^7.23.5"
- "@babel/parser" "^7.23.9"
- "@babel/types" "^7.23.9"
-
-"@babel/template@^7.28.6":
- version "7.28.6"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.28.6.tgz#0e7e56ecedb78aeef66ce7972b082fce76a23e57"
- integrity sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==
- dependencies:
- "@babel/code-frame" "^7.28.6"
- "@babel/parser" "^7.28.6"
- "@babel/types" "^7.28.6"
-
-"@babel/traverse@^7.23.9":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950"
- integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==
- dependencies:
- "@babel/code-frame" "^7.23.5"
- "@babel/generator" "^7.23.6"
- "@babel/helper-environment-visitor" "^7.22.20"
- "@babel/helper-function-name" "^7.23.0"
- "@babel/helper-hoist-variables" "^7.22.5"
- "@babel/helper-split-export-declaration" "^7.22.6"
- "@babel/parser" "^7.23.9"
- "@babel/types" "^7.23.9"
- debug "^4.3.1"
- globals "^11.1.0"
+ "@babel/code-frame" "^7.29.7"
+ "@babel/parser" "^7.29.7"
+ "@babel/types" "^7.29.7"
"@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0":
version "7.29.0"
@@ -1088,22 +1009,13 @@
"@babel/types" "^7.29.0"
debug "^4.3.1"
-"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.23.6", "@babel/types@^7.23.9", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
- version "7.23.9"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002"
- integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==
- dependencies:
- "@babel/helper-string-parser" "^7.23.4"
- "@babel/helper-validator-identifier" "^7.22.20"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.28.6", "@babel/types@^7.29.0":
- version "7.29.0"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.0.tgz#9f5b1e838c446e72cf3cd4b918152b8c605e37c7"
- integrity sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.4", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4":
+ version "7.29.7"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
+ integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==
dependencies:
- "@babel/helper-string-parser" "^7.27.1"
- "@babel/helper-validator-identifier" "^7.28.5"
+ "@babel/helper-string-parser" "^7.29.7"
+ "@babel/helper-validator-identifier" "^7.29.7"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
@@ -1370,7 +1282,7 @@
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
-"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
+"@jridgewell/gen-mapping@^0.3.0":
version "0.3.3"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
@@ -1379,7 +1291,7 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/gen-mapping@^0.3.12":
+"@jridgewell/gen-mapping@^0.3.12", "@jridgewell/gen-mapping@^0.3.5":
version "0.3.13"
resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz#6342a19f44347518c93e43b1ac69deb3c4656a1f"
integrity sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==
@@ -1387,6 +1299,14 @@
"@jridgewell/sourcemap-codec" "^1.5.0"
"@jridgewell/trace-mapping" "^0.3.24"
+"@jridgewell/remapping@^2.3.5":
+ version "2.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/remapping/-/remapping-2.3.5.tgz#375c476d1972947851ba1e15ae8f123047445aa1"
+ integrity sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.5"
+ "@jridgewell/trace-mapping" "^0.3.24"
+
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
@@ -1415,7 +1335,7 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz#6912b00d2c631c0d15ce1a7ab57cd657f2a8f8ba"
integrity sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==
-"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9":
+"@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.18", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.22"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz#72a621e5de59f5f1ef792d0793a82ee20f645e4c"
integrity sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==
@@ -1634,6 +1554,141 @@
unist-util-visit "^5.0.0"
vfile "^6.0.0"
+"@noble/hashes@1.4.0":
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426"
+ integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==
+
+"@peculiar/asn1-cms@^2.6.0", "@peculiar/asn1-cms@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-cms/-/asn1-cms-2.8.0.tgz#b48a8389319228f929e9acd8cee8da6c858738de"
+ integrity sha512-NgekZOrSJFSBFLFoLfwePguAWAx7z1+f2TEsWFUMyiqqfntZ4+S/S5hzqME3q4pCA0iOsFKdwiQ35dwY24eVqA==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ "@peculiar/asn1-x509-attr" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-csr@^2.6.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-csr/-/asn1-csr-2.8.0.tgz#c9bb5dec2eaff824a705e82a4a58d45e6d2c35d0"
+ integrity sha512-akbF8+uvleHs8sejNPQxwmVFuInAg6FMNHOwMILXfP518YfFJwdR3jr6oNUPOaEJfuEhn/vkNOCIT6ASUd4mbg==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-ecc@^2.6.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-ecc/-/asn1-ecc-2.8.0.tgz#d51ab2b07eca98e0cf492d051e98bbd0a071305a"
+ integrity sha512-ohwlk+u9Rv2NOAY1c6MfHj45ATVF8R1DUN/WCgABiRtLi2ZftlZWZX7KvpAbU8v9xPcmoILfELeEABj/rn18AQ==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-pfx@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-pfx/-/asn1-pfx-2.8.0.tgz#8e189b6455e2bf9e5f921bb150ea86d7e7d1875d"
+ integrity sha512-5yof1ytoB++RQtaFbqSUJ8pxDJtZT6vbVqZ8XoJ61ph7UjNVvfFwAilnCodqkNsAodpy13gDhoxZXw00pghnyg==
+ dependencies:
+ "@peculiar/asn1-cms" "^2.8.0"
+ "@peculiar/asn1-pkcs8" "^2.8.0"
+ "@peculiar/asn1-rsa" "^2.8.0"
+ "@peculiar/asn1-schema" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-pkcs8@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.8.0.tgz#a46cf8857b9b063896afa41d2b8b2aa6a07a70a2"
+ integrity sha512-qAKXtLpBEw9LqhKpjw3ajZSXlBur+ipW+y2ivVBQAG6F6qRx94yO+1ZR4mvw+YaCfKSaOzLeYEzsPaBp4SJELA==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-pkcs9@^2.6.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.8.0.tgz#6d62697af0bbd4f30fdf0d23b4018f3f09620de3"
+ integrity sha512-b5nDWCnkV60+cQ141D6sVVwK9nz64R5n3zSVnklGd+ECdkW2Ol3U1a6yYFlalpSOaD557yuJB64A+q42jG7lUQ==
+ dependencies:
+ "@peculiar/asn1-cms" "^2.8.0"
+ "@peculiar/asn1-pfx" "^2.8.0"
+ "@peculiar/asn1-pkcs8" "^2.8.0"
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ "@peculiar/asn1-x509-attr" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-rsa@^2.6.0", "@peculiar/asn1-rsa@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-rsa/-/asn1-rsa-2.8.0.tgz#9d98d0fc42fec50119d2881b8a9925d36daaea73"
+ integrity sha512-zHEUlCqB2mk7x2lxDwHHJy7hWZOPdGHVlsmITWKB5/PbQo61atbu9PJ/0r9dQNMwFzbKPXZ8uK8/91eUhRznSg==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-schema@^2.6.0", "@peculiar/asn1-schema@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz#69699f84259b2161607cabfc34e512a4023dbef9"
+ integrity sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==
+ dependencies:
+ "@peculiar/utils" "^2.0.2"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-x509-attr@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.8.0.tgz#bd168e3f5e8bc23e56b1a97891f9f2fb7f730204"
+ integrity sha512-tHjkfS/qhMnmrlB2J9NhflQlQ7In3khO3CfmVrriOlpTeErY9ZIKOso1hQ5JQiyrJ7ShvqVPk7E5fQmbclkSKA==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/asn1-x509" "^2.8.0"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/asn1-x509@^2.6.0", "@peculiar/asn1-x509@^2.8.0":
+ version "2.8.0"
+ resolved "https://registry.yarnpkg.com/@peculiar/asn1-x509/-/asn1-x509-2.8.0.tgz#9958a9ef35dec8426aabad78ffe8798e318b06e2"
+ integrity sha512-N0CMuhWUzsWEVq6F1q9X6+VKUnWzSW+cSVg+aPaGGwDdbFoFWTYgin5MHwXgpWd6y9COMBxnfy/Qc+Xc7F0Zwg==
+ dependencies:
+ "@peculiar/asn1-schema" "^2.8.0"
+ "@peculiar/utils" "^2.0.2"
+ asn1js "^3.0.10"
+ tslib "^2.8.1"
+
+"@peculiar/utils@^2.0.2":
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/@peculiar/utils/-/utils-2.0.3.tgz#a27ca4c4b73652e110f19a7d16d664f458a5528e"
+ integrity sha512-+oL3HPFRIZ1St2K50lWCXiioIgSoxzz7R1J3uF6neO2yl1sgmpgY6XXJH4BdpoDkMWznQTeYF6oWNDZLCdQ4eQ==
+ dependencies:
+ tslib "^2.8.1"
+
+"@peculiar/x509@^1.14.2":
+ version "1.14.3"
+ resolved "https://registry.yarnpkg.com/@peculiar/x509/-/x509-1.14.3.tgz#2c44c2b89474346afec38a0c2803ec4fb8ce959e"
+ integrity sha512-C2Xj8FZ0uHWeCXXqX5B4/gVFQmtSkiuOolzAgutjTfseNOHT3pUjljDZsTSxXFGgio54bCzVFqmEOUrIVk8RDA==
+ dependencies:
+ "@peculiar/asn1-cms" "^2.6.0"
+ "@peculiar/asn1-csr" "^2.6.0"
+ "@peculiar/asn1-ecc" "^2.6.0"
+ "@peculiar/asn1-pkcs9" "^2.6.0"
+ "@peculiar/asn1-rsa" "^2.6.0"
+ "@peculiar/asn1-schema" "^2.6.0"
+ "@peculiar/asn1-x509" "^2.6.0"
+ pvtsutils "^1.3.6"
+ reflect-metadata "^0.2.2"
+ tslib "^2.8.1"
+ tsyringe "^4.10.0"
+
"@popperjs/core@^2.11.6":
version "2.11.8"
resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.8.tgz#6b79032e760a0899cd4204710beede972a3a185f"
@@ -1705,9 +1760,9 @@
integrity sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==
"@tootallnate/once@2":
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.0.tgz#f544a148d3ab35801c1f633a7441fd87c2e484bf"
- integrity sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-2.0.1.tgz#35adc6222e3662fa2222ce123b961476a746b9ea"
+ integrity sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==
"@types/acorn@^4.0.0":
version "4.0.6"
@@ -1836,7 +1891,7 @@
"@types/range-parser" "*"
"@types/send" "*"
-"@types/express@*", "@types/express@^4.17.21":
+"@types/express@*", "@types/express@^4.17.25":
version "4.17.25"
resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.25.tgz#070c8c73a6fee6936d65c195dbbfb7da5026649b"
integrity sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==
@@ -1927,13 +1982,6 @@
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78"
integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==
-"@types/node-forge@^1.3.0":
- version "1.3.11"
- resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da"
- integrity sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==
- dependencies:
- "@types/node" "*"
-
"@types/node@*":
version "20.11.10"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.10.tgz#6c3de8974d65c362f82ee29db6b5adf4205462f9"
@@ -2224,7 +2272,7 @@ abbrev@1:
resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
-accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8:
+accepts@~1.3.4, accepts@~1.3.8:
version "1.3.8"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e"
integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==
@@ -2434,6 +2482,15 @@ arraybuffer.prototype.slice@^1.0.2:
is-array-buffer "^3.0.2"
is-shared-array-buffer "^1.0.2"
+asn1js@^3.0.10, asn1js@^3.0.6:
+ version "3.0.10"
+ resolved "https://registry.yarnpkg.com/asn1js/-/asn1js-3.0.10.tgz#df26c874c8a8b41ca605efea47b2ad07551013dd"
+ integrity sha512-S2s3aOytiKdFRdulw2qPE51MzjzVOisppcVv7jVFR+Kw0kxwvFrDcYA0h7Ndqbmj0HkMIXYWaoj7fli8kgx1eg==
+ dependencies:
+ pvtsutils "^1.3.6"
+ pvutils "^1.1.5"
+ tslib "^2.8.1"
+
astral-regex@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31"
@@ -2654,7 +2711,7 @@ broadcast-channel@^3.4.1:
rimraf "3.0.2"
unload "2.2.0"
-browserslist@^4.12.0, browserslist@^4.22.2, browserslist@^4.28.1:
+browserslist@^4.12.0, browserslist@^4.22.2, browserslist@^4.24.0, browserslist@^4.28.1:
version "4.28.2"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.2.tgz#f50b65362ef48974ca9f50b3680566d786b811d2"
integrity sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==
@@ -2684,16 +2741,16 @@ bundle-name@^4.1.0:
dependencies:
run-applescript "^7.0.0"
-bytes@3.0.0:
- version "3.0.0"
- resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
- integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==
-
-bytes@~3.1.2:
+bytes@3.1.2, bytes@~3.1.2:
version "3.1.2"
resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5"
integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==
+bytestreamjs@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/bytestreamjs/-/bytestreamjs-2.0.1.tgz#a32947c7ce389a6fa11a09a9a563d0a45889535e"
+ integrity sha512-U1Z/ob71V/bXfVABvNr/Kumf5VyeQRBEm6Txb0PQ6S7V5GpBM3w4Cbqz/xPDicR5tN0uvDifng8C+5qECeGwyQ==
+
call-bind-apply-helpers@^1.0.1, call-bind-apply-helpers@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz#4b5428c222be985d79c3d82657479dbe0b59b2d6"
@@ -2997,24 +3054,24 @@ compare-versions@^3.6.0:
resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.6.0.tgz#1a5689913685e5a87637b8d3ffca75514ec41d62"
integrity sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==
-compressible@~2.0.16:
+compressible@~2.0.18:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
dependencies:
mime-db ">= 1.43.0 < 2"
-compression@^1.7.4:
- version "1.7.4"
- resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
- integrity sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==
+compression@^1.8.1:
+ version "1.8.1"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.8.1.tgz#4a45d909ac16509195a9a28bd91094889c180d79"
+ integrity sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==
dependencies:
- accepts "~1.3.5"
- bytes "3.0.0"
- compressible "~2.0.16"
+ bytes "3.1.2"
+ compressible "~2.0.18"
debug "2.6.9"
- on-headers "~1.0.2"
- safe-buffer "5.1.2"
+ negotiator "~0.6.4"
+ on-headers "~1.1.0"
+ safe-buffer "5.2.1"
vary "~1.1.2"
concat-map@0.0.1:
@@ -3448,12 +3505,12 @@ debug@2.6.9:
dependencies:
ms "2.0.0"
-debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1:
- version "4.3.4"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
- integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+debug@4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1:
+ version "4.4.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
+ integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
dependencies:
- ms "2.1.2"
+ ms "^2.1.3"
debug@^3.2.7:
version "3.2.7"
@@ -3462,13 +3519,6 @@ debug@^3.2.7:
dependencies:
ms "^2.1.1"
-debug@^4.0.0:
- version "4.4.0"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a"
- integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==
- dependencies:
- ms "^2.1.3"
-
decamelize@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
@@ -4113,7 +4163,7 @@ expect@^29.7.0:
jest-message-util "^29.7.0"
jest-util "^29.7.0"
-express@^4.21.2:
+express@^4.22.1:
version "4.22.2"
resolved "https://registry.yarnpkg.com/express/-/express-4.22.2.tgz#c17ae0981e5efc24b22272f0e041c4662503b700"
integrity sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==
@@ -4268,15 +4318,15 @@ for-each@^0.3.3:
is-callable "^1.1.3"
form-data@^4.0.0:
- version "4.0.5"
- resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.5.tgz#b49e48858045ff4cbf6b03e1805cebcad3679053"
- integrity sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==
+ version "4.0.6"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827"
+ integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.8"
es-set-tostringtag "^2.1.0"
- hasown "^2.0.2"
- mime-types "^2.1.12"
+ hasown "^2.0.4"
+ mime-types "^2.1.35"
forwarded@0.2.0:
version "0.2.0"
@@ -4534,6 +4584,13 @@ hasown@^2.0.2:
dependencies:
function-bind "^1.1.2"
+hasown@^2.0.4:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.4.tgz#8c62d8cb90beb2aad5d0a5b67581ad9854c3f003"
+ integrity sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==
+ dependencies:
+ function-bind "^1.1.2"
+
hast-util-to-estree@^3.0.0:
version "3.1.3"
resolved "https://registry.yarnpkg.com/hast-util-to-estree/-/hast-util-to-estree-3.1.3.tgz#e654c1c9374645135695cc0ab9f70b8fcaf733d7"
@@ -4669,10 +4726,10 @@ http-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
-http-proxy-middleware@^2.0.7:
- version "2.0.9"
- resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.9.tgz#e9e63d68afaa4eee3d147f39149ab84c0c2815ef"
- integrity sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==
+http-proxy-middleware@^2.0.9:
+ version "2.0.10"
+ resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-2.0.10.tgz#b2df7b705203d7a8c269ac8450cf96b00c532f94"
+ integrity sha512-RKzRWNPxUZqbuk3BC5mGVJbBnWgr+diEnjJexIOytFbBzDy88Fbh/YvBr3DsNrl1jYAfjWfpATEv0NO35FDuPQ==
dependencies:
"@types/http-proxy" "^1.17.8"
http-proxy "^1.18.1"
@@ -5565,9 +5622,9 @@ js-sha3@0.8.0:
integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
js-yaml@^3.13.1:
- version "3.14.2"
- resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.2.tgz#77485ce1dd7f33c061fd1b16ecea23b55fcb04b0"
- integrity sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==
+ version "3.15.0"
+ resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.15.0.tgz#586e5214eafe3e893756a41e979b50d89d3e4a67"
+ integrity sha512-ttBQIIQPDeLjpPOohtUdXuXUVoA2uIB6fEH9HyJ7234s5mBJ5wTx20njxplLZQgLaOfpmPQA7X2t5AX6tIPbog==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
@@ -5604,11 +5661,6 @@ jsdom@^20.0.0:
ws "^8.11.0"
xml-name-validator "^4.0.0"
-jsesc@^2.5.1:
- version "2.5.2"
- resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
- integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
-
jsesc@^3.0.2:
version "3.1.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.1.0.tgz#74d335a234f67ed19907fdadfac7ccf9d409825d"
@@ -5669,12 +5721,12 @@ kleur@^3.0.3:
integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
launch-editor@^2.6.1:
- version "2.13.2"
- resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.13.2.tgz#41d51baaf8afb393224b89bd2bcb4e02f2306405"
- integrity sha512-4VVDnbOpLXy/s8rdRCSXb+zfMeFR0WlJWpET1iA9CQdlZDfwyLjUuGQzXU4VeOoey6AicSAluWan7Etga6Kcmg==
+ version "2.14.1"
+ resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.14.1.tgz#f7e0da3f58aaea03fea01074d840b5f739ed7ddc"
+ integrity sha512-QWBrQsMpH7gPr965dsKD/3cKWiNoTjpATQf++Xq63N6sKRGMwlVXz41O1IZTMfZQgBctD/K5Zt06+/I6pP6+HA==
dependencies:
picocolors "^1.1.1"
- shell-quote "^1.8.3"
+ shell-quote "^1.8.4"
leven@^3.1.0:
version "3.1.0"
@@ -5959,9 +6011,9 @@ mdast-util-phrasing@^4.0.0:
unist-util-is "^6.0.0"
mdast-util-to-hast@^13.0.0:
- version "13.2.0"
- resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.0.tgz#5ca58e5b921cc0a3ded1bc02eed79a4fe4fe41f4"
- integrity sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==
+ version "13.2.1"
+ resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053"
+ integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==
dependencies:
"@types/hast" "^3.0.0"
"@types/mdast" "^4.0.0"
@@ -6348,7 +6400,7 @@ mime-db@^1.54.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.54.0.tgz#cddb3ee4f9c64530dff640236661d42cb6a314f5"
integrity sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==
-mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
+mime-types@^2.1.27, mime-types@^2.1.35, mime-types@~2.1.17, mime-types@~2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@@ -6418,11 +6470,6 @@ ms@2.0.0:
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
-ms@2.1.2:
- version "2.1.2"
- resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
- integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
-
ms@2.1.3, ms@^2.1.1, ms@^2.1.3:
version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
@@ -6468,16 +6515,16 @@ negotiator@0.6.3:
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd"
integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==
+negotiator@~0.6.4:
+ version "0.6.4"
+ resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.4.tgz#777948e2452651c570b712dd01c23e262713fff7"
+ integrity sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==
+
neo-async@^2.6.2:
version "2.6.2"
resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f"
integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==
-node-forge@^1:
- version "1.4.0"
- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.4.0.tgz#1c7b7d8bdc2d078739f58287d589d903a11b2fc2"
- integrity sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==
-
node-int64@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
@@ -6628,10 +6675,10 @@ on-finished@^2.4.1, on-finished@~2.4.1:
dependencies:
ee-first "1.1.1"
-on-headers@~1.0.2:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
- integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+on-headers@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.1.0.tgz#59da4f91c45f5f989c6e4bcedc5a3b0aed70ff65"
+ integrity sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==
once@^1.3.0, once@^1.3.1, once@^1.4.0:
version "1.4.0"
@@ -6886,6 +6933,18 @@ pkg-dir@^7.0.0:
dependencies:
find-up "^6.3.0"
+pkijs@^3.3.3:
+ version "3.4.0"
+ resolved "https://registry.yarnpkg.com/pkijs/-/pkijs-3.4.0.tgz#d9164def30ff6d97be2d88966d5e36192499ca9c"
+ integrity sha512-emEcLuomt2j03vxD54giVB4SxTjnsqkU692xZOZXHDVoYyypEm+b3jpiTcc+Cf+myooc+/Ly0z01jqeNHVgJGw==
+ dependencies:
+ "@noble/hashes" "1.4.0"
+ asn1js "^3.0.6"
+ bytestreamjs "^2.0.1"
+ pvtsutils "^1.3.6"
+ pvutils "^1.1.3"
+ tslib "^2.8.1"
+
please-upgrade-node@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz#aeddd3f994c933e4ad98b99d9a556efa0e2fe942"
@@ -7032,10 +7091,22 @@ pure-rand@^6.0.0:
resolved "https://registry.yarnpkg.com/pure-rand/-/pure-rand-6.0.4.tgz#50b737f6a925468679bff00ad20eade53f37d5c7"
integrity sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==
+pvtsutils@^1.3.6:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/pvtsutils/-/pvtsutils-1.3.6.tgz#ec46e34db7422b9e4fdc5490578c1883657d6001"
+ integrity sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==
+ dependencies:
+ tslib "^2.8.1"
+
+pvutils@^1.1.3, pvutils@^1.1.5:
+ version "1.1.5"
+ resolved "https://registry.yarnpkg.com/pvutils/-/pvutils-1.1.5.tgz#84b0dea4a5d670249aa9800511804ee0b7c2809c"
+ integrity sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==
+
qs@~6.15.1:
- version "6.15.1"
- resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.1.tgz#bdb55aed06bfac257a90c44a446a73fba5575c8f"
- integrity sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==
+ version "6.15.2"
+ resolved "https://registry.yarnpkg.com/qs/-/qs-6.15.2.tgz#fd55426d710403ddccc45e0f9eab16db7727ece9"
+ integrity sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==
dependencies:
side-channel "^1.1.0"
@@ -7268,6 +7339,11 @@ redent@^3.0.0:
indent-string "^4.0.0"
strip-indent "^3.0.0"
+reflect-metadata@^0.2.2:
+ version "0.2.2"
+ resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b"
+ integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==
+
regenerate-unicode-properties@^10.1.0:
version "10.1.1"
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480"
@@ -7486,16 +7562,16 @@ safe-array-concat@^1.0.1:
has-symbols "^1.0.3"
isarray "^2.0.5"
-safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
safe-buffer@5.2.1, safe-buffer@>=5.1.0, safe-buffer@~5.2.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6"
integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
+safe-buffer@~5.1.0, safe-buffer@~5.1.1:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+ integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
safe-regex-test@^1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.2.tgz#3ba32bdb3ea35f940ee87e5087c60ee786c3f6c5"
@@ -7564,13 +7640,13 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
-selfsigned@^2.4.1:
- version "2.4.1"
- resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0"
- integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==
+selfsigned@^5.5.0:
+ version "5.5.0"
+ resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-5.5.0.tgz#4c9ab7c7c9f35f18fb6a9882c253eb0e6bd6557b"
+ integrity sha512-ftnu3TW4+3eBfLRFnDEkzGxSF/10BJBkaLJuBHZX0kiPS7bRdlpZGu6YGt4KngMkdTwJE6MbjavFpqHvqVt+Ew==
dependencies:
- "@types/node-forge" "^1.3.0"
- node-forge "^1"
+ "@peculiar/x509" "^1.14.2"
+ pkijs "^3.3.3"
semver-compare@^1.0.0:
version "1.0.0"
@@ -7700,10 +7776,10 @@ shebang-regex@^3.0.0:
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
-shell-quote@^1.8.3:
- version "1.8.3"
- resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.3.tgz#55e40ef33cf5c689902353a3d8cd1a6725f08b4b"
- integrity sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==
+shell-quote@^1.8.4:
+ version "1.8.4"
+ resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.4.tgz#2edd9a4dcefc96649e2e2cb12f637b1f1d92a190"
+ integrity sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==
side-channel-list@^1.0.0:
version "1.0.0"
@@ -8164,11 +8240,6 @@ tmpl@1.0.5:
resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
-to-fast-properties@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
- integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
-
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@@ -8225,12 +8296,12 @@ trough@^2.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f"
integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==
-tslib@^1.9.0:
+tslib@^1.9.0, tslib@^1.9.3:
version "1.14.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
-tslib@^2.0.0:
+tslib@^2.0.0, tslib@^2.8.1:
version "2.8.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f"
integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==
@@ -8240,6 +8311,13 @@ tslib@^2.1.0:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae"
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
+tsyringe@^4.10.0:
+ version "4.10.0"
+ resolved "https://registry.yarnpkg.com/tsyringe/-/tsyringe-4.10.0.tgz#d0c95815d584464214060285eaaadd94aa03299c"
+ integrity sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==
+ dependencies:
+ tslib "^1.9.3"
+
type-detect@4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
@@ -8593,14 +8671,14 @@ webpack-dev-middleware@^7.4.2:
range-parser "^1.2.1"
schema-utils "^4.0.0"
-webpack-dev-server@^5.2.1:
- version "5.2.1"
- resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.1.tgz#049072d6e19cbda8cf600b9e364e6662d61218ba"
- integrity sha512-ml/0HIj9NLpVKOMq+SuBPLHcmbG+TGIjXRHsYfZwocUBIqEvws8NnS/V9AFQ5FKP+tgn5adwVwRrTEpGL33QFQ==
+webpack-dev-server@^5.2.5:
+ version "5.2.5"
+ resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.2.5.tgz#648fceaac6a5736b0935e5c1e55d6aa1d0626119"
+ integrity sha512-4wZtCquSuv9CKX8oybo+mqxtxZqWz47uM1Ch94lxowBztOhWCbhqvRbfC/mODOwxgV2brY+JGZpHq58/SuVFYg==
dependencies:
"@types/bonjour" "^3.5.13"
"@types/connect-history-api-fallback" "^1.5.4"
- "@types/express" "^4.17.21"
+ "@types/express" "^4.17.25"
"@types/express-serve-static-core" "^4.17.21"
"@types/serve-index" "^1.9.4"
"@types/serve-static" "^1.15.5"
@@ -8610,17 +8688,17 @@ webpack-dev-server@^5.2.1:
bonjour-service "^1.2.1"
chokidar "^3.6.0"
colorette "^2.0.10"
- compression "^1.7.4"
+ compression "^1.8.1"
connect-history-api-fallback "^2.0.0"
- express "^4.21.2"
+ express "^4.22.1"
graceful-fs "^4.2.6"
- http-proxy-middleware "^2.0.7"
+ http-proxy-middleware "^2.0.9"
ipaddr.js "^2.1.0"
launch-editor "^2.6.1"
open "^10.0.3"
p-retry "^6.2.0"
schema-utils "^4.2.0"
- selfsigned "^2.4.1"
+ selfsigned "^5.5.0"
serve-index "^1.9.1"
sockjs "^0.3.24"
spdy "^4.0.2"
@@ -8703,9 +8781,9 @@ webpack@^5.104.1:
webpack-sources "^3.3.3"
websocket-driver@>=0.5.1, websocket-driver@^0.7.4:
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.4.tgz#89ad5295bbf64b480abcba31e4953aca706f5760"
- integrity sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==
+ version "0.7.5"
+ resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.5.tgz#569d22764ab21f2de20af0e74b411e8ae5a0fa46"
+ integrity sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==
dependencies:
http-parser-js ">=0.5.1"
safe-buffer ">=5.1.0"