Merge commit '4895fbc9fed2c7df28729e3b38929fc26f767ae9' into block-notes

This commit is contained in:
Oliver Walters
2026-07-03 10:51:02 +00:00
67 changed files with 1635 additions and 638 deletions
@@ -1,16 +1,21 @@
"""InvenTree API version information."""
# InvenTree API version
INVENTREE_API_VERSION = 514
INVENTREE_API_VERSION = 515
"""Increment this API version number whenever there is a significant change to the API that any clients need to know about."""
INVENTREE_API_TEXT = """
v514 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/11971
v516 -> 2026-07-03 : https://github.com/inventree/InvenTree/pull/11971
- Removes direct "notes" field from any models which previously supported markdown notes
- Adds a generic "Note" model which can be attached to any model type via a generic foreign key relationship
- Allow multiple notes to be attached to a single object, and for notes to be created / edited / deleted via the API
v514 -> 2026-07-02 : https://github.com/inventree/InvenTree/pull/12294
- Adds "duplicate" field to the BuildOrder, Company, ManufacturerPart, SupplierPart and SalesOrderShipment API endpoints
- Order duplication options: renames "order_id" field to "original", which now performs primary-key validation
- Part duplication options: renames "part" field to "original"
v513 -> 2026-06-25 : https://github.com/inventree/InvenTree/pull/12250
- Adds "active" field to the ProjectCode model and API endpoints
+102 -1
View File
@@ -599,7 +599,7 @@ class InvenTreeModelSerializer(serializers.ModelSerializer):
Default implementation returns an empty list
"""
return []
return getattr(self, 'SKIP_CREATE_FIELDS', [])
def save(self, **kwargs):
"""Catch any django ValidationError thrown at the moment `save` is called, and re-throw as a DRF ValidationError."""
@@ -900,3 +900,104 @@ class ContentTypeField(serializers.ChoiceField):
)
return content_type
class DuplicateOptionsSerializer(serializers.Serializer):
"""Generic serializer for specifying copy options when duplicating a model instance.
Builds its fields dynamically at instantiation time so the same class can be
reused for any model without subclassing.
"""
# Special 'shortcut' fields which are used for multiple models
DEFAULT_FIELDS = [
(
'copy_parameters',
_('Copy Parameters'),
_('Copy parameters from the original item'),
False,
),
(
'copy_lines',
_('Copy Lines'),
_('Copy line items from the original order'),
False,
),
(
'copy_extra_lines',
_('Copy Extra Lines'),
_('Copy extra line items from the original order'),
False,
),
]
def __init__(
self,
queryset: QuerySet,
*args,
copy_fields: Optional[list[dict]] = None,
**kwargs,
):
"""Initialise the serializer and dynamically attach fields.
Arguments:
queryset: Queryset used for the `original` PrimaryKeyRelatedField.
copy_fields: Optional list of dicts, each describing one boolean copy toggle.
Keys:
name: (str, required)
label: (str, optional)
help_text: (str, optional)
default: (bool, optional, default True)
"""
# Enforce certain properties onto this serializer
kwargs['label'] = kwargs.get('label', _('Duplication Options'))
kwargs['help_text'] = kwargs.get(
'help_text', _('Specify options for duplicating this item')
)
kwargs['required'] = False
kwargs['write_only'] = True
copy_fields = copy_fields or []
copy_field_names = [spec['name'] for spec in copy_fields]
# Apply "default" fields
for name, label, help_text, default_value in self.DEFAULT_FIELDS:
popped_value = kwargs.pop(name, default_value)
if name in copy_field_names:
# Manually supplied field, continue
continue
if popped_value:
copy_fields.append({
'name': name,
'label': label,
'help_text': help_text,
'default': True,
})
super().__init__(*args, **kwargs)
# Re-class the instance with a model-specific subclass,
# so that each model generates a unique schema component name
if self.__class__ is DuplicateOptionsSerializer:
self.__class__ = type(
f'{queryset.model.__name__}DuplicateOptionsSerializer',
(DuplicateOptionsSerializer,),
{},
)
self.fields['original'] = serializers.PrimaryKeyRelatedField(
queryset=queryset,
required=True,
label=_('Original'),
help_text=_('Select instance to duplicate'),
)
for spec in copy_fields or []:
self.fields[spec['name']] = serializers.BooleanField(
required=False,
default=spec.get('default', True),
label=spec.get('label', spec['name']),
help_text=spec.get('help_text', ''),
)
@@ -34,6 +34,7 @@ from generic.states.fields import InvenTreeCustomStatusSerializerMixin
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeDecimalField,
InvenTreeModelSerializer,
@@ -65,6 +66,8 @@ class BuildSerializer(
):
"""Serializes a Build object."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Serializer metaclass."""
@@ -78,6 +81,7 @@ class BuildSerializer(
'completed',
'completion_date',
'destination',
'duplicate',
'external',
'parent',
'part',
@@ -187,12 +191,29 @@ class BuildSerializer(
return queryset
duplicate = DuplicateOptionsSerializer(Build.objects.all(), copy_parameters=True)
def __init__(self, *args, **kwargs):
"""Determine if extra serializer fields are required."""
kwargs.pop('create', False)
super().__init__(*args, **kwargs)
@transaction.atomic
def create(self, validated_data):
"""Create a new Build instance, optionally copying data from an existing build."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
def validate_reference(self, reference):
"""Custom validation for the Build reference field."""
# Ensure the reference matches the required pattern
@@ -1,5 +1,6 @@
"""JSON serializers for Company app."""
from django.db import transaction
from django.db.models import Prefetch
from django.utils.translation import gettext_lazy as _
@@ -14,6 +15,7 @@ from importer.registry import register_importer
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.ready import isGeneratingSchema
from InvenTree.serializers import (
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
@@ -116,6 +118,8 @@ class CompanySerializer(
import_exclude_fields = ['image']
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@@ -130,6 +134,7 @@ class CompanySerializer(
'email',
'currency',
'contact',
'duplicate',
'link',
'image',
'active',
@@ -188,6 +193,23 @@ class CompanySerializer(
parameters = common.filters.enable_parameters_filter()
duplicate = DuplicateOptionsSerializer(Company.objects.all(), copy_parameters=True)
@transaction.atomic
def create(self, validated_data):
"""Create a new Company instance, optionally copying data from an existing company."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
@register_importer()
class ContactSerializer(DataImportExportSerializerMixin, InvenTreeModelSerializer):
@@ -213,6 +235,8 @@ class ManufacturerPartSerializer(
):
"""Serializer for ManufacturerPart object."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@@ -225,6 +249,7 @@ class ManufacturerPartSerializer(
'manufacturer',
'manufacturer_detail',
'description',
'duplicate',
'MPN',
'link',
'barcode_hash',
@@ -236,6 +261,25 @@ class ManufacturerPartSerializer(
parameters = common.filters.enable_parameters_filter()
duplicate = DuplicateOptionsSerializer(
ManufacturerPart.objects.all(), copy_parameters=True
)
@transaction.atomic
def create(self, validated_data):
"""Create a new ManufacturerPart instance, optionally copying data from an existing instance."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
part_detail = OptionalField(
serializer_class=part_serializers.PartBriefSerializer,
serializer_kwargs={
@@ -317,6 +361,8 @@ class SupplierPartSerializer(
export_exclude_fields = ['tags']
SKIP_CREATE_FIELDS = ['duplicate']
export_child_fields = [
'part_detail.name',
'part_detail.description',
@@ -333,6 +379,7 @@ class SupplierPartSerializer(
'available',
'availability_updated',
'description',
'duplicate',
'in_stock',
'on_order',
'link',
@@ -487,6 +534,10 @@ class SupplierPartSerializer(
# Date fields
updated = serializers.DateTimeField(allow_null=True, read_only=True)
duplicate = DuplicateOptionsSerializer(
SupplierPart.objects.all(), copy_parameters=True
)
@staticmethod
def annotate_queryset(queryset):
"""Annotate the SupplierPart queryset with extra fields.
@@ -515,8 +566,11 @@ class SupplierPartSerializer(
return response
@transaction.atomic
def create(self, validated_data):
"""Extract manufacturer data and process ManufacturerPart."""
duplicate = validated_data.pop('duplicate', None)
# Extract 'available' quantity from the serializer
available = validated_data.pop('available', None)
@@ -534,6 +588,12 @@ class SupplierPartSerializer(
kwargs = {'manufacturer': manufacturer, 'MPN': MPN}
supplier_part.save(**kwargs)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
supplier_part.copy_parameters_from(original)
return supplier_part
@@ -16,6 +16,7 @@ from taggit.serializers import TagListSerializerField
import data_exporter.serializers
import data_exporter.tasks
import InvenTree.exceptions
import InvenTree.serializers
from common.models import DataOutput
from InvenTree.helpers import str2bool
from InvenTree.tasks import offload_task
@@ -64,6 +65,14 @@ class DataExportSerializerMixin:
# Exclude fields which are not required for data export
for field in self.get_export_exclude_fields(**kwargs):
self.fields.pop(field, None)
# Duplication options are never used for data export
for field in [
name
for name, field in self.fields.items()
if isinstance(field, InvenTree.serializers.DuplicateOptionsSerializer)
]:
self.fields.pop(field, None)
else:
# Exclude fields which are only used for data export
for field in self.get_export_only_fields(**kwargs):
+10
View File
@@ -3,6 +3,8 @@
from rest_framework import fields, serializers
from taggit.serializers import TagListSerializerField
import InvenTree.serializers
class DataImportSerializerMixin:
"""Mixin class for adding data import functionality to a DRF serializer."""
@@ -41,6 +43,14 @@ class DataImportSerializerMixin:
for field in self.get_import_exclude_fields(**kwargs):
self.fields.pop(field, None)
# Duplication options are never used for data import
for field in [
name
for name, field in self.fields.items()
if isinstance(field, InvenTree.serializers.DuplicateOptionsSerializer)
]:
self.fields.pop(field, None)
else:
# Exclude fields which are only used for data import
for field in self.get_import_only_fields(**kwargs):
+61 -59
View File
@@ -32,6 +32,7 @@ from InvenTree.helpers import extract_serial_numbers, hash_barcode, normalize, s
from InvenTree.mixins import DataImportExportSerializerMixin
from InvenTree.serializers import (
CustomStatusSerializerMixin,
DuplicateOptionsSerializer,
FilterableSerializerMixin,
InvenTreeCurrencySerializer,
InvenTreeDecimalField,
@@ -66,40 +67,6 @@ class TotalPriceMixin(serializers.Serializer):
)
class DuplicateOrderSerializer(serializers.Serializer):
"""Serializer for specifying options when duplicating an order."""
class Meta:
"""Metaclass options."""
fields = ['order_id', 'copy_lines', 'copy_extra_lines', 'copy_parameters']
order_id = serializers.IntegerField(
required=True, label=_('Order ID'), help_text=_('ID of the order to duplicate')
)
copy_lines = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Lines'),
help_text=_('Copy line items from the original order'),
)
copy_extra_lines = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Extra Lines'),
help_text=_('Copy extra line items from the original order'),
)
copy_parameters = serializers.BooleanField(
required=False,
default=True,
label=_('Copy Parameters'),
help_text=_('Copy order parameters from the original order'),
)
class AbstractOrderSerializer(
CustomStatusSerializerMixin,
DataImportExportSerializerMixin,
@@ -109,9 +76,8 @@ class AbstractOrderSerializer(
):
"""Abstract serializer class which provides fields common to all order types."""
export_exclude_fields = ['duplicate']
import_exclude_fields = ['duplicate']
export_exclude_fields = ['notes']
import_exclude_fields = ['notes']
# Number of line items in this order
line_items = serializers.IntegerField(
@@ -194,12 +160,8 @@ class AbstractOrderSerializer(
created_by = UserSerializer(read_only=True)
duplicate = DuplicateOrderSerializer(
label=_('Duplicate Order'),
help_text=_('Specify options for duplicating this order'),
required=False,
write_only=True,
)
# Note: The 'duplicate' field must be defined by each concrete serializer class,
# as it requires a queryset specific to the order model type
def validate_reference(self, reference):
"""Custom validation for the reference field."""
@@ -291,30 +253,21 @@ class AbstractOrderSerializer(
instance = super().create(validated_data)
if duplicate:
order_id = duplicate.get('order_id', None)
copy_lines = duplicate.get('copy_lines', True)
copy_extra_lines = duplicate.get('copy_extra_lines', True)
copy_parameters = duplicate.get('copy_parameters', True)
original = duplicate['original']
try:
copy_from = instance.__class__.objects.get(pk=order_id)
except Exception:
# If the order ID is invalid, raise a validation error
raise ValidationError(_('Invalid order ID'))
if copy_lines:
for line in copy_from.lines.all():
if duplicate.get('copy_lines', False):
for line in original.lines.all():
instance.clean_line_item(line)
line.save()
if copy_extra_lines:
for line in copy_from.extra_lines.all():
if duplicate.get('copy_extra_lines', False):
for line in original.extra_lines.all():
line.pk = None
line.order = instance
line.save()
if copy_parameters:
instance.copy_parameters_from(copy_from)
if duplicate.get('copy_parameters', False):
instance.copy_parameters_from(original)
return instance
@@ -449,6 +402,13 @@ class PurchaseOrderSerializer(
return [*fields, 'duplicate']
duplicate = DuplicateOptionsSerializer(
order.models.PurchaseOrder.objects.all(),
copy_lines=True,
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.
@@ -1130,6 +1090,13 @@ class SalesOrderSerializer(
return [*fields, 'duplicate']
duplicate = DuplicateOptionsSerializer(
order.models.SalesOrder.objects.all(),
copy_lines=True,
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.
@@ -1406,6 +1373,8 @@ class SalesOrderShipmentSerializer(
):
"""Serializer for the SalesOrderShipment class."""
SKIP_CREATE_FIELDS = ['duplicate']
class Meta:
"""Metaclass options."""
@@ -1418,6 +1387,7 @@ class SalesOrderShipmentSerializer(
'shipment_address',
'delivery_date',
'checked_by',
'duplicate',
'reference',
'tracking_number',
'invoice_number',
@@ -1501,6 +1471,25 @@ class SalesOrderShipmentSerializer(
tags = common.filters.enable_tags_filter()
duplicate = DuplicateOptionsSerializer(
order.models.SalesOrderShipment.objects.all(), copy_parameters=True
)
@transaction.atomic
def create(self, validated_data):
"""Create a new SalesOrderShipment instance, optionally copying data from an existing shipment."""
duplicate = validated_data.pop('duplicate', None)
instance = super().create(validated_data)
if duplicate:
original = duplicate['original']
if duplicate.get('copy_parameters', True):
instance.copy_parameters_from(original)
return instance
class SalesOrderAllocationSerializer(
FilterableSerializerMixin, InvenTreeModelSerializer
@@ -2191,6 +2180,14 @@ class ReturnOrderSerializer(
return [*fields, 'duplicate']
# Note: line items cannot be duplicated from a ReturnOrder,
# as they are linked to specific stock items
duplicate = DuplicateOptionsSerializer(
order.models.ReturnOrder.objects.all(),
copy_extra_lines=True,
copy_parameters=True,
)
@staticmethod
def annotate_queryset(queryset):
"""Custom annotation for the serializer queryset."""
@@ -2477,6 +2474,11 @@ class TransferOrderSerializer(
return [*fields, 'duplicate']
# Note: TransferOrder does not have "extra" line items
duplicate = DuplicateOptionsSerializer(
order.models.TransferOrder.objects.all(), copy_lines=True, copy_parameters=True
)
@staticmethod
def annotate_queryset(queryset):
"""Add extra information to the queryset.
+5 -5
View File
@@ -573,7 +573,7 @@ class PurchaseOrderTest(OrderTest):
# Duplicate with non-existent PK to provoke error
data['duplicate'] = {
'order_id': 10000001,
'original': 10000001,
'copy_lines': True,
'copy_extra_lines': False,
}
@@ -584,7 +584,7 @@ class PurchaseOrderTest(OrderTest):
response = self.post(reverse('api-po-list'), data, expected_code=400)
data['duplicate'] = {
'order_id': 1,
'original': 1,
'copy_lines': True,
'copy_extra_lines': False,
}
@@ -605,7 +605,7 @@ class PurchaseOrderTest(OrderTest):
data['reference'] = 'PO-9998'
data['duplicate'] = {
'order_id': 1,
'original': 1,
'copy_lines': False,
'copy_extra_lines': True,
}
@@ -1792,7 +1792,7 @@ class SalesOrderTest(OrderTest):
{
'reference': 'SO-12345',
'customer': so.customer.pk,
'duplicate': {'order_id': so.pk, 'copy_parameters': False},
'duplicate': {'original': so.pk, 'copy_parameters': False},
},
)
@@ -1809,7 +1809,7 @@ class SalesOrderTest(OrderTest):
{
'reference': 'SO-12346',
'customer': so.customer.pk,
'duplicate': {'order_id': so.pk},
'duplicate': {'original': so.pk},
},
)
+31 -66
View File
@@ -407,67 +407,6 @@ class PartBriefSerializer(
)
class DuplicatePartSerializer(serializers.Serializer):
"""Serializer for specifying options when duplicating a Part.
The fields in this serializer control how the Part is duplicated.
"""
class Meta:
"""Metaclass options."""
fields = [
'part',
'copy_image',
'copy_bom',
'copy_parameters',
'copy_notes',
'copy_tests',
]
part = serializers.PrimaryKeyRelatedField(
queryset=Part.objects.all(),
label=_('Original Part'),
help_text=_('Select original part to duplicate'),
required=True,
)
copy_image = serializers.BooleanField(
label=_('Copy Image'),
help_text=_('Copy image from original part'),
required=False,
default=False,
)
copy_bom = serializers.BooleanField(
label=_('Copy BOM'),
help_text=_('Copy bill of materials from original part'),
required=False,
default=False,
)
copy_parameters = serializers.BooleanField(
label=_('Copy Parameters'),
help_text=_('Copy parameter data from original part'),
required=False,
default=False,
)
copy_notes = serializers.BooleanField(
label=_('Copy Notes'),
help_text=_('Copy notes from original part'),
required=False,
default=True,
)
copy_tests = serializers.BooleanField(
label=_('Copy Tests'),
help_text=_('Copy test templates from original part'),
required=False,
default=False,
)
class InitialStockSerializer(serializers.Serializer):
"""Serializer for creating initial stock quantity."""
@@ -600,7 +539,7 @@ class PartSerializer(
Used when displaying all details of a single component.
"""
import_exclude_fields = ['creation_date', 'creation_user', 'duplicate']
import_exclude_fields = ['creation_date', 'creation_user']
class Meta:
"""Metaclass defining serializer fields."""
@@ -1005,11 +944,37 @@ class PartSerializer(
)
# Extra fields used only for creation of a new Part instance
duplicate = DuplicatePartSerializer(
duplicate = InvenTree.serializers.DuplicateOptionsSerializer(
Part.objects.all(),
label=_('Duplicate Part'),
help_text=_('Copy initial data from another Part'),
write_only=True,
required=False,
copy_parameters=True,
copy_fields=[
{
'name': 'copy_image',
'label': _('Copy Image'),
'help_text': _('Copy image from original part'),
'default': False,
},
{
'name': 'copy_bom',
'label': _('Copy BOM'),
'help_text': _('Copy bill of materials from original part'),
'default': False,
},
{
'name': 'copy_notes',
'label': _('Copy Notes'),
'help_text': _('Copy notes from original part'),
'default': True,
},
{
'name': 'copy_tests',
'label': _('Copy Tests'),
'help_text': _('Copy test templates from original part'),
'default': False,
},
],
)
initial_stock = InitialStockSerializer(
@@ -1075,7 +1040,7 @@ class PartSerializer(
# Copy data from original Part
if duplicate:
original = duplicate['part']
original = duplicate['original']
if duplicate.get('copy_bom', False):
instance.copy_bom_from(original)
+1 -1
View File
@@ -1649,7 +1649,7 @@ class PartCreationTests(PartAPITestBase):
'testable': do_copy,
'assembly': do_copy,
'duplicate': {
'part': 100,
'original': 100,
'copy_bom': do_copy,
'copy_notes': do_copy,
'copy_image': do_copy,
+1 -1
View File
@@ -30,7 +30,7 @@ class TemplateTagTest(InvenTreeTestCase):
shipped_js = resp.split('<script type="module" src="')[1:]
self.assertGreater(len(shipped_js), 0)
self.assertGreater(len(shipped_js), 3)
self.assertGreater(len(shipped_js), 2)
manifest_file = Path(__file__).parent.joinpath('static/web/.vite/manifest.json')
# Try with removed manifest file
+1
View File
@@ -57,4 +57,5 @@ export interface SettingsStateProps {
pathParams?: PathParams;
getSetting: (key: string, default_value?: string) => string; // Return a raw setting value
isSet: (key: string, default_value?: boolean) => boolean; // Check a "boolean" setting
getSettingLength: () => number;
}
+5 -5
View File
@@ -117,10 +117,10 @@
"zustand": "^5.0.14"
},
"devDependencies": {
"@babel/core": "^7.29.7",
"@babel/preset-react": "^7.29.7",
"@babel/preset-typescript": "^7.29.7",
"@babel/runtime": "^7.29.7",
"@babel/core": "^8.0.1",
"@babel/preset-react": "^8.0.1",
"@babel/preset-typescript": "^8.0.1",
"@babel/runtime": "^8.0.0",
"@codecov/vite-plugin": "^2.0.1",
"@flakiness/playwright": "^1.13.0",
"@lingui/babel-plugin-lingui-macro": "^5.9.5",
@@ -135,7 +135,7 @@
"@types/react-router-dom": "^5.3.3",
"@types/react-window": "^1.8.8",
"@vanilla-extract/vite-plugin": "^5.2.2",
"@vitejs/plugin-react": "^5.2.0",
"@vitejs/plugin-react": "^5",
"babel-plugin-macros": "^3.1.0",
"nyc": "^18.0.0",
"otpauth": "^9.5.1",
@@ -33,7 +33,7 @@ export const PdfPreviewComponent: PreviewAreaComponent = forwardRef(
}
let preview = await api.post(
printingUrl,
printingUrl!,
{
items: [previewItem],
template: template.pk
@@ -1,6 +1,11 @@
import { Boundary } from '@lib/components/Boundary';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { t } from '@lingui/core/macro';
import {
Alert,
Button,
CloseButton,
Group,
List,
@@ -24,11 +29,6 @@ import {
import Split from '@uiw/react-split';
import type React from 'react';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Boundary } from '@lib/components/Boundary';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { api } from '../../../App';
import type { TemplateI } from '../../../tables/settings/TemplateTable';
import { SplitButton } from '../../buttons/SplitButton';
@@ -78,26 +78,38 @@ export type PreviewArea = {
export type TemplateEditorProps = {
templateUrl: string;
printingUrl: string;
printingUrl?: string;
editors: Editor[];
previewAreas: PreviewArea[];
template: TemplateI;
// Name of the file field on the template model (e.g. 'template' or 'snippet')
fileField?: string;
};
export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
const { templateUrl, editors, previewAreas, template } = props;
const {
templateUrl,
editors,
previewAreas,
template,
fileField = 'template'
} = props;
const editorRef = useRef<EditorRef | null>(null);
const previewRef = useRef<PreviewAreaRef | null>(null);
// If no preview areas are provided, the template is saved directly
const hasPreview = previewAreas.length > 0;
const [hasSaveConfirmed, setHasSaveConfirmed] = useState(false);
const [previewItem, setPreviewItem] = useState<string>('');
const [renderingErrors, setRenderingErrors] = useState<string[] | null>(null);
const [isPreviewLoading, setIsPreviewLoading] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [editorValue, setEditorValue] = useState<null | string>(editors[0].key);
const [previewValue, setPreviewValue] = useState<null | string>(
previewAreas[0].key
previewAreas[0]?.key ?? null
);
const codeRef = useRef<string | undefined>(undefined);
@@ -131,12 +143,12 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
if (!templateUrl) return;
api.get(templateUrl).then((response: any) => {
if (response.data?.template) {
if (response.data?.[fileField]) {
// Fetch the raw template file from the server
// Request that it is provided without any caching,
// to ensure that we always get the latest version
api
.get(response.data.template, {
.get(response.data[fileField], {
headers: {
'Cache-Control': 'no-cache',
Pragma: 'no-cache',
@@ -149,7 +161,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
})
.catch(() => {
console.error(
`ERR: Could not load template from ${response.data.template}`
`ERR: Could not load template from ${response.data[fileField]}`
);
codeRef.current = undefined;
hideNotification('template-load-error');
@@ -244,6 +256,45 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
[previewItem]
);
// Save the template file directly to the server, without any preview
const saveTemplate = useCallback(async () => {
const code = await getCodeFromEditor();
if (code === undefined) return;
const filename =
(template as any)[fileField]?.split('/').pop() ?? 'template.html';
const formData = new FormData();
formData.append(fileField, new File([code], filename));
setIsSaving(true);
api
.patch(templateUrl, formData)
.then(() => {
hideNotification('template-save');
showNotification({
id: 'template-save',
title: t`Saved`,
message: t`Template file has been updated`,
color: 'green'
});
})
.catch(() => {
hideNotification('template-save');
showNotification({
id: 'template-save',
title: t`Error`,
message: t`Could not save the template to the server.`,
color: 'red'
});
})
.finally(() => {
setIsSaving(false);
});
}, [getCodeFromEditor, template, templateUrl, fileField]);
const previewApiUrl = useMemo(
() =>
ModelInformationDict[template.model_type ?? ModelType.stockitem]
@@ -261,18 +312,20 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
}, [template]);
useEffect(() => {
if (!hasPreview) return;
api
.get(apiUrl(previewApiUrl), { params: { limit: 1, ...templateFilters } })
.then((res) => {
if (res.data.results.length === 0) return;
setPreviewItem(res.data.results[0].pk);
});
}, [previewApiUrl, templateFilters]);
}, [hasPreview, previewApiUrl, templateFilters]);
return (
<Boundary label='TemplateEditor'>
<Stack style={{ height: '100%', flex: '1' }}>
<Split visible style={{ flex: 1 }}>
<Split visible={hasPreview} style={{ flex: 1 }}>
<Tabs
value={editorValue}
onChange={async (v) => {
@@ -282,7 +335,7 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
keepMounted={false}
style={{
minWidth: '300px',
width: '50%',
width: hasPreview ? '50%' : '100%',
display: 'flex',
flexDirection: 'column'
}}
@@ -301,29 +354,39 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
})}
<Group justify='right' style={{ flex: '1' }} wrap='nowrap'>
<SplitButton
loading={isPreviewLoading}
defaultSelected='preview_save'
name='preview-options'
options={[
{
key: 'preview',
name: t`Reload preview`,
tooltip: t`Use the currently stored template from the server`,
icon: IconRefresh,
onClick: () => updatePreview(true, false),
disabled: !previewItem || isPreviewLoading
},
{
key: 'preview_save',
name: t`Save & Reload Preview`,
tooltip: t`Save the current template and reload the preview`,
icon: IconDeviceFloppy,
onClick: () => updatePreview(hasSaveConfirmed),
disabled: !previewItem || isPreviewLoading
}
]}
/>
{hasPreview ? (
<SplitButton
loading={isPreviewLoading}
defaultSelected='preview_save'
name='preview-options'
options={[
{
key: 'preview',
name: t`Reload preview`,
tooltip: t`Use the currently stored template from the server`,
icon: IconRefresh,
onClick: () => updatePreview(true, false),
disabled: !previewItem || isPreviewLoading
},
{
key: 'preview_save',
name: t`Save & Reload Preview`,
tooltip: t`Save the current template and reload the preview`,
icon: IconDeviceFloppy,
onClick: () => updatePreview(hasSaveConfirmed),
disabled: !previewItem || isPreviewLoading
}
]}
/>
) : (
<Button
leftSection={<IconDeviceFloppy size={18} />}
loading={isSaving}
onClick={() => saveTemplate()}
>
{t`Save`}
</Button>
)}
</Group>
</Tabs.List>
@@ -342,100 +405,102 @@ export function TemplateEditor(props: Readonly<TemplateEditorProps>) {
))}
</Tabs>
<Tabs
value={previewValue}
onChange={setPreviewValue}
keepMounted={false}
style={{
minWidth: '200px',
width: '50%',
display: 'flex',
flexDirection: 'column'
}}
>
<Tabs.List>
{previewAreas.map((PreviewArea) => (
<Tabs.Tab
key={PreviewArea.key}
value={PreviewArea.key}
leftSection={PreviewArea.icon}
>
{PreviewArea.name}
</Tabs.Tab>
))}
</Tabs.List>
<div
{hasPreview && (
<Tabs
value={previewValue}
onChange={setPreviewValue}
keepMounted={false}
style={{
minWidth: '100%',
paddingBottom: '10px',
paddingTop: '10px'
minWidth: '200px',
width: '50%',
display: 'flex',
flexDirection: 'column'
}}
>
<StandaloneField
fieldDefinition={{
field_type: 'related field',
api_url: apiUrl(previewApiUrl),
description: '',
label: t`Select instance to preview`,
model: template.model_type,
value: previewItem,
filters: templateFilters,
onValueChange: (value) => setPreviewItem(value)
}}
/>
</div>
<Tabs.List>
{previewAreas.map((PreviewArea) => (
<Tabs.Tab
key={PreviewArea.key}
value={PreviewArea.key}
leftSection={PreviewArea.icon}
>
{PreviewArea.name}
</Tabs.Tab>
))}
</Tabs.List>
{previewAreas.map((PreviewArea) => (
<Tabs.Panel
key={PreviewArea.key}
value={PreviewArea.key}
<div
style={{
display: 'flex',
flex: previewValue === PreviewArea.key ? 1 : 0
minWidth: '100%',
paddingBottom: '10px',
paddingTop: '10px'
}}
>
<div
<StandaloneField
fieldDefinition={{
field_type: 'related field',
api_url: apiUrl(previewApiUrl),
description: '',
label: t`Select instance to preview`,
model: template.model_type,
value: previewItem,
filters: templateFilters,
onValueChange: (value) => setPreviewItem(value)
}}
/>
</div>
{previewAreas.map((PreviewArea) => (
<Tabs.Panel
key={PreviewArea.key}
value={PreviewArea.key}
style={{
height: '100%',
position: 'relative',
display: 'flex',
flex: '1'
flex: previewValue === PreviewArea.key ? 1 : 0
}}
>
{/* @ts-ignore-next-line */}
<PreviewArea.component ref={previewRef} />
<div
style={{
height: '100%',
position: 'relative',
display: 'flex',
flex: '1'
}}
>
{/* @ts-ignore-next-line */}
<PreviewArea.component ref={previewRef} />
{renderingErrors && (
<Overlay color='red' center blur={0.2}>
<CloseButton
onClick={() => setRenderingErrors(null)}
style={{
position: 'absolute',
top: '10px',
right: '10px',
color: '#fff'
}}
variant='filled'
/>
<Alert
color='red'
icon={<IconExclamationCircle />}
title={t`Error rendering template`}
mx='10px'
>
<List>
{renderingErrors.map((error, index) => (
<ListItem key={index}>{error}</ListItem>
))}
</List>
</Alert>
</Overlay>
)}
</div>
</Tabs.Panel>
))}
</Tabs>
{renderingErrors && (
<Overlay color='red' center blur={0.2}>
<CloseButton
onClick={() => setRenderingErrors(null)}
style={{
position: 'absolute',
top: '10px',
right: '10px',
color: '#fff'
}}
variant='filled'
/>
<Alert
color='red'
icon={<IconExclamationCircle />}
title={t`Error rendering template`}
mx='10px'
>
<List>
{renderingErrors.map((error, index) => (
<ListItem key={index}>{error}</ListItem>
))}
</List>
</Alert>
</Overlay>
)}
</div>
</Tabs.Panel>
))}
</Tabs>
)}
</Split>
</Stack>
</Boundary>
@@ -24,7 +24,7 @@ import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import { cancelEvent } from '@lib/functions/Events';
import { getDetailUrl, navigateToLink } from '@lib/index';
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
import type { ApiFormFieldType } from '@lib/types/Forms';
import { useApi } from '../../../contexts/ApiContext';
import {
@@ -65,8 +65,8 @@ const AboutContent = ({
function fillTable(lookup: AboutLookupRef[], data: any, alwaysLink = false) {
return lookup
.filter((entry: AboutLookupRef) => !!data[entry.ref])
.map((entry: AboutLookupRef, idx) => (
<Table.Tr key={idx}>
.map((entry: AboutLookupRef) => (
<Table.Tr key={entry.ref}>
<Table.Td>{entry.title}</Table.Td>
<Table.Td>
<Group justify='space-between' gap='xs'>
@@ -564,9 +564,9 @@ export function SearchDrawer({
multiple
defaultValue={searchQueries.map((q) => q.model)}
>
{queryResults.map((query, idx) => (
{queryResults.map((query) => (
<QueryResultGroup
key={idx}
key={query.model}
searchText={searchText}
query={query}
navigate={navigate}
@@ -31,9 +31,9 @@ export type { InstanceRenderInterface } from '@lib/types/Rendering';
import {
getBaseUrl,
getDetailUrl,
navigateToLink,
shortenString
} from '@lib/index';
navigateToLink
} from '@lib/functions/Navigation';
import { shortenString } from '@lib/functions/String';
import { IconLink } from '@tabler/icons-react';
import { useApi } from '../../contexts/ApiContext';
import { usePluginState } from '../../states/PluginState';
@@ -12,7 +12,7 @@ import type { NavigateFunction } from 'react-router-dom';
import { ModelInformationDict } from '@lib/enums/ModelInformation';
import type { ModelType } from '@lib/enums/ModelType';
import { getDetailUrl, navigateToLink } from '@lib/index';
import { getDetailUrl, navigateToLink } from '@lib/functions/Navigation';
import { IconLink } from '@tabler/icons-react';
/**
@@ -28,21 +28,34 @@ export function SettingList({
settingsState,
keys,
onChange,
onLoaded
onLoaded,
doGet
}: Readonly<{
heading?: string;
settingsState: SettingsStateProps;
keys?: string[];
onChange?: () => void;
onLoaded?: (settings: SettingsStateProps) => void;
doGet?: boolean;
}>) {
useEffect(() => {
if (settingsState.loaded) {
if (settingsState.loaded === true) {
// Call the onLoaded callback if provided
onLoaded?.(settingsState);
}
}, [settingsState.loaded, settingsState.settings]);
// get data if doGet is true to break memos leading to hidden group panels
useMemo(() => {
if (doGet && !settingsState.loaded) {
settingsState.fetchSettings().then((success) => {
if (success) {
onLoaded?.(settingsState);
}
});
}
}, [doGet, settingsState]);
const api = useApi();
const allKeys = useMemo(
@@ -226,9 +239,11 @@ export function GlobalSettingList({
export function PluginSettingList({
pluginKey,
doGet,
onLoaded
}: Readonly<{
pluginKey: string;
doGet?: boolean;
onLoaded?: (settings: SettingsStateProps) => void;
}>) {
const store = useMemo(
@@ -246,14 +261,22 @@ export function PluginSettingList({
pluginSettings.fetchSettings();
}, [pluginSettings.fetchSettings]);
return <SettingList settingsState={pluginSettings} onLoaded={onLoaded} />;
return (
<SettingList
settingsState={pluginSettings}
onLoaded={onLoaded}
doGet={doGet}
/>
);
}
export function PluginUserSettingList({
pluginKey,
doGet,
onLoaded
}: Readonly<{
pluginKey: string;
doGet?: boolean;
onLoaded?: (settings: SettingsStateProps) => void;
}>) {
const store = useMemo(
@@ -271,7 +294,13 @@ export function PluginUserSettingList({
pluginUserSettings.fetchSettings();
}, [pluginUserSettings.fetchSettings]);
return <SettingList settingsState={pluginUserSettings} onLoaded={onLoaded} />;
return (
<SettingList
settingsState={pluginUserSettings}
onLoaded={onLoaded}
doGet={doGet}
/>
);
}
export function MachineSettingList({
+22 -3
View File
@@ -36,16 +36,18 @@ import {
} from '../hooks/UseGenerator';
import { useGlobalSettingsState } from '../states/SettingsStates';
import { RenderPartColumn } from '../tables/ColumnRenderers';
import { ProjectCodeField, TagsField } from './CommonFields';
import { DuplicateField, ProjectCodeField, TagsField } from './CommonFields';
/**
* Field set for BuildOrder forms
*/
export function useBuildOrderFields({
create,
duplicateBuildId,
modalId
}: {
create: boolean;
duplicateBuildId?: number | null;
modalId: string;
}): ApiFormFieldSet {
const [destination, setDestination] = useState<number | null | undefined>(
@@ -133,7 +135,13 @@ export function useBuildOrderFields({
is_active: true
}
},
external: {}
external: {},
duplicate: DuplicateField({
originalId: duplicateBuildId,
extraFields: {
copy_parameters: {}
}
})
};
if (!globalSettings.isSet('PROJECT_CODES_ENABLED', true)) {
@@ -144,8 +152,19 @@ export function useBuildOrderFields({
delete fields.external;
}
if (!duplicateBuildId) {
delete fields.duplicate;
}
return fields;
}, [create, destination, batchCode, batchGenerator.result, globalSettings]);
}, [
create,
destination,
batchCode,
batchGenerator.result,
globalSettings,
duplicateBuildId
]);
}
export function useBuildOrderOutputFields({
+20
View File
@@ -2,6 +2,26 @@ import type { ApiFormFieldType } from '@lib/types/Forms';
import { t } from '@lingui/core/macro';
import { IconList } from '@tabler/icons-react';
// Generic field for implementing a "duplication options" form field
export function DuplicateField({
originalId,
extraFields
}: Readonly<{
originalId?: number | null;
extraFields?: Record<string, any>;
}>): ApiFormFieldType {
return {
children: {
original: {
value: originalId,
hidden: true
},
...extraFields
}
};
}
// Generic field for rendering a list of tags within a form
export function TagsField({
label,
description,
+58 -10
View File
@@ -13,7 +13,7 @@ import {
IconPhone
} from '@tabler/icons-react';
import { useMemo, useState } from 'react';
import { TagsField } from './CommonFields';
import { DuplicateField, TagsField } from './CommonFields';
/**
* Field set for SupplierPart instance
@@ -21,11 +21,13 @@ import { TagsField } from './CommonFields';
export function useSupplierPartFields({
manufacturerId,
manufacturerPartId,
partId
partId,
duplicateSupplierPartId
}: {
manufacturerId?: number;
manufacturerPartId?: number;
partId?: number;
duplicateSupplierPartId?: number | null;
}) {
const [part, setPart] = useState<any>({});
@@ -95,14 +97,34 @@ export function useSupplierPartFields({
icon: <IconPackage />
},
primary: {},
active: {}
active: {},
duplicate: DuplicateField({
originalId: duplicateSupplierPartId,
extraFields: {
copy_parameters: {}
}
})
};
if (!duplicateSupplierPartId) {
delete fields.duplicate;
}
return fields;
}, [manufacturerId, manufacturerPartId, partId, part]);
}, [
manufacturerId,
manufacturerPartId,
partId,
part,
duplicateSupplierPartId
]);
}
export function useManufacturerPartFields() {
export function useManufacturerPartFields({
duplicateManufacturerPartId
}: {
duplicateManufacturerPartId?: number | null;
} = {}) {
return useMemo(() => {
const fields: ApiFormFieldSet = {
part: {},
@@ -120,18 +142,32 @@ export function useManufacturerPartFields() {
MPN: {},
description: {},
tags: TagsField({}),
link: {}
link: {},
duplicate: DuplicateField({
originalId: duplicateManufacturerPartId,
extraFields: {
copy_parameters: {}
}
})
};
if (!duplicateManufacturerPartId) {
delete fields.duplicate;
}
return fields;
}, []);
}, [duplicateManufacturerPartId]);
}
/**
* Field set for editing a company instance
*/
export function companyFields(): ApiFormFieldSet {
return {
export function companyFields({
duplicateCompanyId
}: {
duplicateCompanyId?: number | null;
} = {}): ApiFormFieldSet {
const fields: ApiFormFieldSet = {
name: {},
description: {},
website: {
@@ -151,6 +187,18 @@ export function companyFields(): ApiFormFieldSet {
is_supplier: {},
is_manufacturer: {},
is_customer: {},
active: {}
active: {},
duplicate: DuplicateField({
originalId: duplicateCompanyId,
extraFields: {
copy_parameters: {}
}
})
};
if (!duplicateCompanyId) {
delete fields.duplicate;
}
return fields;
}
+1 -1
View File
@@ -156,7 +156,7 @@ export function usePartFields({
fields.duplicate = {
icon: <IconCopy />,
children: {
part: {
original: {
value: duplicatePartInstance?.pk,
hidden: true
},
@@ -316,7 +316,7 @@ export function usePurchaseOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
+1 -6
View File
@@ -84,15 +84,10 @@ export function useReturnOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
copy_lines: {
// Cannot duplicate lines from a return order!
value: false,
hidden: true
},
copy_extra_lines: {},
copy_parameters: {}
}
+1 -1
View File
@@ -97,7 +97,7 @@ export function useSalesOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
@@ -54,13 +54,12 @@ export function useTransferOrderFields({
if (!!duplicateOrderId) {
fields.duplicate = {
children: {
order_id: {
original: {
hidden: true,
value: duplicateOrderId
},
copy_lines: {},
// Transfer Orders don't have extra lines for now...
copy_extra_lines: { hidden: true, value: false }
copy_parameters: {}
}
};
}
+13 -8
View File
@@ -10,13 +10,13 @@ import 'mantine-datatable/styles.css';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import type * as LinguiCore from '@lingui/core';
import type * as LinguiReact from '@lingui/react';
import * as LinguiCore from '@lingui/core';
import * as LinguiReact from '@lingui/react';
// Global types to be exported for use in plugins
import type * as MantineCore from '@mantine/core';
import type * as MantineNotifications from '@mantine/notifications';
import * as MantineCore from '@mantine/core';
import * as MantineNotifications from '@mantine/notifications';
import * as React from 'react';
import type * as ReactDOM from 'react-dom';
import * as ReactDOM from 'react-dom';
import * as ReactDOMClient from 'react-dom/client';
import './styles/overrides.css';
@@ -24,7 +24,6 @@ import './styles/overrides.css';
import { getBaseUrl } from '@lib/functions/Navigation';
import type { HostList } from '@lib/types/Server';
import MainView from './views/MainView';
import { loadWindowGlobals } from './window';
// define settings
declare global {
@@ -123,5 +122,11 @@ ReactDOMClient.createRoot(
</React.StrictMode>
);
// Load globals onto the window object, so that they can be accessed by plugins without requiring direct imports
loadWindowGlobals();
// All window globals assigned here since these modules are already statically bundled
window.React = React;
window.ReactDOM = ReactDOM;
window.ReactDOMClient = ReactDOMClient;
window.MantineCore = MantineCore;
window.MantineNotifications = MantineNotifications;
window.LinguiCore = LinguiCore;
window.LinguiReact = LinguiReact;
@@ -1,3 +1,6 @@
import { PluginPanelKey } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import type { PanelGroupType, PanelType } from '@lib/types/Panel';
import { t } from '@lingui/core/macro';
import { Stack } from '@mantine/core';
import {
@@ -5,6 +8,7 @@ import {
IconCpu,
IconDevicesPc,
IconExclamationCircle,
IconFileCode,
IconFileDownload,
IconFileUpload,
IconHome,
@@ -13,6 +17,7 @@ import {
IconMail,
IconNotes,
IconPackages,
IconPhoto,
IconPlugConnected,
IconQrcode,
IconReport,
@@ -22,10 +27,6 @@ import {
IconUsersGroup
} from '@tabler/icons-react';
import { lazy, useMemo } from 'react';
import { PluginPanelKey } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import type { PanelGroupType, PanelType } from '@lib/types/Panel';
import PermissionDenied from '../../../../components/errors/PermissionDenied';
import PageTitle from '../../../../components/nav/PageTitle';
import { SettingsHeader } from '../../../../components/nav/SettingsHeader';
@@ -106,6 +107,14 @@ const LocationTypesTable = Loadable(
lazy(() => import('../../../../tables/stock/LocationTypesTable'))
);
const SnippetTable = Loadable(
lazy(() => import('../../../../tables/settings/SnippetTable'))
);
const AssetTable = Loadable(
lazy(() => import('../../../../tables/settings/AssetTable'))
);
export default function AdminCenter() {
const user = useUserState();
@@ -231,6 +240,18 @@ export default function AdminCenter() {
icon: <IconReport />,
content: <ReportTemplatePanel />
},
{
name: 'snippets',
label: t`Report Snippets`,
icon: <IconFileCode />,
content: <SnippetTable />
},
{
name: 'assets',
label: t`Report Assets`,
icon: <IconPhoto />,
content: <AssetTable />
},
{
name: 'location-types',
label: t`Location Types`,
@@ -283,7 +304,7 @@ export default function AdminCenter() {
{
id: 'reporting',
label: t`Reporting`,
panelIDs: ['labels', 'reports']
panelIDs: ['labels', 'reports', 'snippets', 'assets']
},
{
id: 'plm',
@@ -25,11 +25,13 @@ function PluginSettingGroupItem({
}) {
// Hide the accordion item if there are no settings for this plugin
const [count, setCount] = useState<number>(0);
const [doGet, setDoGet] = useState<boolean>(true);
// Callback once the plugin settings have been loaded
const onLoaded = useCallback(
(settings: SettingsStateProps) => {
setCount(settings.settings?.length || 0);
setCount(settings.getSettingLength());
setDoGet(false);
},
[pluginKey]
);
@@ -50,11 +52,20 @@ function PluginSettingGroupItem({
)}
</Group>
</Accordion.Control>
<Accordion.Panel>
{global ? (
<PluginSettingList pluginKey={pluginKey} onLoaded={onLoaded} />
<PluginSettingList
pluginKey={pluginKey}
onLoaded={onLoaded}
doGet={doGet}
/>
) : (
<PluginUserSettingList pluginKey={pluginKey} onLoaded={onLoaded} />
<PluginUserSettingList
pluginKey={pluginKey}
onLoaded={onLoaded}
doGet={doGet}
/>
)}
</Accordion.Panel>
</Accordion.Item>
@@ -632,6 +632,7 @@ export default function BuildDetail() {
const duplicateBuildOrderFields = useBuildOrderFields({
create: false,
duplicateBuildId: build.pk,
modalId: 'duplicate-build-order'
});
@@ -31,6 +31,7 @@ import { DetailsImage } from '../../components/details/DetailsImage';
import { ItemDetailsGrid } from '../../components/details/ItemDetails';
import {
DeleteItemAction,
DuplicateItemAction,
EditItemAction,
OptionsActionDropdown
} from '../../components/items/ActionDropdown';
@@ -43,6 +44,7 @@ import { PanelGroup } from '../../components/panels/PanelGroup';
import ParametersPanel from '../../components/panels/ParametersPanel';
import { companyFields } from '../../forms/CompanyForms';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
@@ -293,11 +295,23 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
url: ApiEndpoints.company_list,
pk: company?.pk,
title: t`Edit Company`,
fields: companyFields(),
fields: useMemo(() => companyFields({}), []),
queryParams: new URLSearchParams({ tags: 'true' }),
onFormSuccess: refreshInstance
});
const duplicateCompany = useCreateApiFormModal({
url: ApiEndpoints.company_list,
title: t`Duplicate Company`,
initialData: useMemo(() => ({ ...company }), [company]),
fields: useMemo(
() => companyFields({ duplicateCompanyId: company?.pk }),
[company]
),
follow: true,
modelType: ModelType.company
});
const deleteCompany = useDeleteApiFormModal({
url: ApiEndpoints.company_list,
pk: company?.pk,
@@ -322,6 +336,10 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
hidden: !user.hasChangeRole(UserRoles.purchase_order),
onClick: () => editCompany.open()
}),
DuplicateItemAction({
hidden: !user.hasAddRole(UserRoles.purchase_order),
onClick: () => duplicateCompany.open()
}),
DeleteItemAction({
hidden: !user.hasDeleteRole(UserRoles.purchase_order),
onClick: () => deleteCompany.open()
@@ -345,6 +363,7 @@ export default function CompanyDetail(props: Readonly<CompanyDetailProps>) {
<>
{editCompany.modal}
{deleteCompany.modal}
{duplicateCompany.modal}
<InstanceDetail
query={instanceQuery}
requiredPermission={ModelType.company}
@@ -220,10 +220,14 @@ export default function ManufacturerPartDetail() {
onFormSuccess: refreshInstance
});
const duplicateManufacturerPartFields = useManufacturerPartFields({
duplicateManufacturerPartId: manufacturerPart?.pk
});
const duplicateManufacturerPart = useCreateApiFormModal({
url: ApiEndpoints.manufacturer_part_list,
title: t`Add Manufacturer Part`,
fields: editManufacturerPartFields,
fields: duplicateManufacturerPartFields,
initialData: {
...manufacturerPart
},
@@ -357,10 +357,14 @@ export default function SupplierPartDetail() {
}
});
const duplicateSupplierPartFields = useSupplierPartFields({
duplicateSupplierPartId: supplierPart?.pk
});
const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: supplierPartFields,
fields: duplicateSupplierPartFields,
initialData: {
...supplierPart
},
@@ -65,6 +65,9 @@ export const useGlobalSettingsState = create<SettingsStateProps>(
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
})
);
@@ -114,6 +117,9 @@ export const useUserSettingsState = create<SettingsStateProps>((set, get) => ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
@@ -185,6 +191,9 @@ export const createPluginSettingsState = ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
};
@@ -246,6 +255,9 @@ export const createMachineSettingsState = ({
isSet: (key: string, default_value?: boolean) => {
const value = get().lookup[key] ?? default_value ?? 'false';
return isTrue(value);
},
getSettingLength: () => {
return Object.keys(get().lookup).length;
}
}));
};
+2 -1
View File
@@ -2,11 +2,12 @@ import { t } from '@lingui/core/macro';
import { Alert, Divider, Group, Stack, Text } from '@mantine/core';
import { useCallback, useMemo, useState } from 'react';
import { RowEditAction } from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import { ActionButton, RowEditAction, UserRoles } from '@lib/index';
import { ActionButton, UserRoles } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { RowAction, TableColumn } from '@lib/types/Tables';
import { IconExclamationCircle, IconReplace } from '@tabler/icons-react';
@@ -1,9 +1,8 @@
import { RowDeleteAction, RowEditAction } from '@lib/components/RowActions';
import useTable from '@lib/hooks/UseTable';
import {
ApiEndpoints,
ModelType,
RowDeleteAction,
RowEditAction,
YesNoButton,
apiUrl,
formatDecimal
@@ -1,11 +1,13 @@
import {
RowDeleteAction,
RowDuplicateAction,
RowEditAction
} from '@lib/components/RowActions';
import useTable from '@lib/hooks/UseTable';
import {
AddItemButton,
ApiEndpoints,
type ApiFormFieldSet,
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
UserRoles,
apiUrl
} from '@lib/index';
@@ -23,6 +23,7 @@ import { useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { AddItemButton } from '@lib/components/AddItemButton';
import { RowDeleteAction, RowEditAction } from '@lib/components/RowActions';
import { StylishText } from '@lib/components/StylishText';
import { YesNoButton } from '@lib/components/YesNoButton';
import {
@@ -32,7 +33,7 @@ import {
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import { RowDeleteAction, RowEditAction, formatDecimal } from '@lib/index';
import { formatDecimal } from '@lib/index';
import type { RowAction, TableColumn } from '@lib/types/Tables';
import type { InvenTreeTableProps } from '@lib/types/Tables';
import { Trans } from '@lingui/react/macro';
+5 -7
View File
@@ -24,6 +24,7 @@ import { ActionDropdown } from '../../components/items/ActionDropdown';
import ImportPartWizard from '../../components/wizards/ImportPartWizard';
import OrderPartsWizard from '../../components/wizards/OrderPartsWizard';
import { formatDecimal, formatPriceRange } from '../../defaults/formatters';
import { DuplicateField } from '../../forms/CommonFields';
import { dataImporterSessionFields } from '../../forms/ImporterForms';
import { usePartFields } from '../../forms/PartForms';
import { InvenTreeIcon } from '../../functions/icons';
@@ -312,12 +313,9 @@ export function PartListTable({
const duplicatePartFields: ApiFormFieldSet = useMemo(() => {
return {
...createPartFields,
duplicate: {
children: {
part: {
value: selectedPart.pk,
hidden: true
},
duplicate: DuplicateField({
originalId: selectedPart.pk,
extraFields: {
copy_image: {
value: true
},
@@ -337,7 +335,7 @@ export function PartListTable({
hidden: !selectedPart.testable
}
}
}
})
};
}, [createPartFields, globalSettings, selectedPart]);
@@ -133,10 +133,14 @@ export function ManufacturerPartTable({
table: table
});
const duplicateManufacturerPartFields = useManufacturerPartFields({
duplicateManufacturerPartId: selectedPart?.pk
});
const duplicateManufacturerPart = useCreateApiFormModal({
url: ApiEndpoints.manufacturer_part_list,
title: t`Add Manufacturer Part`,
fields: useMemo(() => manufacturerPartFields, [manufacturerPartFields]),
fields: duplicateManufacturerPartFields,
table: table,
initialData: {
...selectedPart
@@ -292,10 +292,14 @@ export function SupplierPartTable({
}
});
const duplicateSupplierPartFields = useSupplierPartFields({
duplicateSupplierPartId: selectedSupplierPart?.pk
});
const duplicateSupplierPart = useCreateApiFormModal({
url: ApiEndpoints.supplier_part_list,
title: t`Add Supplier Part`,
fields: useMemo(() => editSupplierPartFields, [editSupplierPartFields]),
fields: duplicateSupplierPartFields,
initialData: {
...selectedSupplierPart,
primary: false,
@@ -0,0 +1,124 @@
import { AddItemButton } from '@lib/components/AddItemButton';
import { type RowAction, RowDeleteAction } from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { Alert, Text } from '@mantine/core';
import { IconInfoCircle } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import {
useCreateApiFormModal,
useDeleteApiFormModal
} from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState';
import { DescriptionColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export type AssetI = {
pk: number;
asset: string;
description: string;
};
export default function AssetTable() {
const table = useTable('report-asset');
const user = useUserState();
const columns: TableColumn<AssetI>[] = useMemo(() => {
return [
{
accessor: 'asset',
title: t`Asset`,
sortable: false,
switchable: false,
render: (record: AssetI) => {
if (!record.asset) {
return '-';
}
return <AttachmentLink attachment={record.asset} />;
},
noContext: true
},
DescriptionColumn({
accessor: 'description',
sortable: false,
switchable: false
})
];
}, []);
const [selectedAsset, setSelectedAsset] = useState<number>(-1);
const rowActions = useCallback(
(record: AssetI): RowAction[] => {
return [
RowDeleteAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedAsset(record.pk);
deleteAsset.open();
}
})
];
},
[user]
);
const newAssetFields: ApiFormFieldSet = useMemo(() => {
return {
asset: {},
description: {}
};
}, []);
const deleteAsset = useDeleteApiFormModal({
url: ApiEndpoints.report_asset,
pk: selectedAsset,
title: t`Delete Asset`,
table: table
});
const newAsset = useCreateApiFormModal({
url: ApiEndpoints.report_asset,
title: t`Add Asset`,
fields: newAssetFields,
table: table
});
const tableActions: ReactNode[] = useMemo(() => {
return [
<AddItemButton
key='add-asset'
onClick={() => newAsset.open()}
tooltip={t`Add asset`}
hidden={!user.isStaff()}
/>
];
}, [user]);
return (
<>
{newAsset.modal}
{deleteAsset.modal}
<Alert icon={<IconInfoCircle />} title={t`Assets`}>
<Text>{t`Assets are files (such as images) which can be used when rendering reports and labels.`}</Text>
</Alert>
<InvenTreeTable
url={apiUrl(ApiEndpoints.report_asset)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
tableActions: tableActions,
enableSearch: false,
enableFilters: false
}}
/>
</>
);
}
@@ -15,8 +15,9 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import useTable from '@lib/hooks/UseTable';
import { type ApiFormModalProps, getDetailUrl } from '@lib/index';
import type { ApiFormModalProps } from '@lib/index';
import type { TableColumn, TableState } from '@lib/types/Tables';
import { IconUsersGroup } from '@tabler/icons-react';
import { useNavigate } from 'react-router-dom';
@@ -0,0 +1,241 @@
import { AddItemButton } from '@lib/components/AddItemButton';
import {
type RowAction,
RowDeleteAction,
RowEditAction
} from '@lib/components/RowActions';
import { DetailDrawer } from '@lib/components/nav/DetailDrawer';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
import type { ApiFormFieldSet } from '@lib/types/Forms';
import type { TableColumn } from '@lib/types/Tables';
import { t } from '@lingui/core/macro';
import { Trans } from '@lingui/react/macro';
import {
Alert,
Group,
LoadingOverlay,
Stack,
Text,
Title
} from '@mantine/core';
import { IconFileCode, IconInfoCircle } from '@tabler/icons-react';
import { type ReactNode, useCallback, useMemo, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import {
CodeEditor,
TemplateEditor
} from '../../components/editors/TemplateEditor';
import { AttachmentLink } from '../../components/items/AttachmentLink';
import {
useCreateApiFormModal,
useDeleteApiFormModal,
useEditApiFormModal
} from '../../hooks/UseForm';
import { useInstance } from '../../hooks/UseInstance';
import { useUserState } from '../../states/UserState';
import { DescriptionColumn } from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
import type { TemplateI } from './TemplateTable';
export type SnippetI = {
pk: number;
snippet: string;
description: string;
};
export function SnippetDrawer({
id
}: Readonly<{
id: string | number;
}>) {
const {
instance: snippet,
instanceQuery: { isFetching, error }
} = useInstance<SnippetI>({
endpoint: ApiEndpoints.report_snippet,
hasPrimaryKey: true,
pk: id
});
const filename = useMemo(
() => snippet?.snippet?.split('/').pop() ?? '',
[snippet]
);
if (isFetching) {
return <LoadingOverlay visible={true} />;
}
if (error || !snippet) {
return (
<Text>
{(error as any)?.response?.status === 404 ? (
<Trans>Snippet not found</Trans>
) : (
<Trans>An error occurred while fetching snippet details</Trans>
)}
</Text>
);
}
return (
<Stack gap='xs' style={{ display: 'flex', flex: '1' }}>
<Group justify='left'>
<Title order={4}>{filename}</Title>
</Group>
<TemplateEditor
templateUrl={apiUrl(ApiEndpoints.report_snippet, id)}
template={snippet as unknown as TemplateI}
fileField='snippet'
editors={[CodeEditor]}
previewAreas={[]}
/>
</Stack>
);
}
export default function SnippetTable() {
const table = useTable('report-snippet');
const navigate = useNavigate();
const user = useUserState();
const openDetailDrawer = useCallback((pk: number) => navigate(`${pk}/`), []);
const columns: TableColumn<SnippetI>[] = useMemo(() => {
return [
{
accessor: 'snippet',
title: t`Snippet`,
sortable: false,
switchable: false,
render: (record: SnippetI) => {
if (!record.snippet) {
return '-';
}
return <AttachmentLink attachment={record.snippet} />;
},
noContext: true
},
DescriptionColumn({
accessor: 'description',
sortable: false,
switchable: false
})
];
}, []);
const [selectedSnippet, setSelectedSnippet] = useState<number>(-1);
const rowActions = useCallback(
(record: SnippetI): RowAction[] => {
return [
{
title: t`Modify`,
tooltip: t`Modify snippet file`,
icon: <IconFileCode />,
onClick: () => openDetailDrawer(record.pk),
hidden: !user.isStaff()
},
RowEditAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedSnippet(record.pk);
editSnippet.open();
}
}),
RowDeleteAction({
hidden: !user.isStaff(),
onClick: () => {
setSelectedSnippet(record.pk);
deleteSnippet.open();
}
})
];
},
[user]
);
const editSnippetFields: ApiFormFieldSet = useMemo(() => {
return {
description: {}
};
}, []);
const newSnippetFields: ApiFormFieldSet = useMemo(() => {
return {
snippet: {},
description: {}
};
}, []);
const editSnippet = useEditApiFormModal({
url: ApiEndpoints.report_snippet,
pk: selectedSnippet,
title: t`Edit Snippet`,
fields: editSnippetFields,
onFormSuccess: (record: any) => table.updateRecord(record)
});
const deleteSnippet = useDeleteApiFormModal({
url: ApiEndpoints.report_snippet,
pk: selectedSnippet,
title: t`Delete Snippet`,
table: table
});
const newSnippet = useCreateApiFormModal({
url: ApiEndpoints.report_snippet,
title: t`Add Snippet`,
fields: newSnippetFields,
onFormSuccess: (data) => {
table.refreshTable();
openDetailDrawer(data.pk);
}
});
const tableActions: ReactNode[] = useMemo(() => {
return [
<AddItemButton
key='add-snippet'
onClick={() => newSnippet.open()}
tooltip={t`Add snippet`}
hidden={!user.isStaff()}
/>
];
}, [user]);
return (
<>
{newSnippet.modal}
{editSnippet.modal}
{deleteSnippet.modal}
<DetailDrawer
title={t`Edit Snippet`}
size={'90%'}
closeOnEscape={false}
renderContent={(id) => {
return <SnippetDrawer id={id ?? ''} />;
}}
/>
<Alert icon={<IconInfoCircle />} title={t`Snippets`}>
<Text>{t`Snippets are reusable pieces of HTML content that can be inserted into reports and labels.`}</Text>
</Alert>
<InvenTreeTable
url={apiUrl(ApiEndpoints.report_snippet)}
tableState={table}
columns={columns}
props={{
rowActions: rowActions,
tableActions: tableActions,
enableSearch: false,
enableFilters: false,
onRowClick: (record) => openDetailDrawer(record.pk)
}}
/>
</>
);
}
@@ -22,8 +22,9 @@ import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { ModelType } from '@lib/enums/ModelType';
import { UserRoles } from '@lib/enums/Roles';
import { apiUrl } from '@lib/functions/Api';
import { getDetailUrl } from '@lib/functions/Navigation';
import useTable from '@lib/hooks/UseTable';
import { type ApiFormModalProps, getDetailUrl } from '@lib/index';
import type { ApiFormModalProps } from '@lib/index';
import type { TableFilter } from '@lib/types/Filters';
import type { TableColumn, TableState } from '@lib/types/Tables';
import { showNotification } from '@mantine/notifications';
@@ -16,7 +16,12 @@ import {
} from '../../forms/StockForms';
import { useCreateApiFormModal } from '../../hooks/UseForm';
import { useUserState } from '../../states/UserState';
import { PartColumn, StatusColumn, StockColumn } from '../ColumnRenderers';
import {
IPNColumn,
PartColumn,
StatusColumn,
StockColumn
} from '../ColumnRenderers';
import { InvenTreeTable } from '../InvenTreeTable';
export default function InstalledItemsTable({
@@ -61,6 +66,7 @@ export default function InstalledItemsTable({
PartColumn({
part: 'part_detail'
}),
IPNColumn({}),
StockColumn({
accessor: '',
title: t`Stock Item`,
@@ -1,3 +1,9 @@
import {
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
RowViewAction
} from '@lib/components/RowActions';
import { ApiEndpoints } from '@lib/enums/ApiEndpoints';
import { apiUrl } from '@lib/functions/Api';
import useTable from '@lib/hooks/UseTable';
@@ -6,10 +12,6 @@ import {
AddItemButton,
ModelType,
ProgressBar,
RowDeleteAction,
RowDuplicateAction,
RowEditAction,
RowViewAction,
UserRoles,
formatDecimal
} from '@lib/index';
-38
View File
@@ -1,38 +0,0 @@
/**
* Expose certain globals to the window object, so that they can be accessed by plugins,
* without requiring plugins to import these dependencies directly.
*/
export function loadWindowGlobals() {
// (window as any).React = React;
import('react').then((module) => {
window.React = module;
});
// (window as any).ReactDOM = ReactDOM;
import('react-dom').then((module) => {
window.ReactDOM = module;
});
// (window as any).ReactDOMClient = ReactDOMClient;
import('react-dom/client').then((module) => {
window.ReactDOMClient = module;
});
// (window as any).MantineCore = MantineCore;
import('@mantine/core').then((module) => {
window.MantineCore = module;
});
// (window as any).MantineNotifications = MantineNotifications;
import('@mantine/notifications').then((module) => {
window.MantineNotifications = module;
});
import('@lingui/core').then((module) => {
window.LinguiCore = module;
});
import('@lingui/react').then((module) => {
window.LinguiReact = module;
});
}
+2 -2
View File
@@ -75,8 +75,8 @@ test('Plugins - User Settings', async ({ browser }) => {
await navigate(page, 'settings/user/');
await loadTab(page, 'Plugin Settings');
// User settings for the "Sample Plugin" should be visible
await page.getByRole('button', { name: 'Sample Plugin' }).click();
// User settings for the "SampleIntegrationPlugin" should be visible
await page.getByRole('button', { name: 'SampleIntegrationPlugin' }).click();
await page.getByText('User Setting 1').waitFor();
await page.getByText('User Setting 2').waitFor();
+31
View File
@@ -254,6 +254,37 @@ test('Settings - Admin', async ({ browser }) => {
await loadTab(page, 'Category Parameters');
await loadTab(page, 'Label Templates');
await loadTab(page, 'Report Templates');
// Check the "report snippets" panel
await loadTab(page, 'Report Snippets');
await page
.getByText(
'Snippets are reusable pieces of HTML content that can be inserted into reports and labels.'
)
.waitFor();
// Launch the dialog to upload a new snippet
await page.getByLabel('action-button-add-snippet').click();
await page.getByText('Add Snippet', { exact: true }).waitFor();
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
await page.getByLabel('text-field-description').waitFor();
await page.getByRole('button', { name: 'Cancel' }).click();
// Check the "report assets" panel
await loadTab(page, 'Report Assets');
await page
.getByText(
'Assets are files (such as images) which can be used when rendering reports and labels.'
)
.waitFor();
// Launch the dialog to upload a new asset
await page.getByLabel('action-button-add-asset').click();
await page.getByText('Add Asset', { exact: true }).waitFor();
await page.locator('input[type="file"]').waitFor({ state: 'attached' });
await page.getByLabel('text-field-description').waitFor();
await page.getByRole('button', { name: 'Cancel' }).click();
await loadTab(page, 'Plugins');
// Adjust some "location type" items
+375 -192
View File
@@ -60,12 +60,25 @@
js-tokens "^4.0.0"
picocolors "^1.1.1"
"@babel/code-frame@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-8.0.0.tgz#f374ea9392011ffecf223805dd0d9315606e5bbf"
integrity sha512-dYYg153EyN2Ekbqw2zAsbd6/JR+9N2SEoC7YV2GyyqMM7x9bLDTjBD6XBhSMLH0wtIVyJj03jWNriQhaN+eoCw==
dependencies:
"@babel/helper-validator-identifier" "^8.0.0"
js-tokens "^10.0.0"
"@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.17.7", "@babel/core@^7.20.12", "@babel/core@^7.21.0", "@babel/core@^7.23.9", "@babel/core@^7.29.0", "@babel/core@^7.29.7":
"@babel/compat-data@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-8.0.0.tgz#e780eb6052d8ceeaeadd3a6cd82dda470a945622"
integrity sha512-DOjnob/cXOUgDOozCDeq/aK2p5y8dUIVdf6tNhEV1HQRd6I8aQ4f4fbtHRVEvb6lP3BGomrKHiS8ICAASSVQSw==
"@babel/core@^7.17.7", "@babel/core@^7.20.12", "@babel/core@^7.21.0", "@babel/core@^7.23.9", "@babel/core@^7.29.0":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7"
integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==
@@ -86,6 +99,28 @@
json5 "^2.2.3"
semver "^6.3.1"
"@babel/core@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-8.0.1.tgz#dcb133d3ddcfb2b5a6558da03bf240319da0b8c8"
integrity sha512-5FgxM4dLQpMJHSiVATk8foW263dVHQHBVpXYiimNECVWG01f4nFyEbQixeT6Mwvg7TayREJ2gpKl3o2RoMdnqw==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/generator" "^8.0.0"
"@babel/helper-compilation-targets" "^8.0.0"
"@babel/helpers" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/template" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@types/gensync" "^1.0.5"
convert-source-map "^2.0.0"
empathic "^2.0.1"
gensync "^1.0.0-beta.2"
import-meta-resolve "^4.2.0"
json5 "^2.2.3"
obug "^2.1.1"
semver "^7.7.3"
"@babel/generator@^7.21.1", "@babel/generator@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3"
@@ -97,12 +132,24 @@
"@jridgewell/trace-mapping" "^0.3.28"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz#c70fe3c6ecbdc3fd2dd1b0f498428b88b82ce47f"
integrity sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==
"@babel/generator@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-8.0.0.tgz#24b7b53a74fa8e74cc2377e054fecef4dcdada96"
integrity sha512-NT9NrVwJsbSV6Y2FSstWa71EETOnzrjkL5/wX3D2mYHtKM+qvqB1DvR4D0Setb/gDBsHzRICifwEWMO8CnTF6g==
dependencies:
"@babel/types" "^7.29.7"
"@babel/parser" "^8.0.0"
"@babel/types" "^8.0.0"
"@jridgewell/gen-mapping" "^0.3.12"
"@jridgewell/trace-mapping" "^0.3.28"
"@types/jsesc" "^2.5.0"
jsesc "^3.0.2"
"@babel/helper-annotate-as-pure@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-8.0.0.tgz#e094d0ea95f319cdc399fe25ec82e4b3dddb8461"
integrity sha512-NSpMkMsvvZqzThJ0p1B02cbtA2ObEyfBvq950bmNkyxsxvcxwhvvCB036rKhlEnuBBo30bOrk13u3FzlKSoRrw==
dependencies:
"@babel/types" "^8.0.0"
"@babel/helper-compilation-targets@^7.29.7":
version "7.29.7"
@@ -115,31 +162,47 @@
lru-cache "^5.1.1"
semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz#6eddf286f2ec418f740c91d60a83347c55838ddd"
integrity sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==
"@babel/helper-compilation-targets@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-8.0.0.tgz#63f230f427c9ea82323d829c935f6190d93b3736"
integrity sha512-JwculLABZvyPvyLBpwU/E/IbH2uM3mnxNtIJpxnIfb24y1PrdVxK5Dqjle4DpgqpGRnwgC7G8IkzPdSXZrO1Ew==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-member-expression-to-functions" "^7.29.7"
"@babel/helper-optimise-call-expression" "^7.29.7"
"@babel/helper-replace-supers" "^7.29.7"
"@babel/helper-skip-transparent-expression-wrappers" "^7.29.7"
"@babel/traverse" "^7.29.7"
semver "^6.3.1"
"@babel/compat-data" "^8.0.0"
"@babel/helper-validator-option" "^8.0.0"
browserslist "^4.24.0"
lru-cache "^11.0.0"
semver "^7.7.3"
"@babel/helper-create-class-features-plugin@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-8.0.1.tgz#d8c5f55a3741a5f7d526bb4bfc361805601ef954"
integrity sha512-++t3ZktzlLmASAxIlxeXQK9Z2YwUafYGYcvGBFevqOqt16HozVHStUoQvWD09fzAZOb/uJGpUTBuGK41AJAuOA==
dependencies:
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-member-expression-to-functions" "^8.0.0"
"@babel/helper-optimise-call-expression" "^8.0.0"
"@babel/helper-replace-supers" "^8.0.1"
"@babel/helper-skip-transparent-expression-wrappers" "^8.0.0"
"@babel/traverse" "^8.0.0"
semver "^7.7.3"
"@babel/helper-globals@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b"
integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==
"@babel/helper-member-expression-to-functions@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz#8dbdb3ce0b5c487e1aec10e13c9a43a500814df8"
integrity sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==
"@babel/helper-globals@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-8.0.0.tgz#c93789d6c1c2f1b65a97a07515c471a049559bf1"
integrity sha512-lLozHOM6sWWlxNo8CYqHy4MBZeTvHXNgVPBfPOGsjPKUzHC2Az9QwB6gxdQmpwHl6GlQtbGgS+lj5887guDiLw==
"@babel/helper-member-expression-to-functions@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-8.0.0.tgz#c097e24f7aeb60ab08966ef6c2127fc8ee32449e"
integrity sha512-xkXrMbtk87Gk7+oKBVmBc6EORg/Qwx++AHESldmHkpvG8wgccdhJJFwrzqlF382Fk8wfXhJHWE/g/43QvEGNPQ==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.29.7":
version "7.29.7"
@@ -149,6 +212,14 @@
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-module-imports@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-8.0.0.tgz#b6b16c75f80616b545f72e842d8224b981fd41d5"
integrity sha512-NZ7mSS93o4ndX4KrbD7W8Sf3QT8Qe24PrnFyUcuOPDzK6faqDFKjY9RG7he7+I7FdiQ4llpnosFqzrXa+Vy3Ew==
dependencies:
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/helper-module-transforms@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae"
@@ -158,50 +229,79 @@
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-optimise-call-expression@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz#77b0b5b94f1997fa9d6e3125f445227b1faf9d85"
integrity sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==
"@babel/helper-module-transforms@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-8.0.1.tgz#e8cf988892d7d8c774a9072dca0962f94b606bb6"
integrity sha512-UgAhl1kqiW5ciE0yCXqqvnb4H2n3IELJ7lIIQRezwDPilPEZX5i+Rvbja9MFTkwUn2biEiSMeV31aUzR4Lwakw==
dependencies:
"@babel/types" "^7.29.7"
"@babel/helper-module-imports" "^8.0.0"
"@babel/helper-validator-identifier" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/helper-optimise-call-expression@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-8.0.0.tgz#dc1118493494fbfc3bb24b490550673908a99b6c"
integrity sha512-3W6satvtPuCUkUx63S2jMoW9EQNYkADgs1HTfufmL7gCmAulHMKupA/12WNz4A0GMMFn/YnWWwqOT9IZrJHQjg==
dependencies:
"@babel/types" "^8.0.0"
"@babel/helper-plugin-utils@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz#c0a0766f1a13617d8a17407d7ab8f9d486225ea4"
integrity sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==
"@babel/helper-replace-supers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz#bc3c3964329043c79112e513c1b198f16589ac21"
integrity sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==
dependencies:
"@babel/helper-member-expression-to-functions" "^7.29.7"
"@babel/helper-optimise-call-expression" "^7.29.7"
"@babel/traverse" "^7.29.7"
"@babel/helper-plugin-utils@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-8.0.1.tgz#d1c9d8ae225902c597deac8c79ace4227feac8af"
integrity sha512-3PKFgjTyPlhFhorfP+SjKQxLViIL++zWjFOO4hGriYU+Bsm983DxEM1JmDRJVWXV0O9npu+xXRqz7Pbd3mh70g==
"@babel/helper-skip-transparent-expression-wrappers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz#50c95c7e4c4f54936cfa0116428edc559862d551"
integrity sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==
"@babel/helper-replace-supers@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-8.0.1.tgz#c9b23aba6b9aff16cacac135052e790cde8d7d5b"
integrity sha512-B1SZADIcy3tmH8CmWvj4SHi/oAPom4UL3uknTc2QRNsPVLFk/sPnZvQL/8kj7Y5omvjMqie0vklvs6XM4OLW5Q==
dependencies:
"@babel/traverse" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-member-expression-to-functions" "^8.0.0"
"@babel/helper-optimise-call-expression" "^8.0.0"
"@babel/traverse" "^8.0.0"
"@babel/helper-skip-transparent-expression-wrappers@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-8.0.0.tgz#27a6db3fb4ed17d2b32dbb69c4b30be346d8e8a6"
integrity sha512-xmCA9kP3IhySsqhzwIdWGlDN/1A4cCKNBO/uwZx/3YzmDoMePwno2Q5/Bq0q+tYaKbeF940YiKV/kaW8Mzvpjw==
dependencies:
"@babel/traverse" "^8.0.0"
"@babel/types" "^8.0.0"
"@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-string-parser@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-8.0.0.tgz#4d47d9ad35d0f5bd7d202b348bf558328a347977"
integrity sha512-6mJgmFFFIIO82vvoLt9XtRC7/TkzXfts1t/SpRX4IHSzMgqoPYCWesVu1udUPUWioAE/2fcG6WuI8zrkE1gwrg==
"@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-identifier@^8.0.0":
version "8.0.2"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-8.0.2.tgz#5527f2e24e5a9f4de7426dca448031749691bb6f"
integrity sha512-9Fr9QeyCAyi1BR1jKZ6uYQ24EIhQUx5ReHfQU7drOE+TPOb+w11/dsqLkMOT2U29OdCT71XajrOT8xDc1C7orA==
"@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-validator-option@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-8.0.0.tgz#53307a6882254fa8d940017a701a8f7ec603a1a9"
integrity sha512-U4Dybxh4WESWHt5XhBeExi4DrY0/DNK1aHpQbsrQXCUbFHuMweT0TpLEWKvaraV2Y6fS+ZXunsZ8zIuZIgvF2Q==
"@babel/helpers@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607"
@@ -210,6 +310,14 @@
"@babel/template" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helpers@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-8.0.0.tgz#2c612d4b7fdbd5f1d2fa7321015e36b02d1a2d79"
integrity sha512-wfbi91pM3py96oIiJEz7qIpyXDytgr9zQC1HEWwlGNVRAEmItuU/0a41ZUKu1sJGyhhOIpc4t5vk4PYzt8wpsg==
dependencies:
"@babel/template" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.20.7", "@babel/parser@^7.22.0", "@babel/parser@^7.23.9", "@babel/parser@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334"
@@ -217,41 +325,62 @@
dependencies:
"@babel/types" "^7.29.7"
"@babel/plugin-syntax-jsx@^7.16.7", "@babel/plugin-syntax-jsx@^7.29.7":
"@babel/parser@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-8.0.0.tgz#31f6860840277dc1c6d6f8b67bf74e0ccaa5df0a"
integrity sha512-aLxAE+imI9bCcyaPrUDjBv3uSkWieifjLe0kuFOZF0zli0L6GCsTmsePnTr55adbIAgYz2zhN1vnFimCBUYcRQ==
dependencies:
"@babel/types" "^8.0.0"
"@babel/plugin-syntax-jsx@^7.16.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz#622c16f9ad63782fe6e83dadc7e40330744b7f1e"
integrity sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.23.3", "@babel/plugin-syntax-typescript@^7.29.7":
"@babel/plugin-syntax-jsx@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-8.0.1.tgz#ab457699bd42e3071541fdc2dbade25049f2cc2f"
integrity sha512-n0jtCOxEovhU7METqSQjcZO9pX53nu9uNIjMS+hEt+Nt9jA7oOZoBIgbCxhhASmF6T6rPDGge5UAvh6Z4eFz/g==
dependencies:
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-syntax-typescript@^7.16.7", "@babel/plugin-syntax-typescript@^7.23.3":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz#7c29388932313ed58413a0343048d75d92fb5b24"
integrity sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-transform-modules-commonjs@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz#70e6835abf2663dafbe94b8ef1f51de7351ef135"
integrity sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==
"@babel/plugin-syntax-typescript@^8.0.1":
version "8.0.3"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-8.0.3.tgz#695e5768ff9f2f83289c6dee5b1dc734becd0c59"
integrity sha512-jmTPwps7oSQSZaV1SxkQ3C12UWyufGysGc5OzDpZzvPAIX4mO7dJT3hoqkWVrSImvkcMiknir1iLN1SNV/CZzg==
dependencies:
"@babel/helper-module-transforms" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-display-name@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.29.7.tgz#bf161a6d750267b79db7ff6f8fb89c3369b02df3"
integrity sha512-+1wdDMGNb4UPeY3Q4L5yLiYe6TXPXubs4NjrgRFw13hPRLJfEMw2Q5OXkee6/IfdqePIeW4Jjwe3aBh7SdKz4Q==
"@babel/plugin-transform-modules-commonjs@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-8.0.1.tgz#ae8c414da671dc7ac4bb4b502a2714841ab84402"
integrity sha512-PMuzulWrrzFNmY3lXSk/tV9NRb7y0eZZLJY4UEo2TKszroxvUZHAPPi+T9FDyrQhod+TQA+t+8/QYaaMpiEuhA==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-module-transforms" "^8.0.1"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-jsx-development@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.29.7.tgz#64e6aacb5cb43b9e80d3d5f19ddefc158a624f09"
integrity sha512-Xfy3UVMF04+ypnFbkhvfqtmvwfe92qwQdbGZVonhE+6v35GzlofmOnA1szaZqzb9xYWr0nl1e5EMmzi0DNON1g==
"@babel/plugin-transform-react-display-name@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-8.0.1.tgz#da70220bacd0abcfb3a5fa4af89acde94d891fbc"
integrity sha512-soLishXlkyu6jcICPyO3HEP7A3GCzKEnn7XfvYrImuWEOwFAz93qShmWSYPf5ww0ZkO4By0zsN2bVIDF54fSdA==
dependencies:
"@babel/plugin-transform-react-jsx" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-react-jsx-development@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-8.0.1.tgz#11fa2b7af26ceaafc47a3cc6e2bd720bba96fc04"
integrity sha512-Hb+HUZpV9KFHjm+F+P3aLDMi8QXU9l3ROCQv20z18Me2sGyW5nNNR5YTevNlgHvCpFek3BnAwhDGq/BRndXViw==
dependencies:
"@babel/plugin-transform-react-jsx" "^8.0.1"
"@babel/plugin-transform-react-jsx-self@^7.27.1":
version "7.29.7"
@@ -267,64 +396,68 @@
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-transform-react-jsx@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.29.7.tgz#3d16a0e5773f079400a8c82a190709cdf92ee204"
integrity sha512-WsZulLVBUHXVj2cUcPVx6UE21TpalB6bHbSFErKT0Ib++ax24jjXe73FqlWvdylFOjiuPHYi6VCcgRad1ItN+A==
"@babel/plugin-transform-react-jsx@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-8.0.1.tgz#0b7192bd35e5ca68d1324cd9347f5990f296177f"
integrity sha512-NgkoF7Uq+30TmOPDdNUimT0Nta02uVjqJRFNlVWKrbOCu/CkzfHa4aMnIs0lMpkMmZmWA1e42Va+F04i/pY1zw==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-module-imports" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/plugin-syntax-jsx" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-module-imports" "^8.0.0"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-syntax-jsx" "^8.0.1"
"@babel/types" "^8.0.0"
"@babel/plugin-transform-react-pure-annotations@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.29.7.tgz#76445c90112dd0a7371b63264563bfa9a4fcd6e3"
integrity sha512-H5E+HBgDpr6Q5t+Aj11tL7XkIui1jhbIoArVQnqjgXo5/3YxkN7ZEBcWF4RQlB0T4rrxJQbXS6kiFV6B7XTqUA==
"@babel/plugin-transform-react-pure-annotations@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-8.0.1.tgz#95d5bf8dab5ce0631b320b75ce3c6a6e550c356a"
integrity sha512-7/8UwU8hoPBurXa9tUiTTC8aACTRy5tCqLUtqikHp2eGiWoEB57AduOdbQ71OOMTEvawKrGhv3WfzkDpI+/oSg==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/plugin-transform-typescript@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz#f0449c3df7037bbe232043476851c38f5e4a7615"
integrity sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==
"@babel/plugin-transform-typescript@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-8.0.1.tgz#805b11cae5334b4346640286950003c897f24b72"
integrity sha512-0Svqp3413Eg0GElldykF/T7SNsxQO5YVGD70fZyAdZTnX8WRgcopmbiU7GTa5xY5ZnJcEpNbfns8/GjX+/1yeA==
dependencies:
"@babel/helper-annotate-as-pure" "^7.29.7"
"@babel/helper-create-class-features-plugin" "^7.29.7"
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-skip-transparent-expression-wrappers" "^7.29.7"
"@babel/plugin-syntax-typescript" "^7.29.7"
"@babel/helper-annotate-as-pure" "^8.0.0"
"@babel/helper-create-class-features-plugin" "^8.0.1"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-skip-transparent-expression-wrappers" "^8.0.0"
"@babel/plugin-syntax-typescript" "^8.0.1"
"@babel/preset-react@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.29.7.tgz#2ed18366e38c2081bbf1760dc01e88fa5674eb17"
integrity sha512-C+PV1TFUPTmBQGoPBL8j2QmLpZ117YTCwxIZeJOM96GbYMFSc7/pOXU5lVykwnZxyTqQxRsvoRk6f2FktZgGHA==
"@babel/preset-react@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-8.0.1.tgz#0cb1802ff37a96faf983b5e781270f2752bb3476"
integrity sha512-jrFuPp/pTddFZbtmWhdLNAYc6UMcpboeUPnw0BBrm4nOmcAko/1TRcFi1PzWCeOFRU+VaSiKmat87W1HvR7mIg==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
"@babel/plugin-transform-react-display-name" "^7.29.7"
"@babel/plugin-transform-react-jsx" "^7.29.7"
"@babel/plugin-transform-react-jsx-development" "^7.29.7"
"@babel/plugin-transform-react-pure-annotations" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-validator-option" "^8.0.0"
"@babel/plugin-transform-react-display-name" "^8.0.1"
"@babel/plugin-transform-react-jsx" "^8.0.1"
"@babel/plugin-transform-react-jsx-development" "^8.0.1"
"@babel/plugin-transform-react-pure-annotations" "^8.0.1"
"@babel/preset-typescript@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz#de9be1f47b785c979ec7b3a71f4cd8bae5267b62"
integrity sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==
"@babel/preset-typescript@^8.0.1":
version "8.0.1"
resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-8.0.1.tgz#603e709db117e5fafef296de087e8a0a7679a543"
integrity sha512-qrPhQIN1NLrPmzgazF9XKQqXrOcp/WJly+K+6ReFonn24FZqRJO7clxOJo6Ni75L+2vAqI3cHVU2OJLBxoPp5A==
dependencies:
"@babel/helper-plugin-utils" "^7.29.7"
"@babel/helper-validator-option" "^7.29.7"
"@babel/plugin-syntax-jsx" "^7.29.7"
"@babel/plugin-transform-modules-commonjs" "^7.29.7"
"@babel/plugin-transform-typescript" "^7.29.7"
"@babel/helper-plugin-utils" "^8.0.1"
"@babel/helper-validator-option" "^8.0.0"
"@babel/plugin-transform-modules-commonjs" "^8.0.1"
"@babel/plugin-transform-typescript" "^8.0.1"
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.29.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.20.13", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.29.7.tgz#12022450c45a4da6d8d8287b18a4ff2ddb23f768"
integrity sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==
"@babel/runtime@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-8.0.0.tgz#d7bd513e6843662346552c2798ab895716cf97f2"
integrity sha512-sL6cvO2IfkSu/iU+zs2S/w01B7A8V7suXSIKEN4hPFFdZoiPGxrj5pAG0lCaqLWiEIrjKzdznIWuaLcxPR53qw==
"@babel/template@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700"
@@ -334,6 +467,15 @@
"@babel/parser" "^7.29.7"
"@babel/types" "^7.29.7"
"@babel/template@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-8.0.0.tgz#18c12626b0d6d23a0443771655864ed7566a3452"
integrity sha512-eAD0QW/AlbamBbw0FeGiwasbCVPq5ncW0HNVyLP3B9czqLyh4gvw+5JTSNt6le9+ziAU7mqDZsKTHf3jTb4chQ==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/types" "^8.0.0"
"@babel/traverse@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d"
@@ -347,6 +489,19 @@
"@babel/types" "^7.29.7"
debug "^4.3.1"
"@babel/traverse@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-8.0.0.tgz#0473589140c796d13e27641ab0783069f0fe7512"
integrity sha512-bxTj/W2VclGE6CctlfQOpxg8MPDzXArRqkOBePw8EHfebcjF7fETWSS3BriEECo+UiU/Yblq+xUtSImFu7cTbw==
dependencies:
"@babel/code-frame" "^8.0.0"
"@babel/generator" "^8.0.0"
"@babel/helper-globals" "^8.0.0"
"@babel/parser" "^8.0.0"
"@babel/template" "^8.0.0"
"@babel/types" "^8.0.0"
obug "^2.1.1"
"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.21.2", "@babel/types@^7.28.2", "@babel/types@^7.29.7":
version "7.29.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92"
@@ -355,6 +510,14 @@
"@babel/helper-string-parser" "^7.29.7"
"@babel/helper-validator-identifier" "^7.29.7"
"@babel/types@^8.0.0":
version "8.0.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-8.0.0.tgz#b518f1ef7f9838bffdca4e123b449d3f644f4494"
integrity sha512-K8ponJDxBwDHigkeFqaqT5wLGl4bTlwMafR8k7b5CPxr6Ww+UG9ls8Yx6Tcpboxu97eeGVEEyKcHmEyOwN1vSw==
dependencies:
"@babel/helper-string-parser" "^8.0.0"
"@babel/helper-validator-identifier" "^8.0.0"
"@codecov/bundler-plugin-core@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@codecov/bundler-plugin-core/-/bundler-plugin-core-2.0.1.tgz#bdbeb575339a25b330178154eee6df9dcb01f31e"
@@ -731,9 +894,9 @@
integrity sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==
"@flakiness/playwright@^1.13.0":
version "1.16.0"
resolved "https://registry.yarnpkg.com/@flakiness/playwright/-/playwright-1.16.0.tgz#55c30aa8a82196615bfb58556a9213c92c28657f"
integrity sha512-w1mEHd9UyyhfaZtd1ptschPR/C1rUlv1hjJvein9GRsL81vRyObSz7E4t1fNljDrJIymaEQypBLMM1d/Qqm/0A==
version "1.17.0"
resolved "https://registry.yarnpkg.com/@flakiness/playwright/-/playwright-1.17.0.tgz#37c949cef8a20c7252ea236458bbdf3f672a464a"
integrity sha512-RJcCWinnkhJoEPfDZZmUF+v1DP2ZyqEF4TpdoDoHrrpbI1EhcH0qjXd79QU1sxI+ST5+Q+U+e+Vu3H+1CXk9Xg==
"@floating-ui/core@^1.7.5":
version "1.7.5"
@@ -1220,9 +1383,9 @@
"@octokit/types" "^16.0.0"
"@octokit/request@^10.0.6", "@octokit/request@^10.0.7":
version "10.0.10"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.10.tgz#45e46934f3d772f006733be6b5ec18f22e54a00c"
integrity sha512-KxNC2pTqqhszMNrf12ZRd4PonRgyJdsM4F/jySiddQK+DsRcfBtUvqn8t7UsyZhnRJHvX46OohDt5N3VqIWC2w==
version "10.0.11"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-10.0.11.tgz#a11d8b9bae5f6e868efb7dea946edd855ebb2549"
integrity sha512-+s7HUxjfFqOMS9VlIwDffq0MikjSAK0gSpG73W+meAvVAvX4MBrHYTK5Bj3Uot55qFT4gzUtfzE4mGWY4Br8/Q==
dependencies:
"@octokit/endpoint" "^11.0.3"
"@octokit/request-error" "^7.0.2"
@@ -1401,59 +1564,59 @@
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz#3671ce3f9b928d5c01f879792d5c0b60ae14d4ad"
integrity sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==
"@sentry/browser-utils@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/browser-utils/-/browser-utils-10.62.0.tgz#b75ff44f77980744bafd2fa94642c00c23b84c2b"
integrity sha512-mS9HVVuWIdye9o0xUGFmzNOBqktF4n5kugrF8NCOYYDrr5ZV8Cx7BlquHQn5UpCeViVhZtcDlEm4iOK7++Px7A==
"@sentry/browser-utils@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/browser-utils/-/browser-utils-10.63.0.tgz#1cc79923d35be2213d7f7d872642f3fda9fa5f15"
integrity sha512-DhUGNN+CH8fzAs6qAsueKPU70qShyTX3NxLhIP+l5DbGXDSXpYXBT6s8ubZus0/LhxpLvI0iSyNIDvZRD/gZaA==
dependencies:
"@sentry/core" "10.62.0"
"@sentry/core" "10.63.0"
"@sentry/browser@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-10.62.0.tgz#9bf41b5cfaafcdf75970475a72a883c03bde76a7"
integrity sha512-uJi0yPssB3Nt/cZ8/S8opW42gaM59/6IyNtPFYD7C0ciudi/nIo5QMVpCYBBI3jnKFOIQLlsMT4pDlOLuxxNuQ==
"@sentry/browser@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-10.63.0.tgz#145400e655bb524561d5503c9f211d32ad3e876e"
integrity sha512-0mi56YOkwgyjdLOcN5cB1//EcYzEOt3NZ2GLygE92B3zAAwVM1WgbmibZCXToKFClH7z1uH3VWVfBffmkwIMYw==
dependencies:
"@sentry/browser-utils" "10.62.0"
"@sentry/core" "10.62.0"
"@sentry/feedback" "10.62.0"
"@sentry/replay" "10.62.0"
"@sentry/replay-canvas" "10.62.0"
"@sentry/browser-utils" "10.63.0"
"@sentry/core" "10.63.0"
"@sentry/feedback" "10.63.0"
"@sentry/replay" "10.63.0"
"@sentry/replay-canvas" "10.63.0"
"@sentry/core@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.62.0.tgz#73a4a8bcdded6d741d6cf58bfd055c1b60b3fe10"
integrity sha512-tV69fMg2sS5DUFmQSnS7Jd5qJAp0izxwcsvBVz2ieTM9VMRi99IfOSYW9UYr3p1yfuksk41kefN5PEbeedUE+A==
"@sentry/core@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/core/-/core-10.63.0.tgz#8c37016a70c25f22258f7cb3c0f71f931a78fd4e"
integrity sha512-OtUbsrnbEHffOF2S2+M5zXa3HIM0U2b4CDVLKMY1dgS0J3ivRF8XvkjvyIcEG/y8JXnwXbnprLyjhG+AqMdUZQ==
"@sentry/feedback@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/feedback/-/feedback-10.62.0.tgz#440bfb5690be9c70ec1edb2291a08ed3f98c8d36"
integrity sha512-d0BVjJVny6qpBgGJgWL0fbcoQHjtD3z3R8EK/KzTS3RO92JX5n3A536n5D/rh0gZFgcIwiUzBXegmyPOSQn9ng==
"@sentry/feedback@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/feedback/-/feedback-10.63.0.tgz#030801b9655fd0d3beadf26e12db286bfab615ff"
integrity sha512-If/+72xFg9ylz4twUo3U9gUpZ+Ys+T/3Y09WH7r2gGhWEOF9bp+ta94+Pg7Lb0M2nVD7waz4OxIvB49GEvtLDA==
dependencies:
"@sentry/core" "10.62.0"
"@sentry/core" "10.63.0"
"@sentry/react@^10.57.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-10.62.0.tgz#0ea654b9f7080d183b5b223ac2295fe3449b5a0d"
integrity sha512-PChimVpY0wzs3H/hJqyl87/ITTHwIZWTSY68QoENZyLnp7DvLcFiZYub/gFws1pzDPhtIQXVLU72fbmUjT5PSg==
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/react/-/react-10.63.0.tgz#f17b6c505ba819d7527ce009543922bf434c6a72"
integrity sha512-+/Y0dd4EMqyqYBJ1D3bAYYuG+ccIx5+IFcbTZ9p+XWnW6nNIVjy5zttVftYo6xOmtTQbzRuPT/vO4dqDHKKmfw==
dependencies:
"@sentry/browser" "10.62.0"
"@sentry/core" "10.62.0"
"@sentry/browser" "10.63.0"
"@sentry/core" "10.63.0"
"@sentry/replay-canvas@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/replay-canvas/-/replay-canvas-10.62.0.tgz#0ce22ef5786b7d012c3003dc2289d86d12ea1dc0"
integrity sha512-CzPAxmpe5US/ABGA1TzpjFKOFZN5uqlzrRh/uM9/daVuzLVKIAQ0XRNxo/PPEXvlDm/PoMdI5L0qIODuIKnyyw==
"@sentry/replay-canvas@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/replay-canvas/-/replay-canvas-10.63.0.tgz#754631d8c0773914847f59f97e8fe166ecbe876d"
integrity sha512-1Dg6yo+KDNZcE9M6V2EP4DGgTDJMcUgg5ui69w/E96ZZPWErS/bibK2bGj20H3qwpJXlnEwXB5YAJ2fZ620T1A==
dependencies:
"@sentry/core" "10.62.0"
"@sentry/replay" "10.62.0"
"@sentry/core" "10.63.0"
"@sentry/replay" "10.63.0"
"@sentry/replay@10.62.0":
version "10.62.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-10.62.0.tgz#66455f7c02b227244b02a78523b7578d339531de"
integrity sha512-rWp4hBhZOmdQhisxcKzAwTGiRk/LvWnNaElWe7nbRhjsM/usp2095yfjq4iJ47v9MtO7xxY6eUz++fLBycqXKg==
"@sentry/replay@10.63.0":
version "10.63.0"
resolved "https://registry.yarnpkg.com/@sentry/replay/-/replay-10.63.0.tgz#014c1be7110eb86d6e6fe148c691130094539460"
integrity sha512-u4fDaLbd4QmJbU0qGzV5g2B2hjw5utdeZzpTrmq565AS5o6mfaZdCz30zF9R2Unkn0g9SJr90piTN2RMwvDrkw==
dependencies:
"@sentry/browser-utils" "10.62.0"
"@sentry/core" "10.62.0"
"@sentry/browser-utils" "10.63.0"
"@sentry/core" "10.63.0"
"@sinclair/typebox@^0.27.8":
version "0.27.10"
@@ -1783,6 +1946,11 @@
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24"
integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==
"@types/gensync@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@types/gensync/-/gensync-1.0.5.tgz#1819eba537d036dcf4ec7e8c8f15fbfe938e99a0"
integrity sha512-MbsRCT7mTikHwKZ0X+LVUTLRrZZRLipTuXEO9qOYO+zmjMVk81axyClMROf6uoPD9MRVu46bx8zoR0Ad9q3NAg==
"@types/history@^4.7.11":
version "4.7.11"
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.11.tgz#56588b17ae8f50c53983a524fc3cc47437969d64"
@@ -1807,10 +1975,15 @@
dependencies:
"@types/istanbul-lib-report" "*"
"@types/jsesc@^2.5.0":
version "2.5.1"
resolved "https://registry.yarnpkg.com/@types/jsesc/-/jsesc-2.5.1.tgz#c34defc608ec94b68dc6a12a581b440942c6d503"
integrity sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==
"@types/node@*":
version "26.0.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.0.1.tgz#4a60e2c7a6d68bd261e265f8983bfe1601263ce3"
integrity sha512-fc3KiUoBt6kie0N9bIW3E47vZsuaMf0PM2AaUpLCLT0s/LvX1nxAim6Fc049cNxODPpGm6qRAuUOB86SkRuPQw==
version "26.1.0"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.0.tgz#aa85f0727fc5611347091c478341c63650903439"
integrity sha512-O0A1G3xPGy4w7AgQdAQYUlQ+BKk2Oovw8eRpofyp5KdBZULnbe+WqaOVNrm705SHphCiG4XHsACrSmPu1f+Kgw==
dependencies:
undici-types "~8.3.0"
@@ -1969,9 +2142,9 @@
vite-node "^3.2.2 || ^5.0.0 || ^6.0.0"
"@vanilla-extract/css@^1.20.1":
version "1.21.0"
resolved "https://registry.yarnpkg.com/@vanilla-extract/css/-/css-1.21.0.tgz#cb73abc0b25b7f28cd629b9ae386b010d5b5cf30"
integrity sha512-lqdRtP622Z85RprHlJemV5+ipdi+g48J115LaL8nrI64iixIp4SWPlvAEPf3o9pwEZaZPb5/ZfRwiXLE4p3+kQ==
version "1.21.1"
resolved "https://registry.yarnpkg.com/@vanilla-extract/css/-/css-1.21.1.tgz#11ddf605b85279511cff801c571ab816cae0681f"
integrity sha512-T6z6oeT+o0OwqukE6kLs9w5ooB75b8NPRadyU9pPTml83l40TPN+mscPt2OQ9P9cYfwEBMfsfivwiJujs2mzmg==
dependencies:
"@emotion/hash" "^0.9.0"
"@vanilla-extract/private" "^1.0.9"
@@ -2014,7 +2187,7 @@
"@vanilla-extract/compiler" "^0.7.0"
"@vanilla-extract/integration" "^8.0.10"
"@vitejs/plugin-react@^5.2.0":
"@vitejs/plugin-react@^5":
version "5.2.0"
resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz#108bd0f566f288ce3566982df4eff137ded7b15f"
integrity sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==
@@ -2159,9 +2332,9 @@ base64-js@^1.3.1:
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.38:
version "2.10.40"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz#f372c8eb36ff4ad0b5e7ae467014abef124554ba"
integrity sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==
version "2.10.41"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.41.tgz#86738e4aafb4392a32672642ddd092c0c2f80161"
integrity sha512-WwS7MHhqGHHlaVsqRZnhvCEMS0owDX+SxRlve7JkuH7My1Ara3ZriTmCQupPfYjxMZ8I/tgxtJYr2t7taHaH4A==
before-after-hook@^4.0.0:
version "4.0.0"
@@ -2259,9 +2432,9 @@ camelize@^1.0.0:
integrity sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==
caniuse-lite@^1.0.30001799:
version "1.0.30001799"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz#5c909138c27f1a61219d3e092071c1cc7d32dc55"
integrity sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==
version "1.0.30001800"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz#b896c773e1c39400809415162bb5320371291b36"
integrity sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==
chalk@4.1.2, chalk@^4.0.0, chalk@^4.1.0:
version "4.1.2"
@@ -2637,9 +2810,9 @@ dunder-proto@^1.0.1:
gopd "^1.2.0"
electron-to-chromium@^1.5.376:
version "1.5.381"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.381.tgz#037549001adc80e04834ede96dc52a1fbf6c9fde"
integrity sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==
version "1.5.385"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.385.tgz#fbb900d4ddde6fab4651f37de6c1659a070bc68d"
integrity sha512-78sa/M08MNAYHQfjoWMvOlKQqZ0ElhSm/L5HNUc96VZ3b+KvDVnngFm8sYQy0XrhTRgAhggHr5abA7yTvRdo4Q==
embla-carousel-react@^8.6.0:
version "8.6.0"
@@ -2664,6 +2837,11 @@ emoji-regex@^8.0.0:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
empathic@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/empathic/-/empathic-2.0.1.tgz#37b1bede31093e04a03d2abce95ea323fee8ef49"
integrity sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==
error-ex@^1.3.1:
version "1.3.4"
resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.4.tgz#b3a8d8bb6f92eecc1629e3e27d3c8607a8a32414"
@@ -2687,9 +2865,9 @@ es-module-lexer@^0.4.1:
integrity sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==
es-module-lexer@^2.0.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.2.0.tgz#fe824f12e3f28bde741bb572b5a182786eaf3764"
integrity sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==
version "2.3.0"
resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-2.3.0.tgz#fda770234c345064c122eb905e1c4200ffa4ce7e"
integrity sha512-KLdwQm2NvGLDkQDCGvmiQrhkd0JbMzXthwQAUgWjQuQdBLFa3eiBP5arXZyA+f8x+x7OXgud6bq2rxjGtHV2tw==
es-object-atoms@^1.0.0, es-object-atoms@^1.1.1:
version "1.1.2"
@@ -3064,15 +3242,10 @@ ieee754@^1.1.13:
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==
immer@^10.1.1:
version "10.2.0"
resolved "https://registry.yarnpkg.com/immer/-/immer-10.2.0.tgz#88a4ce06a1af64172d254b70f7cb04df51c871b1"
integrity sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==
immer@^11.0.0:
version "11.1.8"
resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.8.tgz#08a6426f7019dbce8d6dff8c4a43bb25c550a575"
integrity sha512-/tbkHMW7y10Lx6i1crLjD4/OhNkRG+Fo7byZHtah0547nIeXYcpIXaUh0IAQY6gO5459qpGGYapcEOHtFXkIuA==
immer@^11.0.0, immer@^11.1.8:
version "11.1.9"
resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.9.tgz#e0ce69359d29cafdb14fc5b6d2cd56f826097966"
integrity sha512-sc/z0Cyti70bZa0ZU4sWfAElfovFb9Ni8tArJZLuklYWxegPiK3pDOql1Rq5H0FIRAW9LSQRG6OX4KqBldbhBA==
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.1"
@@ -3082,6 +3255,11 @@ import-fresh@^3.2.1, import-fresh@^3.3.0:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-meta-resolve@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz#08cb85b5bd37ecc8eb1e0f670dc2767002d43734"
integrity sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@@ -3275,6 +3453,11 @@ js-sha256@^0.10.1:
resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.10.1.tgz#b40104ba1368e823fdd5f41b66b104b15a0da60d"
integrity sha512-5obBtsz9301ULlsgggLg542s/jqtddfOpV5KJc4hajc9JV8GeY2gZHSVpYBn4nWqAUTJ9v+xwtbJ1mIBgIH5Vw==
js-tokens@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-10.0.0.tgz#dffe7599b4a8bb7fe30aff8d0235234dffb79831"
integrity sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -3771,9 +3954,9 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
integrity sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==
picomatch@^4.0.2, picomatch@^4.0.3, picomatch@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.4.tgz#fd6f5e00a143086e074dffe4c924b8fb293b0589"
integrity sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==
version "4.0.5"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.5.tgz#51ea57a17d86f605f81039595fbc40ed06a55fab"
integrity sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==
pkg-dir@^4.1.0:
version "4.2.0"
@@ -3978,9 +4161,9 @@ prosemirror-transform@^1.0.0, prosemirror-transform@^1.1.0, prosemirror-transfor
prosemirror-model "^1.21.0"
prosemirror-view@^1.0.0, prosemirror-view@^1.1.0, prosemirror-view@^1.27.0, prosemirror-view@^1.31.0, prosemirror-view@^1.41.4, prosemirror-view@^1.41.8:
version "1.41.9"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.41.9.tgz#e756493e7116f7df7c73aaf050f60871cab42100"
integrity sha512-clTunTX+eaLbr87L1V1QPheRlEQJyTlL3gXe9x3jQIk3rL0RVWxviDGz8tFaydwIVm+hKhYCyr+R/zBtWr9s6A==
version "1.42.0"
resolved "https://registry.yarnpkg.com/prosemirror-view/-/prosemirror-view-1.42.0.tgz#82ed0823a1bf971d27cb647f3a23d5e005d04c00"
integrity sha512-N54DF3OXNWDuP81G1kbfCys8ZzIjuL1VnvJ2mk5STSu/fNxWIcX/EutQLA3s9KR/2wVhgDi4hzBB/1fINVxk0A==
dependencies:
prosemirror-model "^1.25.8"
prosemirror-state "^1.0.0"
@@ -4191,16 +4374,16 @@ readdirp@~3.5.0:
picomatch "^2.2.1"
recharts@^3.8.1:
version "3.9.0"
resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.9.0.tgz#b78a4822d1b1805a7529a9e8a82f7a043e94c3c9"
integrity sha512-dCEcE9y20c8H2tkVeByrAXhhnBJk6/QLbxKmn+dJUptOfc5NMjwRh1jo0vZPRLD+5dMrHrP+hPEsfbGBMfnf5Q==
version "3.9.1"
resolved "https://registry.yarnpkg.com/recharts/-/recharts-3.9.1.tgz#6fe1b4617147e2016fa6f4cd681c8c818255955e"
integrity sha512-WMcwlXcB7l+BbxiEdyClkG+1sxrMHNZpzT577LEvU4+rXPd8oTAy1wXk72hnk2KOOmxuLvw3z5DtXT7HEAydtg==
dependencies:
"@reduxjs/toolkit" "^1.9.0 || 2.x.x"
clsx "^2.1.1"
decimal.js-light "^2.5.1"
es-toolkit "^1.39.3"
eventemitter3 "^5.0.1"
immer "^10.1.1"
immer "^11.1.8"
react-redux "8.x.x || 9.x.x"
reselect "5.2.0"
tiny-invariant "^1.3.3"
@@ -4353,7 +4536,7 @@ semver@^6.0.0, semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4:
semver@^7.5.3, semver@^7.5.4, semver@^7.7.3:
version "7.8.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.8.5.tgz#39b646037dd50c14fb451e7e4cac58ed8b863f69"
integrity sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==
@@ -4654,9 +4837,9 @@ undici@^6.23.0, undici@^6.24.0:
integrity sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==
undici@^8.4.1:
version "8.5.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-8.5.0.tgz#31ba9021a3d84c15e61cc5e28be2d7c451e66a66"
integrity sha512-xamtWoB1EshgjpmlXd7GGm2VfdDtw1+rD8uhry8pSNW3If6S8E0m2T2+orSKeZXEn/aPJMviCpDBA65WJt8zhg==
version "8.6.0"
resolved "https://registry.yarnpkg.com/undici/-/undici-8.6.0.tgz#1595d019e47c8c9c3c62ce22685fb4b6c094f4ea"
integrity sha512-l2FlC6I510GawyEd1qgcE/okihKrzy+BRTEBlu6T0fdbM9m5yxtIH5Oa3ysRsH0zC4EhmWUEaSDsy2QngBeRlw==
universal-user-agent@^7.0.0, universal-user-agent@^7.0.2:
version "7.0.3"