Compare commits

..
2 Commits
Author SHA1 Message Date
khaccount de8e3f55df Dev Commit 2026-07-09 10:10:05 +02:00
o-rascheandClaude Opus 4.8 bb7c03235d Checkpoint: headless JSON architecture documented + cleanup (Stufe 1+2)
Restore point before Stufe 3 (central resolver-service refactoring).

Includes this session's work:
- Success Story (usecase) rebuild: new fields/tabs, UsecaseSerializer,
  thin List/Show renderers, MM relations, inline content elements.
- vitec_columns content block (two-column layout with per-item content)
  incl. unified header section; special-cased JSON resolution.
- Container per-column flex (items[].config align/justify) + gap on parent.
- Unified header section across CEs/plugins/containers; header fields in JSON.
- ISO-style architecture spec: Documentation/Headless-JSON-Architecture.md.
- Cleanup: removed local scratch + verified .bak backups.
- Fixes: B-6 (undefined thumbnail variable in Downloadcardcollection image
  resolver), B-7 (unsatisfiable legacy CType OR branch in Downloadcard/Datasheets).

Rollback: git reset --hard snapshot-2026-07-09-pre-stufe3

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 10:01:56 +02:00
197 changed files with 6639 additions and 1690 deletions

No files matched your search

+1
View File
@@ -20,6 +20,7 @@
"evomedien/vitec": "^1.0.1", "evomedien/vitec": "^1.0.1",
"friendsoftypo3/content-blocks": "^2.4", "friendsoftypo3/content-blocks": "^2.4",
"friendsoftypo3/headless": "^5.0@rc", "friendsoftypo3/headless": "^5.0@rc",
"georgringer/news": "^14.0",
"netzbewegung/nb-headless-content-blocks": "^0.0.23", "netzbewegung/nb-headless-content-blocks": "^0.0.23",
"typo3/cms-backend": "^14.3", "typo3/cms-backend": "^14.3",
"typo3/cms-belog": "^14.3", "typo3/cms-belog": "^14.3",
Generated
+513 -413
View File
File diff suppressed because it is too large. Load diff
+20
View File
@@ -15,6 +15,11 @@ dependencies:
- vitec/card - vitec/card
- vitec/intro-paragraph - vitec/intro-paragraph
- vitec/video - vitec/video
- vitec/faq
- georgringer/news
- georgringer/news-sitemap
- georgringer/news-recordlinks
- vitec/columns
frontendBase: '' frontendBase: ''
headless: 1 headless: 1
languages: languages:
@@ -49,4 +54,19 @@ routeEnhancers:
tableName: tx_vitec_domain_model_product tableName: tx_vitec_domain_model_product
routeFieldName: slug routeFieldName: slug
routeValuePrefix: '' routeValuePrefix: ''
NewsDetail:
type: Extbase
limitToPages:
- 21
extension: News
plugin: Pi1
routes:
- routePath: '/{news-title}'
_controller: 'News::detail'
_arguments:
news-title: news
defaultController: 'News::detail'
aspects:
news-title:
type: NewsTitle
websiteTitle: '' websiteTitle: ''
+30
View File
@@ -0,0 +1,30 @@
#!/bin/bash
# =============================================================================
# VITEC — fix backend asset permissions after a deploy via the Windows sshfs mount
# -----------------------------------------------------------------------------
# sshfs-win (WinFsp) writes newly deployed files with mode 770 (no other-read).
# The webserver runs as "other" (suexec) -> assets under Resources/Public and
# public/_assets return HTTP 403 (backend icons / CSS / JS missing).
#
# chmod OVER the Windows mount does NOT change the real server mode, so this
# has to run ON THE SERVER, via real SSH, as the site owner (vitecevo).
#
# Usage (from the live/ project root on the server):
# bash fix-asset-perms.sh
# =============================================================================
set -u
cd "$(dirname "$0")" || exit 1
echo "[perms] Project root: $(pwd)"
echo "[perms] Setting files 644 / directories 755 under Resources/Public + _assets ..."
find public/_assets packages/vitec/Resources/Public -type f -exec chmod 644 {} \; 2>/dev/null
find public/_assets packages/vitec/Resources/Public -type d -exec chmod 755 {} \; 2>/dev/null
echo "[perms] Flushing TYPO3 caches ..."
if [ -x vendor/bin/typo3 ]; then
vendor/bin/typo3 cache:flush
else
echo "[perms] (vendor/bin/typo3 not found — skip cache flush)"
fi
echo "[perms] Done."
+39
View File
@@ -0,0 +1,39 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Controller;
use Evomedien\Vitec\Domain\Repository\EventRepository;
use Psr\Http\Message\ResponseInterface;
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
/**
* This file is part of the "VITEC" Extension for TYPO3 CMS.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* (c) 2026
*/
/**
* EventController — Extbase registration target for the Eventlist plugin.
* In headless mode the JSON output is produced by EventlistJsonRenderer,
* not by this controller.
*/
class EventController extends ActionController
{
protected EventRepository $eventRepository;
public function __construct(EventRepository $eventRepository)
{
$this->eventRepository = $eventRepository;
}
public function listAction(): ResponseInterface
{
$this->view->assign('events', $this->eventRepository->findUpcoming());
return $this->htmlResponse();
}
}
@@ -12,6 +12,8 @@ use Evomedien\Vitec\UserFunc\SolutionShowJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer; use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer; use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer;
use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer; use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer;
use Evomedien\Vitec\UserFunc\EventlistJsonRenderer;
use Evomedien\Vitec\UserFunc\NewsJsonRenderer;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -20,9 +22,13 @@ use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
/** /**
* Collect container children grouped by colPos and emit a lean, transport- * Collect container children grouped by colPos and emit a lean, transport-
* ready structure. Each child is normalised to: * ready structure. Each column is normalised to:
* { id, type, colPos, sorting, appearance, data } * { config: { colPos, align, justify }, contentElements: [ { id, type, … } ] }
* `data` only contains non-empty content-relevant fields. * `align`/`justify` are the PARENT container's per-column flex settings
* (tx_vitec_col{N}_align/justify) and therefore apply to ALL children of that
* column. Each child is normalised to { id, type, colPos, sorting, appearance,
* data }, where `data` only contains non-empty content-relevant fields —
* container-level fields (gap, col flex) are never leaked to children.
* *
* VITEC list-plugins nested as container children (productlist, productshow, * VITEC list-plugins nested as container children (productlist, productshow,
* usecaselist, usecaseshow) are resolved to the SAME JSON the top-level * usecaselist, usecaseshow) are resolved to the SAME JSON the top-level
@@ -59,6 +65,30 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
'uploads_description', 'uploads_type', 'uploads_description', 'uploads_type',
]; ];
/**
* Container-level fields — they belong on the parent container (grid gap
* and per-column flex) and must never appear in a child's `data`.
*/
private const CONTAINER_FIELDS = [
'tx_vitec_gap',
'tx_vitec_col1_align', 'tx_vitec_col1_justify',
'tx_vitec_col2_align', 'tx_vitec_col2_justify',
'tx_vitec_col3_align', 'tx_vitec_col3_justify',
'tx_vitec_col4_align', 'tx_vitec_col4_justify',
];
/**
* Per container CType: colPos value => 1-based column number. Used to pick
* the parent's tx_vitec_col{N}_align/justify for each rendered column.
*/
private const COLPOS_TO_COLUMN = [
'vitec_cols_50_50' => [211 => 1, 212 => 2],
'vitec_cols_33_66' => [251 => 1, 252 => 2],
'vitec_cols_66_33' => [241 => 1, 242 => 2],
'vitec_cols_33_33_33' => [221 => 1, 222 => 2, 223 => 3],
'vitec_cols_25_25_25_25' => [231 => 1, 232 => 2, 233 => 3, 234 => 4],
];
/** Fields whose 0/empty value is still meaningful. */ /** Fields whose 0/empty value is still meaningful. */
private const KEEP_IF_ZERO = [ private const KEEP_IF_ZERO = [
'header_layout', 'header_layout',
@@ -80,6 +110,16 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
'vitec_downloadcard' => [DownloadcardJsonRenderer::class, 'downloadcard'], 'vitec_downloadcard' => [DownloadcardJsonRenderer::class, 'downloadcard'],
'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'], 'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'],
'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'], 'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'],
'vitec_eventlist' => [EventlistJsonRenderer::class, 'eventlist'],
'news_pi1' => [NewsJsonRenderer::class, 'news'],
'news_newsliststicky' => [NewsJsonRenderer::class, 'news'],
'news_newsselectedlist' => [NewsJsonRenderer::class, 'news'],
'news_newsdetail' => [NewsJsonRenderer::class, 'news'],
'news_newsdatemenu' => [NewsJsonRenderer::class, 'news'],
'news_categorylist' => [NewsJsonRenderer::class, 'news'],
'news_newssearchform' => [NewsJsonRenderer::class, 'news'],
'news_newssearchresult' => [NewsJsonRenderer::class, 'news'],
'news_taglist' => [NewsJsonRenderer::class, 'news'],
]; ];
public function process( public function process(
@@ -121,10 +161,20 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
} }
ksort($byColPos); ksort($byColPos);
// Per-column flex settings come from the parent container record.
$parentCType = (string)($cObj->data['CType'] ?? '');
$colMap = self::COLPOS_TO_COLUMN[$parentCType] ?? [];
$items = []; $items = [];
foreach ($byColPos as $colPos => $contentElements) { foreach ($byColPos as $colPos => $contentElements) {
$config = ['colPos' => $colPos];
$columnNo = $colMap[$colPos] ?? null;
if ($columnNo !== null) {
$config['align'] = (string)($cObj->data['tx_vitec_col' . $columnNo . '_align'] ?? 'stretch');
$config['justify'] = (string)($cObj->data['tx_vitec_col' . $columnNo . '_justify'] ?? 'flex-start');
}
$items[] = [ $items[] = [
'config' => ['colPos' => $colPos], 'config' => $config,
'contentElements' => $contentElements, 'contentElements' => $contentElements,
]; ];
} }
@@ -147,6 +197,9 @@ final class ContainerChildrenProcessor implements DataProcessorInterface
if (in_array($field, self::SYSTEM_FIELDS, true)) { if (in_array($field, self::SYSTEM_FIELDS, true)) {
continue; continue;
} }
if (in_array($field, self::CONTAINER_FIELDS, true)) {
continue;
}
if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) { if ($this->isEmpty($value) && !in_array($field, self::KEEP_IF_ZERO, true)) {
continue; continue;
} }
+266
View File
@@ -0,0 +1,266 @@
<?php
namespace Evomedien\Vitec\Domain\Model;
use TYPO3\CMS\Extbase\Domain\Model\Category;
use TYPO3\CMS\Extbase\Domain\Model\FileReference;
use TYPO3\CMS\Extbase\DomainObject\AbstractEntity;
use TYPO3\CMS\Extbase\Persistence\ObjectStorage;
class Event extends AbstractEntity
{
/** @var string */
protected $title = '';
/** @var string */
protected $slug = '';
/** @var string */
protected $teaser = '';
/** @var string */
protected $description = '';
/** @var \DateTime|null */
protected $eventstart;
/** @var \DateTime|null */
protected $eventend;
/** @var string */
protected $venue = '';
/** @var string */
protected $booth = '';
/** @var string */
protected $city = '';
/** @var string */
protected $country = '';
/** @var string */
protected $attendancemode = 'offline';
/** @var string */
protected $eventstatus = 'scheduled';
/** @var string */
protected $eventurl = '';
/** @var string */
protected $meetinglink = '';
/**
* @var \TYPO3\CMS\Extbase\Domain\Model\FileReference|null
* @TYPO3\CMS\Extbase\Annotation\ORM\Cascade("remove")
*/
protected $image;
/** @var bool */
protected $hideonwebsite = false;
/** @var bool */
protected $hideonapp = false;
/**
* @var \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\TYPO3\CMS\Extbase\Domain\Model\Category>
* @TYPO3\CMS\Extbase\Annotation\ORM\Lazy
*/
protected $categories;
public function __construct()
{
$this->categories = new ObjectStorage();
}
public function getTitle(): string
{
return $this->title;
}
public function setTitle(string $title): void
{
$this->title = $title;
}
public function getSlug(): string
{
return $this->slug;
}
public function setSlug(string $slug): void
{
$this->slug = $slug;
}
public function getTeaser(): string
{
return $this->teaser;
}
public function setTeaser(string $teaser): void
{
$this->teaser = $teaser;
}
public function getDescription(): string
{
return $this->description;
}
public function setDescription(string $description): void
{
$this->description = $description;
}
public function getEventstart(): ?\DateTime
{
return $this->eventstart;
}
public function setEventstart(?\DateTime $eventstart): void
{
$this->eventstart = $eventstart;
}
public function getEventend(): ?\DateTime
{
return $this->eventend;
}
public function setEventend(?\DateTime $eventend): void
{
$this->eventend = $eventend;
}
public function getVenue(): string
{
return $this->venue;
}
public function setVenue(string $venue): void
{
$this->venue = $venue;
}
public function getBooth(): string
{
return $this->booth;
}
public function setBooth(string $booth): void
{
$this->booth = $booth;
}
public function getCity(): string
{
return $this->city;
}
public function setCity(string $city): void
{
$this->city = $city;
}
public function getCountry(): string
{
return $this->country;
}
public function setCountry(string $country): void
{
$this->country = $country;
}
public function getAttendancemode(): string
{
return $this->attendancemode;
}
public function setAttendancemode(string $attendancemode): void
{
$this->attendancemode = $attendancemode;
}
public function getEventstatus(): string
{
return $this->eventstatus;
}
public function setEventstatus(string $eventstatus): void
{
$this->eventstatus = $eventstatus;
}
public function getEventurl(): string
{
return $this->eventurl;
}
public function setEventurl(string $eventurl): void
{
$this->eventurl = $eventurl;
}
public function getMeetinglink(): string
{
return $this->meetinglink;
}
public function setMeetinglink(string $meetinglink): void
{
$this->meetinglink = $meetinglink;
}
public function getImage(): ?FileReference
{
return $this->image;
}
public function setImage(?FileReference $image): void
{
$this->image = $image;
}
public function getHideonwebsite(): bool
{
return $this->hideonwebsite;
}
public function setHideonwebsite(bool $hideonwebsite): void
{
$this->hideonwebsite = $hideonwebsite;
}
public function getHideonapp(): bool
{
return $this->hideonapp;
}
public function setHideonapp(bool $hideonapp): void
{
$this->hideonapp = $hideonapp;
}
public function addCategory(Category $category): void
{
$this->categories->attach($category);
}
public function removeCategory(Category $categoryToRemove): void
{
$this->categories->detach($categoryToRemove);
}
public function getCategories(): ObjectStorage
{
return $this->categories;
}
public function setCategories(ObjectStorage $categories): void
{
$this->categories = $categories;
}
}
@@ -0,0 +1,71 @@
<?php
namespace Evomedien\Vitec\Domain\Repository;
use TYPO3\CMS\Extbase\Persistence\QueryInterface;
use TYPO3\CMS\Extbase\Persistence\QueryResultInterface;
use TYPO3\CMS\Extbase\Persistence\Repository;
/**
* The repository for Event
*/
class EventRepository extends Repository
{
protected $defaultOrderings = [
'eventstart' => QueryInterface::ORDER_ASCENDING,
];
/**
* All upcoming (or currently running) events, soonest first.
* "Upcoming" = event end (or start, when no end is set) is today or later.
*/
public function findUpcoming(): QueryResultInterface
{
$todayMidnight = strtotime('today');
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->matching(
$query->logicalAnd(
$query->equals('hideonwebsite', 0),
$query->logicalOr(
$query->greaterThanOrEqual('eventend', $todayMidnight),
$query->logicalAnd(
$query->equals('eventend', 0),
$query->greaterThanOrEqual('eventstart', $todayMidnight)
)
)
)
);
return $query->execute();
}
/**
* Past events, most recent first.
*/
public function findPast(): QueryResultInterface
{
$todayMidnight = strtotime('today');
$query = $this->createQuery();
$query->getQuerySettings()->setRespectStoragePage(false);
$query->matching(
$query->logicalAnd(
$query->equals('hideonwebsite', 0),
$query->logicalOr(
$query->logicalAnd(
$query->greaterThan('eventend', 0),
$query->lessThan('eventend', $todayMidnight)
),
$query->logicalAnd(
$query->equals('eventend', 0),
$query->greaterThan('eventstart', 0),
$query->lessThan('eventstart', $todayMidnight)
)
)
)
);
$query->setOrderings(['eventstart' => QueryInterface::ORDER_DESCENDING]);
return $query->execute();
}
}
@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\EventListener;
use TYPO3\CMS\Backend\Controller\Event\ModifyPageLayoutContentEvent;
use TYPO3\CMS\Core\Attribute\AsEventListener;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Loads the container collapse/expand JS + CSS into the Page module.
* Pure additive DOM enhancement — see
* Resources/Public/JavaScript/container-collapse.js.
*/
#[AsEventListener(identifier: 'vitec/load-container-collapse-assets')]
final class LoadContainerCollapseAssets
{
public function __invoke(ModifyPageLayoutContentEvent $event): void
{
$pageRenderer = GeneralUtility::makeInstance(PageRenderer::class);
$pageRenderer->loadJavaScriptModule('@evomedien/vitec/container-collapse.js');
$pageRenderer->addCssFile(
'EXT:vitec/Resources/Public/Css/backend-container-collapse.css'
);
}
}
@@ -14,6 +14,8 @@ use Evomedien\Vitec\UserFunc\SolutionShowJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer; use Evomedien\Vitec\UserFunc\DownloadcardJsonRenderer;
use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer; use Evomedien\Vitec\UserFunc\DownloadcardcollectionJsonRenderer;
use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer; use Evomedien\Vitec\UserFunc\DatasheetsJsonRenderer;
use Evomedien\Vitec\UserFunc\EventlistJsonRenderer;
use Evomedien\Vitec\UserFunc\NewsJsonRenderer;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -73,6 +75,16 @@ final class ContentElementResolver
'vitec_downloadcard' => [DownloadcardJsonRenderer::class, 'downloadcard'], 'vitec_downloadcard' => [DownloadcardJsonRenderer::class, 'downloadcard'],
'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'], 'vitec_downloadcardcollection' => [DownloadcardcollectionJsonRenderer::class, 'downloadcardcollection'],
'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'], 'vitec_datasheets' => [DatasheetsJsonRenderer::class, 'datasheets'],
'vitec_eventlist' => [EventlistJsonRenderer::class, 'eventlist'],
'news_pi1' => [NewsJsonRenderer::class, 'news'],
'news_newsliststicky' => [NewsJsonRenderer::class, 'news'],
'news_newsselectedlist' => [NewsJsonRenderer::class, 'news'],
'news_newsdetail' => [NewsJsonRenderer::class, 'news'],
'news_newsdatemenu' => [NewsJsonRenderer::class, 'news'],
'news_categorylist' => [NewsJsonRenderer::class, 'news'],
'news_newssearchform' => [NewsJsonRenderer::class, 'news'],
'news_newssearchresult' => [NewsJsonRenderer::class, 'news'],
'news_taglist' => [NewsJsonRenderer::class, 'news'],
]; ];
/** /**
@@ -305,6 +305,148 @@ final class StructuredDataService
return $video; return $video;
} }
/**
* schema.org FAQPage from a list of question/answer pairs.
* Answers may contain RTE HTML — tags are stripped for the JSON-LD text.
*
* @param list<array{question:string,answer:string}> $items
*/
public function buildFaqPage(array $items, string $base): ?array
{
$mainEntity = [];
foreach ($items as $item) {
$question = trim((string)($item['question'] ?? ''));
$answer = trim(strip_tags((string)($item['answer'] ?? '')));
if ($question === '' || $answer === '') {
continue;
}
$mainEntity[] = [
'@type' => 'Question',
'name' => $question,
'acceptedAnswer' => [
'@type' => 'Answer',
'text' => $answer,
],
];
}
if ($mainEntity === []) {
return null;
}
return [
'@type' => 'FAQPage',
'mainEntity' => $mainEntity,
];
}
/**
* schema.org ExhibitionEvent (trade-show) node from a
* tx_vitec_domain_model_event row.
*
* Google rich-result requirements: name, startDate, location.
* Recommended extras handled here: endDate, description, image, url,
* eventStatus, eventAttendanceMode, organizer.
*
* @param array<string,mixed> $e Event DB row
* @param list<string> $imageUrls Absolute or root-relative logo URLs
*/
public function buildEvent(array $e, string $base, array $imageUrls, bool $hasOrganization): ?array
{
$name = trim((string)($e['title'] ?? ''));
$start = (int)($e['eventstart'] ?? 0);
if ($name === '' || $start === 0) {
return null;
}
$event = [
'@type' => 'ExhibitionEvent',
'name' => $name,
'startDate' => date('Y-m-d', $start),
];
$end = (int)($e['eventend'] ?? 0);
if ($end > 0) {
$event['endDate'] = date('Y-m-d', $end);
}
$description = trim(strip_tags((string)($e['description'] ?? '')));
if ($description === '') {
$description = trim((string)($e['teaser'] ?? ''));
}
if ($description !== '') {
$event['description'] = $description;
}
// Location: Place with venue + city/country, or VirtualLocation for online.
$mode = (string)($e['attendancemode'] ?? 'offline');
$venue = trim((string)($e['venue'] ?? ''));
$city = trim((string)($e['city'] ?? ''));
$country = trim((string)($e['country'] ?? ''));
if ($mode === 'online') {
$url = trim((string)($e['eventurl'] ?? ''));
if ($url !== '') {
$event['location'] = [
'@type' => 'VirtualLocation',
'url' => $this->absUrl($url, $base),
];
}
} else {
$address = array_filter([
'@type' => 'PostalAddress',
'addressLocality' => $city !== '' ? $city : null,
'addressCountry' => $country !== '' ? $country : null,
]);
$place = array_filter([
'@type' => 'Place',
'name' => $venue !== '' ? $venue : ($city !== '' ? $city : null),
'address' => count($address) > 1 ? $address : null,
]);
if (count($place) > 1) {
$event['location'] = $place;
}
}
$statusMap = [
'scheduled' => 'https://schema.org/EventScheduled',
'cancelled' => 'https://schema.org/EventCancelled',
'postponed' => 'https://schema.org/EventPostponed',
'rescheduled' => 'https://schema.org/EventRescheduled',
'movedonline' => 'https://schema.org/EventMovedOnline',
];
$event['eventStatus'] = $statusMap[(string)($e['eventstatus'] ?? 'scheduled')] ?? $statusMap['scheduled'];
$modeMap = [
'offline' => 'https://schema.org/OfflineEventAttendanceMode',
'online' => 'https://schema.org/OnlineEventAttendanceMode',
'mixed' => 'https://schema.org/MixedEventAttendanceMode',
];
$event['eventAttendanceMode'] = $modeMap[$mode] ?? $modeMap['offline'];
$images = [];
foreach ($imageUrls as $imageUrl) {
$abs = $this->absUrl((string)$imageUrl, $base);
if ($abs !== '') {
$images[] = $abs;
}
}
if ($images !== []) {
$event['image'] = $images;
}
$eventUrl = trim((string)($e['eventurl'] ?? ''));
if ($eventUrl !== '' && !str_starts_with($eventUrl, 't3://')) {
$event['url'] = $this->absUrl($eventUrl, $base);
}
if ($hasOrganization) {
$event['organizer'] = ['@id' => $base . '/#organization'];
}
return $event;
}
/** /**
* Wrap one or more schema nodes into a single @graph document string. * Wrap one or more schema nodes into a single @graph document string.
* *
+530
View File
@@ -0,0 +1,530 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\Service;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* Serialises a Success Story (tx_vitec_domain_model_usecase) DB row to the
* headless JSON shapes consumed by the React frontend.
*
* serializeListItem() -> lean card data for the list plugin
* serializeDetail() -> full detail (hero, content elements, related, seo)
*
* Inline content elements are resolved flat; the VITEC Columns content block
* (vitec_columns) is resolved specially into { header…, layout, columns:{…} }.
*
* All relation/FAL resolution lives here so the List/Show UserFunc renderers
* stay thin. Every resolver is exception-safe and returns null/[] on failure.
*/
final class UsecaseSerializer
{
private const TABLE = 'tx_vitec_domain_model_usecase';
private const IMAGE_WIDTHS = [400, 800, 1200, 1600];
private const COLUMNS_CTYPE = 'vitec_columns';
/**
* @param array<string,mixed> $u
* @return array<string,mixed>
*/
public function serializeListItem(array $u): array
{
$uid = (int)$u['uid'];
return [
'uid' => $uid,
'title' => (string)($u['title'] ?? ''),
'slug' => (string)($u['slug'] ?? ''),
'subtitle' => (string)($u['subtitle'] ?? ''),
'teaser' => (string)($u['teaser'] ?? ''),
'cardImage' => $this->image($uid, 'card_image'),
'customerLogo' => $this->image($uid, 'customer_logo'),
'market' => $this->market((int)($u['market'] ?? 0)),
'categories' => $this->categories($uid),
'featured' => (bool)($u['featured'] ?? false),
'layoutVariant' => (string)($u['layout_variant'] ?? 'standard'),
'backgroundVariant' => (string)($u['background_variant'] ?? 'none'),
];
}
/**
* @param array<string,mixed> $u
* @return array<string,mixed>
*/
public function serializeDetail(array $u): array
{
$uid = (int)$u['uid'];
$base = $this->serializeListItem($u);
return array_merge($base, [
'hero' => [
'bgImage' => $this->image($uid, 'hero_bgimage'),
'smallImage' => $this->image($uid, 'hero_small_image'),
'video' => $this->video($uid, 'hero_video'),
'overlayColor' => (string)($u['hero_overlay_color'] ?? ''),
'overlayOpacity' => (float)($u['hero_overlay_opacity'] ?? 0),
'layout' => (string)($u['hero_layout'] ?? 'fullscreen'),
'textTheme' => (string)($u['text_theme'] ?? 'light'),
],
'contentElements' => $this->contentElements($uid),
'related' => [
'show' => (bool)($u['show_related'] ?? true),
'market' => $base['market'],
'solutions' => $this->relationMulti('tx_vitec_usecase_solution_mm', 'tx_vitec_domain_model_solution', $uid),
'products' => $this->relationMulti('tx_vitec_usecase_product_mm', 'tx_vitec_domain_model_product', $uid),
'categories' => $base['categories'],
],
'seo' => $this->seo($u),
'appearance' => [
'layoutVariant' => (string)($u['layout_variant'] ?? 'standard'),
'backgroundVariant' => (string)($u['background_variant'] ?? 'none'),
'accentColor' => (string)($u['accent_color'] ?? ''),
'featured' => (bool)($u['featured'] ?? false),
'heroLayout' => (string)($u['hero_layout'] ?? 'fullscreen'),
'textTheme' => (string)($u['text_theme'] ?? 'light'),
],
]);
}
// ---------------------------------------------------------------- images
/**
* FAL image (with srcset). $table defaults to the usecase table but can be
* a content-block collection table (for Columns items).
*
* @return array<string,mixed>|null
*/
public function image(int $recordUid, string $fieldName, ?string $table = null): ?array
{
try {
$table = $table ?? self::TABLE;
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$ref = $qb
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$qb->expr()->eq('sfr.tablenames', $qb->createNamedParameter($table, ParameterType::STRING)),
$qb->expr()->eq('sfr.fieldname', $qb->createNamedParameter($fieldName, ParameterType::STRING)),
$qb->expr()->eq('sfr.uid_foreign', $qb->createNamedParameter($recordUid, ParameterType::INTEGER)),
$qb->expr()->eq('sfr.deleted', 0),
$qb->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$ref) {
return null;
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
$srcset = [];
foreach (self::IMAGE_WIDTHS as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $ref['crop'] ?? null]
);
$srcset[] = [
'url' => $imageService->getImageUri($variant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $ref['crop'] ?? null]
);
return [
'uid' => (int)$ref['uid'],
'url' => $imageService->getImageUri($default),
'title' => (string)($ref['title'] ?? ''),
'alternative' => (string)($ref['alternative'] ?? ''),
'description' => (string)($ref['description'] ?? ''),
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Throwable $e) {
return null;
}
}
/**
* FAL video (public URL only). $table as in image().
*
* @return array<string,mixed>|null
*/
public function video(int $recordUid, string $fieldName, ?string $table = null): ?array
{
try {
$table = $table ?? self::TABLE;
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$ref = $qb
->select('uid', 'title', 'description')
->from('sys_file_reference')
->where(
$qb->expr()->eq('tablenames', $qb->createNamedParameter($table, ParameterType::STRING)),
$qb->expr()->eq('fieldname', $qb->createNamedParameter($fieldName, ParameterType::STRING)),
$qb->expr()->eq('uid_foreign', $qb->createNamedParameter($recordUid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$ref) {
return null;
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$ref['uid']);
return [
'uid' => (int)$ref['uid'],
'url' => $fileReference->getPublicUrl(),
'title' => (string)($ref['title'] ?? ''),
'mimeType' => $fileReference->getProperty('mime_type'),
];
} catch (\Throwable $e) {
return null;
}
}
// ------------------------------------------------------------- relations
/**
* Single market (foreign_table, no MM) -> title/slug.
*
* @return array<string,mixed>|null
*/
public function market(int $marketUid): ?array
{
if ($marketUid <= 0) {
return null;
}
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_market');
$row = $qb
->select('uid', 'title', 'subtitle', 'teaser')
->from('tx_vitec_domain_model_market')
->where(
$qb->expr()->eq('uid', $qb->createNamedParameter($marketUid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
if (!$row) {
return null;
}
return [
'uid' => (int)$row['uid'],
'title' => (string)($row['title'] ?? ''),
'subtitle' => (string)($row['subtitle'] ?? ''),
'teaser' => (string)($row['teaser'] ?? ''),
];
} catch (\Throwable $e) {
return null;
}
}
/**
* n:n relation resolved through an MM table -> [{uid,title}, …].
*
* @return array<int,array<string,mixed>>
*/
public function relationMulti(string $mmTable, string $foreignTable, int $usecaseUid): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable($foreignTable);
$rows = $qb
->select('f.uid', 'f.title')
->from($foreignTable, 'f')
->join('f', $mmTable, 'mm', 'mm.uid_foreign = f.uid')
->where(
$qb->expr()->eq('mm.uid_local', $qb->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$qb->expr()->eq('f.deleted', 0),
$qb->expr()->eq('f.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static fn ($r) => [
'uid' => (int)$r['uid'],
'title' => (string)($r['title'] ?? ''),
], $rows);
} catch (\Throwable $e) {
return [];
}
}
/**
* System categories (sys_category MM) -> [{uid,title,description}, …].
*
* @return array<int,array<string,mixed>>
*/
public function categories(int $usecaseUid): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$rows = $qb
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter(self::TABLE, ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static fn ($c) => [
'uid' => (int)$c['uid'],
'title' => (string)($c['title'] ?? ''),
'description' => (string)($c['description'] ?? ''),
], $rows);
} catch (\Throwable $e) {
return [];
}
}
// ------------------------------------------------------- content elements
/**
* Inline content elements (tt_content owned via tx_vitec_usecase_content).
* Flat CEs are normalised; a VITEC Columns element is resolved specially.
*
* @return array<int,array<string,mixed>>
*/
public function contentElements(int $usecaseUid): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$rows = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('tx_vitec_usecase_content', $qb->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
$out = [];
foreach ($rows as $row) {
$resolved = $this->resolveInlineElement($row);
if ($resolved !== null) {
$out[] = $resolved;
}
}
return $out;
} catch (\Throwable $e) {
return [];
}
}
/**
* @param array<string,mixed> $row
* @return array<string,mixed>|null
*/
private function resolveInlineElement(array $row): ?array
{
if ((string)($row['CType'] ?? '') === self::COLUMNS_CTYPE) {
return $this->resolveColumnsElement($row);
}
return ContentElementResolver::normaliseRecord($row);
}
/**
* VITEC Columns content block -> { header…, layout, columns:{left,right} }.
* Items live in a content-blocks collection table (discovered from TCA) and
* carry a `column` (left/right) plus a typed payload.
*
* @param array<string,mixed> $row
* @return array<string,mixed>
*/
private function resolveColumnsElement(array $row): array
{
$uid = (int)$row['uid'];
$left = [];
$right = [];
$table = $this->collectionTable('vitec_items') ?? $this->collectionTable('items');
if ($table !== null) {
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable($table);
$items = $qb
->select('*')
->from($table)
->where(
$qb->expr()->eq('foreign_table_parent_uid', $qb->createNamedParameter($uid, ParameterType::INTEGER)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($items as $it) {
$column = (string)($it['vitec_column'] ?? $it['column'] ?? 'left');
$resolved = $this->resolveColumnItem($it, $table);
if ($column === 'right') {
$right[] = $resolved;
} else {
$left[] = $resolved;
}
}
} catch (\Throwable $e) {
// collection table not ready / query failed -> empty columns
}
}
return [
'id' => $uid,
'type' => self::COLUMNS_CTYPE,
'colPos' => (int)($row['colPos'] ?? 0),
'sorting' => (int)($row['sorting'] ?? 0),
'header' => (string)($row['header'] ?? ''),
'subheader' => (string)($row['subheader'] ?? ''),
'headerLayout' => (int)($row['header_layout'] ?? 0),
'headerPosition' => (string)($row['header_position'] ?? ''),
'headerLink' => (string)($row['header_link'] ?? ''),
'layout' => (string)($row['vitec_layout'] ?? $row['layout'] ?? 'cols_50_50'),
'columns' => [
'left' => $left,
'right' => $right,
],
];
}
/**
* @param array<string,mixed> $it
* @return array<string,mixed>
*/
private function resolveColumnItem(array $it, string $table): array
{
$uid = (int)$it['uid'];
$get = static fn (string $k): string => (string)($it['vitec_' . $k] ?? $it[$k] ?? '');
$imageField = array_key_exists('vitec_image', $it) ? 'vitec_image' : 'image';
$videoField = array_key_exists('vitec_video', $it) ? 'vitec_video' : 'video';
return [
'type' => $get('item_type') !== '' ? $get('item_type') : 'text',
'headline' => $get('headline'),
'text' => $get('text'),
'image' => $this->image($uid, $imageField, $table),
'video' => $this->video($uid, $videoField, $table),
'link' => $get('link'),
'linkLabel' => $get('link_label'),
];
}
/**
* Resolve a content-blocks collection field's storage table from TCA.
*/
private function collectionTable(string $field): ?string
{
$t = $GLOBALS['TCA']['tt_content']['columns'][$field]['config']['foreign_table'] ?? null;
return (is_string($t) && $t !== '') ? $t : null;
}
// ------------------------------------------------------------------- seo
/**
* SEO bundle with fallback resolution (title/teaser/card/hero images).
*
* @param array<string,mixed> $u
* @return array<string,mixed>
*/
public function seo(array $u): array
{
$uid = (int)$u['uid'];
$title = $this->firstNonEmpty([(string)($u['seo_title'] ?? ''), (string)($u['title'] ?? '')]);
$desc = $this->firstNonEmpty([(string)($u['seo_description'] ?? ''), (string)($u['teaser'] ?? '')]);
$ogTitle = $this->firstNonEmpty([(string)($u['og_title'] ?? ''), $title]);
$ogDesc = $this->firstNonEmpty([(string)($u['og_description'] ?? ''), $desc]);
$ogImage = $this->image($uid, 'og_image')
?? $this->image($uid, 'card_image')
?? $this->image($uid, 'hero_bgimage');
$twTitle = $this->firstNonEmpty([(string)($u['twitter_title'] ?? ''), $ogTitle]);
$twDesc = $this->firstNonEmpty([(string)($u['twitter_description'] ?? ''), $ogDesc]);
$twImage = $this->image($uid, 'twitter_image') ?? $ogImage;
return [
'title' => $title,
'description' => $desc,
'canonical' => (string)($u['canonical_link'] ?? ''),
'robots' => [
'noIndex' => (bool)($u['no_index'] ?? false),
'noFollow' => (bool)($u['no_follow'] ?? false),
],
'openGraph' => [
'title' => $ogTitle,
'description' => $ogDesc,
'image' => $ogImage,
],
'twitter' => [
'title' => $twTitle,
'description' => $twDesc,
'image' => $twImage,
],
];
}
/**
* @param array<int,string> $candidates
*/
private function firstNonEmpty(array $candidates): string
{
foreach ($candidates as $c) {
$c = trim($c);
if ($c !== '') {
return $c;
}
}
return '';
}
}
@@ -7,6 +7,7 @@ namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable; use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Service\FlexFormService;
@@ -49,13 +50,7 @@ class DatasheetsJsonRenderer
->from('tt_content') ->from('tt_content')
->where( ->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->or(
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_datasheets', ParameterType::STRING)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_datasheets', ParameterType::STRING)),
$qb->expr()->and(
$qb->expr()->eq('CType', $qb->createNamedParameter('list', ParameterType::STRING)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_datasheets', ParameterType::STRING))
)
),
$qb->expr()->eq('deleted', 0), $qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0) $qb->expr()->eq('hidden', 0)
) )
@@ -118,6 +113,7 @@ class DatasheetsJsonRenderer
$response['debug'] = [ $response['debug'] = [
'productCount' => count($itemsOut), 'productCount' => count($itemsOut),
'settings' => $settings, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromItems($itemsOut),
]; ];
} }
@@ -259,6 +255,7 @@ class DatasheetsJsonRenderer
private function serializeDatasheet(array $download): array private function serializeDatasheet(array $download): array
{ {
$uid = (int)$download['uid']; $uid = (int)$download['uid'];
$filetype = $this->getDownloadFileType($uid);
return [ return [
'uid' => $uid, 'uid' => $uid,
'title' => (string)($download['title'] ?? ''), 'title' => (string)($download['title'] ?? ''),
@@ -266,10 +263,48 @@ class DatasheetsJsonRenderer
'teaser' => (string)($download['teaser'] ?? ''), 'teaser' => (string)($download['teaser'] ?? ''),
'description' => (string)($download['description'] ?? ''), 'description' => (string)($download['description'] ?? ''),
'tstamp' => (int)($download['tstamp'] ?? 0), 'tstamp' => (int)($download['tstamp'] ?? 0),
'file' => $this->getDownloadFile($uid), 'filetype' => $filetype,
'file' => $this->getDownloadFile($uid, (string)($download['fileprefix'] ?? ''), $filetype),
]; ];
} }
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.filetype')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0),
$qb->expr()->eq('c.parent', $qb->createNamedParameter(4, ParameterType::INTEGER))
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($categories as $category) {
$filetype = trim((string)($category['filetype'] ?? ''));
if ($filetype !== '') {
return $filetype;
}
}
return '';
}
/** /**
* @return array<string,mixed>|null * @return array<string,mixed>|null
*/ */
@@ -356,7 +391,7 @@ class DatasheetsJsonRenderer
/** /**
* @return array<string,mixed>|null * @return array<string,mixed>|null
*/ */
private function getDownloadFile(int $downloadUid): ?array private function getDownloadFile(int $downloadUid, string $fileprefix = '', string $filetype = ''): ?array
{ {
$qb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference'); ->getQueryBuilderForTable('sys_file_reference');
@@ -378,7 +413,7 @@ class DatasheetsJsonRenderer
->fetchAssociative(); ->fetchAssociative();
if (!$row) { if (!$row) {
return null; return $this->resolveFileByConvention($fileprefix, $filetype);
} }
return [ return [
@@ -392,4 +427,129 @@ class DatasheetsJsonRenderer
'description' => (string)($row['description'] ?? ''), 'description' => (string)($row['description'] ?? ''),
]; ];
} }
/**
* Resolve file from naming convention:
* [fileprefix]__[filetype]__[number]-[letter].ext
* and pick highest number + highest letter.
*
* @return array<string,mixed>|null
*/
private function resolveFileByConvention(string $fileprefix, string $filetype): ?array
{
$fileprefix = trim($fileprefix);
$filetype = trim($filetype);
if ($fileprefix === '' || $filetype === '') {
return null;
}
$baseDir = Environment::getPublicPath() . '/fileadmin/downloads/Collateral';
if (!is_dir($baseDir) || !is_readable($baseDir)) {
return null;
}
$escapedPrefix = preg_quote($fileprefix, '/');
$escapedType = preg_quote($filetype, '/');
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
$bestFile = null;
$bestNumber = -1;
$bestLetterRank = -1;
$entries = scandir($baseDir);
if ($entries === false) {
return null;
}
foreach ($entries as $entry) {
if (!is_string($entry) || $entry === '.' || $entry === '..') {
continue;
}
if (!preg_match($pattern, $entry, $matches)) {
continue;
}
$number = (int)$matches[1];
$letter = strtoupper($matches[2]);
$letterRank = $this->letterSequenceToRank($letter);
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
$bestNumber = $number;
$bestLetterRank = $letterRank;
$bestFile = $entry;
}
}
if ($bestFile === null) {
return null;
}
$fullPath = $baseDir . '/' . $bestFile;
if (!is_file($fullPath) || !is_readable($fullPath)) {
return null;
}
$extension = strtolower(pathinfo($bestFile, PATHINFO_EXTENSION));
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
return [
'uid' => 0,
'name' => $bestFile,
'url' => '/fileadmin/downloads/Collateral/' . rawurlencode($bestFile),
'size' => (int)(filesize($fullPath) ?: 0),
'extension' => $extension,
'mimeType' => $mimeType,
'title' => '',
'description' => '',
];
}
private function letterSequenceToRank(string $letters): int
{
$rank = 0;
$letters = strtoupper($letters);
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$char = ord($letters[$i]);
if ($char < 65 || $char > 90) {
continue;
}
$rank = ($rank * 26) + ($char - 64);
}
return $rank;
}
/**
* @param array<int,array<string,mixed>> $items
* @return array<int,array<string,mixed>>
*/
private function collectFallbackFilesFromItems(array $items): array
{
$fallbackFiles = [];
foreach ($items as $item) {
$datasheet = $item['datasheet'] ?? null;
if (!is_array($datasheet)) {
continue;
}
$file = $datasheet['file'] ?? null;
if (!is_array($file) || (int)($file['uid'] ?? 1) !== 0) {
continue;
}
$fallbackFiles[] = [
'datasheetUid' => (int)($datasheet['uid'] ?? 0),
'name' => (string)($file['name'] ?? ''),
'url' => (string)($file['url'] ?? ''),
'source' => 'naming-convention-fallback',
];
}
return $fallbackFiles;
}
} }
@@ -7,6 +7,7 @@ namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable; use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -44,20 +45,12 @@ class DownloadcardJsonRenderer
$qb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content'); ->getQueryBuilderForTable('tt_content');
// After v14 migration the CType is the plugin signature; for legacy
// (unmigrated) records the CType is still "list" with list_type set.
$rows = $qb $rows = $qb
->select('*') ->select('*')
->from('tt_content') ->from('tt_content')
->where( ->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->or(
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_downloadcard', ParameterType::STRING)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_downloadcard', ParameterType::STRING)),
$qb->expr()->and(
$qb->expr()->eq('CType', $qb->createNamedParameter('list', ParameterType::STRING)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_downloadcard', ParameterType::STRING))
)
),
$qb->expr()->eq('deleted', 0), $qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0) $qb->expr()->eq('hidden', 0)
) )
@@ -106,14 +99,17 @@ class DownloadcardJsonRenderer
: ''; : '';
} }
$serializedDownload = $this->serializeDownload($download);
$response = [ $response = [
'mode' => 'single', 'mode' => 'single',
'download' => $this->serializeDownload($download), 'download' => $serializedDownload,
'settings' => $settingsOut, 'settings' => $settingsOut,
]; ];
if ($debugMode) { if ($debugMode) {
$response['debug'] = [ $response['debug'] = [
'pageId' => $pageId, 'downloadUid' => $downloadUid, 'settings' => $settings, 'pageId' => $pageId, 'downloadUid' => $downloadUid, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromDownloads([$serializedDownload]),
]; ];
} }
return json_encode($response); return json_encode($response);
@@ -123,11 +119,12 @@ class DownloadcardJsonRenderer
if ($productUid > 0) { if ($productUid > 0) {
$downloads = $this->fetchDownloadsForProduct($productUid); $downloads = $this->fetchDownloadsForProduct($productUid);
$product = $this->fetchProductContext($productUid); $product = $this->fetchProductContext($productUid);
$serializedDownloads = array_map(fn($r) => $this->serializeDownload($r), $downloads);
$response = [ $response = [
'mode' => 'product-downloads', 'mode' => 'product-downloads',
'product' => $product, 'product' => $product,
'downloads' => array_map(fn($r) => $this->serializeDownload($r), $downloads), 'downloads' => $serializedDownloads,
'settings' => $settingsOut, 'settings' => $settingsOut,
]; ];
if ($debugMode) { if ($debugMode) {
@@ -136,6 +133,7 @@ class DownloadcardJsonRenderer
'productUid' => $productUid, 'productUid' => $productUid,
'downloadCount' => count($downloads), 'downloadCount' => count($downloads),
'settings' => $settings, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromDownloads($serializedDownloads),
]; ];
} }
return json_encode($response); return json_encode($response);
@@ -248,6 +246,8 @@ class DownloadcardJsonRenderer
protected function serializeDownload(array $download): array protected function serializeDownload(array $download): array
{ {
$uid = (int)$download['uid']; $uid = (int)$download['uid'];
$fileprefix = (string)($download['fileprefix'] ?? '');
$filetype = $this->getDownloadFileType($uid);
return [ return [
'uid' => $uid, 'uid' => $uid,
@@ -258,22 +258,60 @@ class DownloadcardJsonRenderer
'keywords' => (string)($download['keywords'] ?? ''), 'keywords' => (string)($download['keywords'] ?? ''),
'icon' => (string)($download['icon'] ?? ''), 'icon' => (string)($download['icon'] ?? ''),
'filepath' => (string)($download['filepath'] ?? ''), 'filepath' => (string)($download['filepath'] ?? ''),
'fileprefix' => (string)($download['fileprefix'] ?? ''), 'fileprefix' => $fileprefix,
'filetype' => $filetype,
'private_download' => (bool)($download['private_download'] ?? false), 'private_download' => (bool)($download['private_download'] ?? false),
'hideonapp' => (bool)($download['hideonapp'] ?? false), 'hideonapp' => (bool)($download['hideonapp'] ?? false),
'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false), 'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($download['hideondatasheets'] ?? false), 'hideondatasheets' => (bool)($download['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($download['hideonproducts'] ?? false), 'hideonproducts' => (bool)($download['hideonproducts'] ?? false),
'file' => $this->getDownloadFile($uid), 'file' => $this->getDownloadFile($uid, $fileprefix, $filetype),
]; ];
} }
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.filetype')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0),
$qb->expr()->eq('c.parent', $qb->createNamedParameter(4, ParameterType::INTEGER))
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($categories as $category) {
$filetype = trim((string)($category['filetype'] ?? ''));
if ($filetype !== '') {
return $filetype;
}
}
return '';
}
/** /**
* Resolve the FAL file reference (fieldname=file) for a download. * Resolve the FAL file reference (fieldname=file) for a download.
* *
* @return array<string,mixed>|null * @return array<string,mixed>|null
*/ */
protected function getDownloadFile(int $downloadUid): ?array protected function getDownloadFile(int $downloadUid, string $fileprefix = '', string $filetype = ''): ?array
{ {
$qb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference'); ->getQueryBuilderForTable('sys_file_reference');
@@ -295,7 +333,7 @@ class DownloadcardJsonRenderer
->fetchAssociative(); ->fetchAssociative();
if (!$row) { if (!$row) {
return null; return $this->resolveFileByConvention($fileprefix, $filetype);
} }
$thumbnailUrl = null; $thumbnailUrl = null;
@@ -324,4 +362,124 @@ class DownloadcardJsonRenderer
'description' => (string)($row['description'] ?? ''), 'description' => (string)($row['description'] ?? ''),
]; ];
} }
/**
* @return array<string,mixed>|null
*/
private function resolveFileByConvention(string $fileprefix, string $filetype): ?array
{
$fileprefix = trim($fileprefix);
$filetype = trim($filetype);
if ($fileprefix === '' || $filetype === '') {
return null;
}
$baseDir = Environment::getPublicPath() . '/fileadmin/downloads/Collateral';
if (!is_dir($baseDir) || !is_readable($baseDir)) {
return null;
}
$escapedPrefix = preg_quote($fileprefix, '/');
$escapedType = preg_quote($filetype, '/');
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
$bestFile = null;
$bestNumber = -1;
$bestLetterRank = -1;
$entries = scandir($baseDir);
if ($entries === false) {
return null;
}
foreach ($entries as $entry) {
if (!is_string($entry) || $entry === '.' || $entry === '..') {
continue;
}
if (!preg_match($pattern, $entry, $matches)) {
continue;
}
$number = (int)$matches[1];
$letterRank = $this->letterSequenceToRank((string)$matches[2]);
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
$bestNumber = $number;
$bestLetterRank = $letterRank;
$bestFile = $entry;
}
}
if ($bestFile === null) {
return null;
}
$fullPath = $baseDir . '/' . $bestFile;
if (!is_file($fullPath) || !is_readable($fullPath)) {
return null;
}
$extension = strtolower(pathinfo($bestFile, PATHINFO_EXTENSION));
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
return [
'uid' => 0,
'name' => $bestFile,
'url' => '/fileadmin/downloads/Collateral/' . rawurlencode($bestFile),
'thumbnail' => null,
'size' => (int)(filesize($fullPath) ?: 0),
'extension' => $extension,
'mimeType' => $mimeType,
'title' => '',
'description' => '',
];
}
private function letterSequenceToRank(string $letters): int
{
$rank = 0;
$letters = strtoupper($letters);
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$char = ord($letters[$i]);
if ($char < 65 || $char > 90) {
continue;
}
$rank = ($rank * 26) + ($char - 64);
}
return $rank;
}
/**
* @param array<int,array<string,mixed>> $downloads
* @return array<int,array<string,mixed>>
*/
private function collectFallbackFilesFromDownloads(array $downloads): array
{
$fallbackFiles = [];
foreach ($downloads as $download) {
$file = $download['file'] ?? null;
if (!is_array($file)) {
continue;
}
if ((int)($file['uid'] ?? 1) !== 0) {
continue;
}
$fallbackFiles[] = [
'downloadUid' => (int)($download['uid'] ?? 0),
'name' => (string)($file['name'] ?? ''),
'url' => (string)($file['url'] ?? ''),
'source' => 'naming-convention-fallback',
];
}
return $fallbackFiles;
}
} }
@@ -7,6 +7,7 @@ namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable; use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory; use TYPO3\CMS\Core\Resource\ResourceFactory;
@@ -96,13 +97,14 @@ class DownloadcardcollectionJsonRenderer
// --- Downloads: comma-separated UID list from settings.download --- // --- Downloads: comma-separated UID list from settings.download ---
$downloadUids = $this->parseUidList($settings['download'] ?? ''); $downloadUids = $this->parseUidList($settings['download'] ?? '');
$downloads = $downloadUids === [] ? [] : $this->fetchDownloadsByUids($downloadUids); $downloads = $downloadUids === [] ? [] : $this->fetchDownloadsByUids($downloadUids);
$serializedDownloads = array_map(fn($r) => $this->serializeDownload($r), $downloads);
// --- Header image (inline FAL on this tt_content element, fieldname=image) --- // --- Header image (inline FAL on this tt_content element, fieldname=image) ---
$image = $this->getCollectionImage($ttContentUid); $image = $this->getCollectionImage($ttContentUid);
$response = [ $response = [
'image' => $image, 'image' => $image,
'downloads' => array_map(fn($r) => $this->serializeDownload($r), $downloads), 'downloads' => $serializedDownloads,
'settings' => $settingsOut, 'settings' => $settingsOut,
]; ];
@@ -112,6 +114,7 @@ class DownloadcardcollectionJsonRenderer
'downloadUids' => $downloadUids, 'downloadUids' => $downloadUids,
'downloadCount' => count($downloads), 'downloadCount' => count($downloads),
'settings' => $settings, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromDownloads($serializedDownloads),
]; ];
} }
@@ -212,7 +215,6 @@ class DownloadcardcollectionJsonRenderer
); );
$srcset[] = [ $srcset[] = [
'url' => $imageService->getImageUri($variant), 'url' => $imageService->getImageUri($variant),
'thumbnail' => $thumbnailUrl,
'width' => $width, 'width' => $width,
'descriptor' => $width . 'w', 'descriptor' => $width . 'w',
]; ];
@@ -226,7 +228,6 @@ class DownloadcardcollectionJsonRenderer
return [ return [
'uid' => (int)$fileRefData['uid'], 'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($default), 'url' => $imageService->getImageUri($default),
'thumbnail' => $thumbnailUrl,
'title' => $fileRefData['title'] ?? '', 'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '', 'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '', 'description' => $fileRefData['description'] ?? '',
@@ -249,6 +250,8 @@ class DownloadcardcollectionJsonRenderer
protected function serializeDownload(array $download): array protected function serializeDownload(array $download): array
{ {
$uid = (int)$download['uid']; $uid = (int)$download['uid'];
$fileprefix = (string)($download['fileprefix'] ?? '');
$filetype = $this->getDownloadFileType($uid);
return [ return [
'uid' => $uid, 'uid' => $uid,
@@ -259,22 +262,60 @@ class DownloadcardcollectionJsonRenderer
'keywords' => (string)($download['keywords'] ?? ''), 'keywords' => (string)($download['keywords'] ?? ''),
'icon' => (string)($download['icon'] ?? ''), 'icon' => (string)($download['icon'] ?? ''),
'filepath' => (string)($download['filepath'] ?? ''), 'filepath' => (string)($download['filepath'] ?? ''),
'fileprefix' => (string)($download['fileprefix'] ?? ''), 'fileprefix' => $fileprefix,
'filetype' => $filetype,
'private_download' => (bool)($download['private_download'] ?? false), 'private_download' => (bool)($download['private_download'] ?? false),
'hideonapp' => (bool)($download['hideonapp'] ?? false), 'hideonapp' => (bool)($download['hideonapp'] ?? false),
'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false), 'hideonwebsite' => (bool)($download['hideonwebsite'] ?? false),
'hideondatasheets' => (bool)($download['hideondatasheets'] ?? false), 'hideondatasheets' => (bool)($download['hideondatasheets'] ?? false),
'hideonproducts' => (bool)($download['hideonproducts'] ?? false), 'hideonproducts' => (bool)($download['hideonproducts'] ?? false),
'file' => $this->getDownloadFile($uid), 'file' => $this->getDownloadFile($uid, $fileprefix, $filetype),
]; ];
} }
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.filetype')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0),
$qb->expr()->eq('c.parent', $qb->createNamedParameter(4, ParameterType::INTEGER))
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($categories as $category) {
$filetype = trim((string)($category['filetype'] ?? ''));
if ($filetype !== '') {
return $filetype;
}
}
return '';
}
/** /**
* Resolve the FAL file reference (fieldname=file) for a download. * Resolve the FAL file reference (fieldname=file) for a download.
* *
* @return array<string,mixed>|null * @return array<string,mixed>|null
*/ */
protected function getDownloadFile(int $downloadUid): ?array protected function getDownloadFile(int $downloadUid, string $fileprefix = '', string $filetype = ''): ?array
{ {
$qb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference'); ->getQueryBuilderForTable('sys_file_reference');
@@ -296,7 +337,7 @@ class DownloadcardcollectionJsonRenderer
->fetchAssociative(); ->fetchAssociative();
if (!$row) { if (!$row) {
return null; return $this->resolveFileByConvention($fileprefix, $filetype);
} }
@@ -325,4 +366,124 @@ class DownloadcardcollectionJsonRenderer
'description' => (string)($row['description'] ?? ''), 'description' => (string)($row['description'] ?? ''),
]; ];
} }
/**
* @return array<string,mixed>|null
*/
private function resolveFileByConvention(string $fileprefix, string $filetype): ?array
{
$fileprefix = trim($fileprefix);
$filetype = trim($filetype);
if ($fileprefix === '' || $filetype === '') {
return null;
}
$baseDir = Environment::getPublicPath() . '/fileadmin/downloads/Collateral';
if (!is_dir($baseDir) || !is_readable($baseDir)) {
return null;
}
$escapedPrefix = preg_quote($fileprefix, '/');
$escapedType = preg_quote($filetype, '/');
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
$bestFile = null;
$bestNumber = -1;
$bestLetterRank = -1;
$entries = scandir($baseDir);
if ($entries === false) {
return null;
}
foreach ($entries as $entry) {
if (!is_string($entry) || $entry === '.' || $entry === '..') {
continue;
}
if (!preg_match($pattern, $entry, $matches)) {
continue;
}
$number = (int)$matches[1];
$letterRank = $this->letterSequenceToRank((string)$matches[2]);
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
$bestNumber = $number;
$bestLetterRank = $letterRank;
$bestFile = $entry;
}
}
if ($bestFile === null) {
return null;
}
$fullPath = $baseDir . '/' . $bestFile;
if (!is_file($fullPath) || !is_readable($fullPath)) {
return null;
}
$extension = strtolower(pathinfo($bestFile, PATHINFO_EXTENSION));
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
return [
'uid' => 0,
'name' => $bestFile,
'url' => '/fileadmin/downloads/Collateral/' . rawurlencode($bestFile),
'thumbnail' => null,
'size' => (int)(filesize($fullPath) ?: 0),
'extension' => $extension,
'mimeType' => $mimeType,
'title' => '',
'description' => '',
];
}
private function letterSequenceToRank(string $letters): int
{
$rank = 0;
$letters = strtoupper($letters);
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$char = ord($letters[$i]);
if ($char < 65 || $char > 90) {
continue;
}
$rank = ($rank * 26) + ($char - 64);
}
return $rank;
}
/**
* @param array<int,array<string,mixed>> $downloads
* @return array<int,array<string,mixed>>
*/
private function collectFallbackFilesFromDownloads(array $downloads): array
{
$fallbackFiles = [];
foreach ($downloads as $download) {
$file = $download['file'] ?? null;
if (!is_array($file)) {
continue;
}
if ((int)($file['uid'] ?? 1) !== 0) {
continue;
}
$fallbackFiles[] = [
'downloadUid' => (int)($download['uid'] ?? 0),
'name' => (string)($file['name'] ?? ''),
'url' => (string)($file['url'] ?? ''),
'source' => 'naming-convention-fallback',
];
}
return $fallbackFiles;
}
} }
+314
View File
@@ -0,0 +1,314 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* UserFunc to render the event list (tx_vitec_domain_model_event) as JSON.
*
* Default: upcoming events (event end — or start, when no end is set — is
* today or later), soonest first. FlexForm options: showpast (append past
* events, most recent first, under "pastEvents"), limit, debug.
*
* `render()` = TypoScript entry (cObj->data or page discovery).
* `renderForRecord()` = one specific tt_content row; reused by
* ContainerChildrenProcessor / ContentElementResolver. Exception-safe.
*/
class EventlistJsonRenderer
{
#[AsAllowedCallable]
public function render(string $content, array $conf): string
{
// 1) cObj data path
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_eventlist') {
return $this->renderForRecord($row);
}
// 2) Page discovery via v14 request attribute
$pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$pageInfo = $request->getAttribute('frontend.page.information');
if ($pageInfo !== null) {
$pageId = (int)$pageInfo->getId();
}
}
if ($pageId <= 0) {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
}
if ($pageId <= 0) {
return '';
}
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$contentElements = $qb
->select('*')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_eventlist', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if (empty($contentElements)) {
return '';
}
return $this->renderForRecord($contentElements[0]);
}
/**
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$showPast = (bool)($settings['showpast'] ?? false);
$daysInAdvance = (int)($settings['daysinadvance'] ?? 0);
$limit = (int)($settings['limit'] ?? 0);
$debugMode = (bool)($settings['debug'] ?? false);
$upcoming = $this->fetchEvents(true, $limit, $daysInAdvance);
$response = [
'events' => array_map(fn($e) => $this->serializeEvent($e), $upcoming),
'settings' => [
'showpast' => $showPast,
'daysinadvance' => $daysInAdvance,
'limit' => $limit,
],
];
if ($showPast) {
$past = $this->fetchEvents(false, $limit);
$response['pastEvents'] = array_map(fn($e) => $this->serializeEvent($e), $past);
}
if ($debugMode) {
$response['debug'] = [
'upcomingCount' => count($upcoming),
'settings' => $settings,
];
}
return json_encode($response);
} catch (\Throwable $e) {
return '';
}
}
/**
* @return array<int,array<string,mixed>>
*/
private function fetchEvents(bool $upcoming, int $limit, int $daysInAdvance = 0): array
{
$todayMidnight = strtotime('today');
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_event');
$expr = $qb->expr();
$qb
->select('*')
->from('tx_vitec_domain_model_event')
->where(
$expr->eq('deleted', 0),
$expr->eq('hidden', 0),
$expr->eq('hideonwebsite', 0)
);
if ($upcoming) {
// running or future: end >= today, or (no end and start >= today)
$qb->andWhere(
$expr->or(
$expr->gte('eventend', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER)),
$expr->and(
$expr->eq('eventend', 0),
$expr->gte('eventstart', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER))
)
)
);
$qb->orderBy('eventstart', 'ASC');
if ($daysInAdvance > 0) {
$qb->andWhere(
$expr->lt('eventstart', $qb->createNamedParameter($todayMidnight + ($daysInAdvance * 86400), ParameterType::INTEGER))
);
}
} else {
// past: (end > 0 and end < today) or (no end and 0 < start < today)
$qb->andWhere(
$expr->or(
$expr->and(
$expr->gt('eventend', 0),
$expr->lt('eventend', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER))
),
$expr->and(
$expr->eq('eventend', 0),
$expr->gt('eventstart', 0),
$expr->lt('eventstart', $qb->createNamedParameter($todayMidnight, ParameterType::INTEGER))
)
)
);
$qb->orderBy('eventstart', 'DESC');
}
if ($limit > 0) {
$qb->setMaxResults($limit);
}
return $qb->executeQuery()->fetchAllAssociative();
}
/**
* @param array<string,mixed> $event
* @return array<string,mixed>
*/
protected function serializeEvent(array $event): array
{
$uid = (int)$event['uid'];
$start = (int)($event['eventstart'] ?? 0);
$end = (int)($event['eventend'] ?? 0);
return [
'uid' => $uid,
'title' => (string)($event['title'] ?? ''),
'slug' => (string)($event['slug'] ?? ''),
'teaser' => (string)($event['teaser'] ?? ''),
'description' => (string)($event['description'] ?? ''),
'eventstart' => $start > 0 ? date('Y-m-d', $start) : null,
'eventend' => $end > 0 ? date('Y-m-d', $end) : null,
'venue' => (string)($event['venue'] ?? ''),
'booth' => (string)($event['booth'] ?? ''),
'city' => (string)($event['city'] ?? ''),
'country' => (string)($event['country'] ?? ''),
'attendancemode' => (string)($event['attendancemode'] ?? 'offline'),
'eventstatus' => (string)($event['eventstatus'] ?? 'scheduled'),
'eventurl' => (string)($event['eventurl'] ?? ''),
'meetinglink' => (string)($event['meetinglink'] ?? ''),
'image' => $this->getEventImage($uid),
'categories' => $this->getEventCategories($uid),
];
}
/**
* @return array<string,mixed>|null
*/
protected function getEventImage(int $eventUid): ?array
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $qb
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$qb->expr()->eq('sfr.tablenames', $qb->createNamedParameter('tx_vitec_domain_model_event', ParameterType::STRING)),
$qb->expr()->eq('sfr.fieldname', $qb->createNamedParameter('image', ParameterType::STRING)),
$qb->expr()->eq('sfr.uid_foreign', $qb->createNamedParameter($eventUid, ParameterType::INTEGER)),
$qb->expr()->eq('sfr.deleted', 0),
$qb->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRefData) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$srcset = [];
foreach ([400, 800] as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $fileRefData['crop'] ?? null]
);
$srcset[] = [
'url' => $imageService->getImageUri($variant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 400, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($default),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
protected function getEventCategories(int $eventUid): array
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_event', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($eventUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => (string)($cat['title'] ?? ''),
'description' => (string)($cat['description'] ?? ''),
];
}, $categories);
}
}
@@ -0,0 +1,753 @@
<?php
declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\ParameterType;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/**
* Headless JSON renderer for EXT:news content elements.
*
* The renderer supports the news list/detail plugin variants used in this
* project and emits a stable JSON structure for the frontend.
*
* Exception-safe: returns '' on any failure.
*/
final class NewsJsonRenderer
{
private const NEWS_CTYPES = [
'news_pi1',
'news_newsliststicky',
'news_newsselectedlist',
'news_newsdetail',
'news_newsdatemenu',
'news_categorylist',
'news_newssearchform',
'news_newssearchresult',
'news_taglist',
];
private const TEMPLATE_LAYOUTS = [
'1' => 'Compact List',
'2' => 'Featured Story',
'3' => 'Editorial Grid',
'4' => 'Detail Spotlight',
];
private const TEMPLATE_LAYOUT_DEFAULTS = [
'news_pi1' => '1',
'news_newsliststicky' => '1',
'news_newsselectedlist' => '1',
'news_newsdetail' => '2',
'news_newsdatemenu' => '3',
'news_categorylist' => '3',
'news_newssearchform' => '4',
'news_newssearchresult' => '4',
'news_taglist' => '3',
];
#[AsAllowedCallable]
public function render(string $content, array $conf): string
{
try {
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row !== null && in_array((string)($row['CType'] ?? ''), self::NEWS_CTYPES, true)) {
return $this->renderForRecord($row);
}
$pageId = $this->resolveCurrentPageId();
if ($pageId <= 0) {
return '';
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$ctypeConstraints = [];
foreach (self::NEWS_CTYPES as $ctype) {
$ctypeConstraints[] = $queryBuilder->expr()->eq(
'CType',
$queryBuilder->createNamedParameter($ctype, ParameterType::STRING)
);
}
$contentElements = $queryBuilder
->select('*')
->from('tt_content')
->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->or(...$ctypeConstraints),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
if ($contentElements === []) {
return '';
}
return $this->renderForRecord($contentElements[0]);
} catch (\Throwable $e) {
return '';
}
}
/**
* Render exactly the given news tt_content row.
*
* @param array<string,mixed> $contentElement
*/
public function renderForRecord(array $contentElement): string
{
try {
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? [];
$type = (string)($contentElement['CType'] ?? '');
$mode = $type === 'news_newsdetail' ? 'detail' : 'list';
$debug = (bool)($settings['debug'] ?? false);
if ($mode === 'detail') {
$news = $this->resolveDetailNews($contentElement, $settings);
$response = [
'mode' => 'detail',
'news' => $news !== null ? $this->serializeNews($news, $settings) : null,
'settings' => $this->buildSettingsPayload($contentElement, $settings),
];
if ($debug) {
$response['debug'] = [
'contentElementUid' => (int)($contentElement['uid'] ?? 0),
'settings' => $settings,
];
}
return json_encode($response) ?: '';
}
$newsRows = $this->fetchNewsList($contentElement, $settings);
$items = array_map(fn (array $newsRow): array => $this->serializeNews($newsRow, $settings), $newsRows);
$response = [
'mode' => 'list',
'items' => $items,
'settings' => $this->buildSettingsPayload($contentElement, $settings),
];
if ($debug) {
$response['debug'] = [
'contentElementUid' => (int)($contentElement['uid'] ?? 0),
'count' => count($items),
'settings' => $settings,
];
}
return json_encode($response) ?: '';
} catch (\Throwable $e) {
return json_encode([
'mode' => 'error',
'message' => $e->getMessage(),
]) ?: '';
}
}
/**
* @param array<string,mixed> $contentElement
* @param array<string,mixed> $settings
* @return array<string,mixed>|null
*/
private function resolveDetailNews(array $contentElement, array $settings): ?array
{
$newsUid = $this->resolveNewsUid((string)($settings['singleNews'] ?? ''));
if ($newsUid <= 0) {
$queryParams = $GLOBALS['TYPO3_REQUEST']?->getQueryParams() ?? [];
$newsParam = $queryParams['tx_news_pi1']['news'] ?? null;
$newsUid = $this->resolveNewsUid((string)($newsParam ?? ''));
}
if ($newsUid > 0) {
$news = $this->fetchNewsByUid($newsUid, (int)($contentElement['sys_language_uid'] ?? 0));
if ($news !== null) {
return $news;
}
}
$list = $this->fetchNewsList($contentElement, $settings, 1);
return $list[0] ?? null;
}
/**
* @param array<string,mixed> $contentElement
* @param array<string,mixed> $settings
* @param int|null $limitOverride
* @return array<int,array<string,mixed>>
*/
private function fetchNewsList(array $contentElement, array $settings, ?int $limitOverride = null): array
{
$selectedNews = $this->parseUidList((string)($settings['selectedList'] ?? ''));
$storagePids = $this->parseUidList((string)($settings['startingpoint'] ?? ''));
if ($storagePids === []) {
$storagePids = [(int)($contentElement['pid'] ?? 0)];
}
$categoryUids = $this->parseUidList((string)($settings['categories'] ?? ''));
$includeSubCategories = (bool)($settings['includeSubCategories'] ?? false);
$tagUids = $this->parseUidList((string)($settings['tags'] ?? ''));
$limit = $limitOverride ?? (int)($settings['limit'] ?? 0);
$orderDirection = strtoupper((string)($settings['orderDirection'] ?? 'DESC')) === 'ASC' ? 'ASC' : 'DESC';
$topNewsFirst = (bool)($settings['topNewsFirst'] ?? false);
$searchTerm = $this->resolveSearchTerm((string)($contentElement['CType'] ?? ''));
$languageUid = (int)($contentElement['sys_language_uid'] ?? 0);
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
$rows = $queryBuilder
->select('n.*')
->from('tx_news_domain_model_news', 'n')
->where(
$queryBuilder->expr()->eq('n.deleted', 0),
$queryBuilder->expr()->eq('n.hidden', 0),
$queryBuilder->expr()->in('n.pid', $queryBuilder->createNamedParameter($storagePids, ArrayParameterType::INTEGER))
)
->orderBy('n.istopnews', $topNewsFirst ? 'DESC' : 'ASC')
->addOrderBy('n.datetime', 'DESC')
->executeQuery()
->fetchAllAssociative();
if ($languageUid > 0) {
$rows = array_values(array_filter($rows, static fn (array $row): bool => (int)($row['sys_language_uid'] ?? 0) === $languageUid));
} else {
$rows = array_values(array_filter($rows, static fn (array $row): bool => in_array((int)($row['sys_language_uid'] ?? 0), [0, -1], true)));
}
if ($selectedNews !== []) {
$selectedLookup = array_fill_keys($selectedNews, true);
$rows = array_values(array_filter($rows, static fn (array $row): bool => isset($selectedLookup[(int)($row['uid'] ?? 0)])));
}
if ($categoryUids !== []) {
$categoryLookup = $this->newsUidsForCategories($categoryUids, $includeSubCategories);
if ($categoryLookup === [] && !$includeSubCategories) {
$categoryLookup = $this->newsUidsForCategories($this->expandCategoryUids($categoryUids), true);
}
if ($categoryLookup !== []) {
$rows = array_values(array_filter($rows, static fn (array $row): bool => isset($categoryLookup[(int)($row['uid'] ?? 0)])));
}
}
if ($tagUids !== []) {
$tagLookup = $this->newsUidsForTags($tagUids);
if ($tagLookup !== []) {
$rows = array_values(array_filter($rows, static fn (array $row): bool => isset($tagLookup[(int)($row['uid'] ?? 0)])));
}
}
if ($searchTerm !== '') {
$needle = mb_strtolower($searchTerm);
$rows = array_values(array_filter($rows, static function (array $row) use ($needle): bool {
$haystack = mb_strtolower((string)($row['title'] ?? '') . ' ' . (string)($row['teaser'] ?? '') . ' ' . (string)($row['path_segment'] ?? ''));
return str_contains($haystack, $needle);
}));
}
if ($limit > 0) {
$rows = array_slice($rows, 0, $limit);
}
return $rows;
}
/**
* @param int $newsUid
* @param int $languageUid
* @return array<string,mixed>|null
*/
private function fetchNewsByUid(int $newsUid, int $languageUid): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
$query = $queryBuilder
->select('n.*')
->from('tx_news_domain_model_news', 'n')
->where(
$queryBuilder->expr()->eq('n.uid', $queryBuilder->createNamedParameter($newsUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('n.deleted', 0),
$queryBuilder->expr()->eq('n.hidden', 0)
);
if ($languageUid > 0) {
$query->andWhere(
$queryBuilder->expr()->eq('n.sys_language_uid', $queryBuilder->createNamedParameter($languageUid, ParameterType::INTEGER))
);
}
$news = $query->executeQuery()->fetchAssociative();
return is_array($news) ? $news : null;
}
/**
* @param array<string,mixed> $news
* @param array<string,mixed> $settings
* @return array<string,mixed>
*/
private function serializeNews(array $news, array $settings = []): array
{
$uid = (int)($news['uid'] ?? 0);
$pathSegment = (string)($news['path_segment'] ?? '');
$urls = $this->buildNewsUrls($pathSegment, $settings);
return [
'uid' => $uid,
'title' => (string)($news['title'] ?? ''),
'alternativeTitle' => (string)($news['alternative_title'] ?? ''),
'pathSegment' => $pathSegment,
'detailUrl' => $urls['detailUrl'],
'canonicalUrl' => $urls['canonicalUrl'],
'teaser' => (string)($news['teaser'] ?? ''),
'bodytext' => (string)($news['bodytext'] ?? ''),
'datetime' => (int)($news['datetime'] ?? 0),
'archive' => (int)($news['archive'] ?? 0),
'istopnews' => (bool)($news['istopnews'] ?? false),
'author' => (string)($news['author'] ?? ''),
'authorEmail' => (string)($news['author_email'] ?? ''),
'categories' => $this->resolveCategories($uid),
'tags' => $this->resolveTags($uid),
'media' => $this->resolveFiles('tx_news_domain_model_news', 'fal_media', $uid),
'relatedFiles' => $this->resolveFiles('tx_news_domain_model_news', 'fal_related_files', $uid),
'related' => $this->resolveRelatedNews($uid),
'relatedLinks' => $this->resolveRelatedLinks($uid),
];
}
/**
* @param array<string,mixed> $settings
* @return array{detailUrl:string, canonicalUrl:string}
*/
private function buildNewsUrls(string $pathSegment, array $settings): array
{
$pathSegment = trim($pathSegment, '/');
if ($pathSegment === '') {
return ['detailUrl' => '', 'canonicalUrl' => ''];
}
$detailPid = (int)($settings['detailPid'] ?? 0);
if ($detailPid <= 0) {
$detailPid = $this->resolveCurrentPageId();
}
$detailPageSlug = $detailPid > 0 ? $this->resolvePageSlug($detailPid) : '';
if ($detailPageSlug === '') {
return ['detailUrl' => '', 'canonicalUrl' => ''];
}
$detailUrl = rtrim($detailPageSlug, '/') . '/' . $pathSegment;
$canonicalUrl = $this->absoluteUrl($detailUrl);
return [
'detailUrl' => $detailUrl,
'canonicalUrl' => $canonicalUrl,
];
}
private function resolvePageSlug(int $pageId): string
{
if ($pageId <= 0) {
return '';
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('pages');
$row = $queryBuilder
->select('slug')
->from('pages')
->where(
$queryBuilder->expr()->eq('uid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
return (string)($row['slug'] ?? '');
}
private function absoluteUrl(string $path): string
{
$path = '/' . ltrim($path, '/');
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$normalizedParams = $request->getAttribute('normalizedParams');
if ($normalizedParams !== null) {
return rtrim($normalizedParams->getSiteUrl(), '/') . $path;
}
}
$site = $request?->getAttribute('site');
if ($site !== null) {
return rtrim((string)$site->getBase(), '/') . $path;
}
return $path;
}
/**
* @return array<int,array<string,mixed>>
*/
private function resolveCategories(int $newsUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
return $queryBuilder
->select('c.uid', 'c.title')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($newsUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('mm.tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news', ParameterType::STRING)),
$queryBuilder->expr()->eq('mm.fieldname', $queryBuilder->createNamedParameter('categories', ParameterType::STRING))
)
->orderBy('c.title', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
/**
* @return array<int,array<string,mixed>>
*/
private function resolveTags(int $newsUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_tag');
return $queryBuilder
->select('t.uid', 't.title')
->from('tx_news_domain_model_tag', 't')
->join(
't',
'tx_news_domain_model_news_tag_mm',
'mm',
'mm.uid_foreign = t.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($newsUid, ParameterType::INTEGER))
)
->orderBy('t.title', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
/**
* @param list<int> $categoryUids
* @return array<int,true>
*/
private function newsUidsForCategories(array $categoryUids, bool $includeSubCategories): array
{
if ($categoryUids === []) {
return [];
}
$categories = $includeSubCategories ? $this->expandCategoryUids($categoryUids) : $categoryUids;
if ($categories === []) {
return [];
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category_record_mm');
$uids = $queryBuilder
->select('uid_foreign')
->from('sys_category_record_mm')
->where(
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter('tx_news_domain_model_news', ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter('categories', ParameterType::STRING)),
$queryBuilder->expr()->in('uid_local', $queryBuilder->createNamedParameter($categories, ArrayParameterType::INTEGER))
)
->executeQuery()
->fetchFirstColumn();
return array_fill_keys(array_map('intval', $uids), true);
}
/**
* @param list<int> $tagUids
* @return array<int,true>
*/
private function newsUidsForTags(array $tagUids): array
{
if ($tagUids === []) {
return [];
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news_tag_mm');
$uids = $queryBuilder
->select('uid_local')
->from('tx_news_domain_model_news_tag_mm')
->where(
$queryBuilder->expr()->in('uid_foreign', $queryBuilder->createNamedParameter($tagUids, ArrayParameterType::INTEGER))
)
->executeQuery()
->fetchFirstColumn();
return array_fill_keys(array_map('intval', $uids), true);
}
/**
* @return array<int,array<string,mixed>>
*/
private function resolveRelatedNews(int $newsUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
return $queryBuilder
->select('n.uid', 'n.title', 'n.path_segment')
->from('tx_news_domain_model_news', 'n')
->join(
'n',
'tx_news_domain_model_news_related_mm',
'mm',
'mm.uid_foreign = n.uid'
)
->where(
$queryBuilder->expr()->eq('mm.uid_local', $queryBuilder->createNamedParameter($newsUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('n.deleted', 0),
$queryBuilder->expr()->eq('n.hidden', 0)
)
->orderBy('n.datetime', 'DESC')
->executeQuery()
->fetchAllAssociative();
}
/**
* @return array<int,array<string,mixed>>
*/
private function resolveRelatedLinks(int $newsUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_link');
return $queryBuilder
->select('l.uid', 'l.title', 'l.description', 'l.uri')
->from('tx_news_domain_model_link', 'l')
->where(
$queryBuilder->expr()->eq('l.parent', $queryBuilder->createNamedParameter($newsUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('l.deleted', 0),
$queryBuilder->expr()->eq('l.hidden', 0)
)
->orderBy('l.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
}
/**
* @return array<int,array<string,mixed>>
*/
private function resolveFiles(string $tableName, string $fieldName, int $uid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$rows = $queryBuilder
->select('uid')
->from('sys_file_reference')
->where(
$queryBuilder->expr()->eq('uid_foreign', $queryBuilder->createNamedParameter($uid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('tablenames', $queryBuilder->createNamedParameter($tableName, ParameterType::STRING)),
$queryBuilder->expr()->eq('fieldname', $queryBuilder->createNamedParameter($fieldName, ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->orderBy('sorting_foreign', 'ASC')
->executeQuery()
->fetchAllAssociative();
if ($rows === []) {
return [];
}
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$files = [];
foreach ($rows as $row) {
try {
$fileReference = $resourceFactory->getFileReferenceObject((int)$row['uid']);
$processed = $imageService->applyProcessingInstructions($fileReference, ['width' => 1400]);
$files[] = [
'uid' => (int)$row['uid'],
'url' => $imageService->getImageUri($processed),
];
} catch (\Throwable $e) {
continue;
}
}
return $files;
}
private function resolveNewsUid(string $rawValue): int
{
$rawValue = trim($rawValue);
if ($rawValue === '') {
return 0;
}
if (ctype_digit($rawValue)) {
return (int)$rawValue;
}
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_news_domain_model_news');
$row = $queryBuilder
->select('uid')
->from('tx_news_domain_model_news')
->where(
$queryBuilder->expr()->eq('path_segment', $queryBuilder->createNamedParameter($rawValue, ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
return (int)($row['uid'] ?? 0);
}
/**
* @return list<int>
*/
private function parseUidList(string $rawValue): array
{
$values = array_filter(array_map('trim', explode(',', $rawValue)));
return array_values(array_filter(array_map(static fn (string $value): int => (int)$value, $values)));
}
/**
* @param list<int> $categoryUids
* @return list<int>
*/
private function expandCategoryUids(array $categoryUids): array
{
if ($categoryUids === []) {
return [];
}
$allUids = array_fill_keys($categoryUids, true);
$queue = $categoryUids;
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
while ($queue !== []) {
$currentUid = array_shift($queue);
if ($currentUid === null) {
continue;
}
$children = $queryBuilder
->select('uid')
->from('sys_category')
->where(
$queryBuilder->expr()->eq('parent', $queryBuilder->createNamedParameter($currentUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('deleted', 0)
)
->executeQuery()
->fetchFirstColumn();
foreach ($children as $childUid) {
$childUid = (int)$childUid;
if ($childUid <= 0 || isset($allUids[$childUid])) {
continue;
}
$allUids[$childUid] = true;
$queue[] = $childUid;
}
}
return array_values(array_map('intval', array_keys($allUids)));
}
private function resolveCurrentPageId(): int
{
$request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) {
$pageInfo = $request->getAttribute('frontend.page.information');
if ($pageInfo !== null) {
return (int)$pageInfo->getId();
}
}
return (int)($GLOBALS['TSFE']->id ?? 0);
}
/**
* @param array<string,mixed> $contentElement
* @param array<string,mixed> $settings
* @return array<string,mixed>
*/
private function buildSettingsPayload(array $contentElement, array $settings): array
{
$payload = $settings;
$payload['type'] = (string)($contentElement['CType'] ?? '');
$payload['detailPid'] = (int)($settings['detailPid'] ?? 0);
$payload['listPid'] = (int)($settings['listPid'] ?? 0);
$payload['templateLayout'] = $this->resolveTemplateLayoutValue((string)($contentElement['CType'] ?? ''), (string)($settings['templateLayout'] ?? ''));
$payload['templateLayoutLabel'] = self::TEMPLATE_LAYOUTS[$payload['templateLayout']] ?? '';
$payload['templateLayouts'] = array_map(
static fn (string $label, string $key): array => ['key' => $key, 'label' => $label],
self::TEMPLATE_LAYOUTS,
array_keys(self::TEMPLATE_LAYOUTS)
);
return $payload;
}
private function resolveTemplateLayoutValue(string $type, string $value): string
{
$value = trim($value);
if ($value !== '' && isset(self::TEMPLATE_LAYOUTS[$value])) {
return $value;
}
return self::TEMPLATE_LAYOUT_DEFAULTS[$type] ?? '';
}
private function resolveSearchTerm(string $type): string
{
if ($type !== 'news_newssearchresult') {
return '';
}
$queryParams = $GLOBALS['TYPO3_REQUEST']?->getQueryParams() ?? [];
$search = $queryParams['tx_news_pi1']['search']['sword'] ?? '';
return trim((string)$search);
}
}
@@ -93,6 +93,24 @@ final class PageJsonLdRenderer
$hasOrganization $hasOrganization
); );
} }
// FAQPage when the page contains at least one vitec_faq element.
$faqItems = $this->collectFaqItems($pageId);
if ($faqItems !== []) {
$nodes[] = $service->buildFaqPage($faqItems, $base);
}
// Event nodes when the page contains a vitec_eventlist element.
foreach ($this->collectUpcomingEvents($pageId) as $eventRow) {
$nodes[] = $service->buildEvent(
$eventRow,
$base,
$this->eventImageUrls((int)$eventRow['uid'], $base, $service),
$hasOrganization
);
}
return $service->encodeGraph($nodes); return $service->encodeGraph($nodes);
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -225,4 +243,171 @@ final class PageJsonLdRenderer
return $urls; return $urls;
} }
/**
* Collect question/answer pairs from all visible vitec_faq elements on
* this page (Content Blocks collection child table).
*
* @return list<array{question:string,answer:string}>
*/
private function collectFaqItems(int $pageId): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$elements = $qb
->select('uid')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_faq', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAllAssociative();
if ($elements === []) {
return [];
}
$items = [];
foreach ($elements as $element) {
$childQb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('vitec_faq_items');
$rows = $childQb
->select('*')
->from('vitec_faq_items')
->where(
$childQb->expr()->eq('foreign_table_parent_uid', $childQb->createNamedParameter((int)$element['uid'], ParameterType::INTEGER)),
$childQb->expr()->eq('deleted', 0),
$childQb->expr()->eq('hidden', 0)
)
->orderBy('sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($rows as $row) {
$items[] = [
'question' => (string)($row['question'] ?? $row['vitec_question'] ?? ''),
'answer' => (string)($row['answer'] ?? $row['vitec_answer'] ?? ''),
];
}
}
return $items;
} catch (\Throwable $e) {
// Table may not exist yet (schema not updated) — skip silently.
return [];
}
}
/**
* Upcoming event rows when this page contains at least one visible
* vitec_eventlist element. Empty array otherwise.
*
* @return list<array<string,mixed>>
*/
private function collectUpcomingEvents(int $pageId): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tt_content');
$eventlistElement = $qb
->select('uid', 'pi_flexform')
->from('tt_content')
->where(
$qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$qb->expr()->eq('CType', $qb->createNamedParameter('vitec_eventlist', ParameterType::STRING)),
$qb->expr()->eq('deleted', 0),
$qb->expr()->eq('hidden', 0)
)
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$eventlistElement) {
return [];
}
// Mirror the plugin's "days in advance" window so structured data
// matches what the page actually shows.
$daysInAdvance = 0;
$flexFormService = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Service\FlexFormService::class);
$flexSettings = $flexFormService->convertFlexFormContentToArray((string)($eventlistElement['pi_flexform'] ?? ''))['settings'] ?? [];
$daysInAdvance = (int)($flexSettings['daysinadvance'] ?? 0);
$todayMidnight = strtotime('today');
$eventQb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_event');
$expr = $eventQb->expr();
$eventQb
->select('*')
->from('tx_vitec_domain_model_event')
->where(
$expr->eq('deleted', 0),
$expr->eq('hidden', 0),
$expr->eq('hideonwebsite', 0),
$expr->or(
$expr->gte('eventend', $eventQb->createNamedParameter($todayMidnight, ParameterType::INTEGER)),
$expr->and(
$expr->eq('eventend', 0),
$expr->gte('eventstart', $eventQb->createNamedParameter($todayMidnight, ParameterType::INTEGER))
)
)
)
->orderBy('eventstart', 'ASC');
if ($daysInAdvance > 0) {
$eventQb->andWhere(
$expr->lt('eventstart', $eventQb->createNamedParameter($todayMidnight + ($daysInAdvance * 86400), ParameterType::INTEGER))
);
}
return $eventQb->executeQuery()->fetchAllAssociative();
} catch (\Throwable $e) {
// Table may not exist yet — skip silently.
return [];
}
}
/**
* Root-relative logo URLs for an event (fieldname=image).
*
* @return list<string>
*/
private function eventImageUrls(int $eventUid, string $base, $service): array
{
try {
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$rows = $qb
->select('f.identifier')
->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where(
$qb->expr()->eq('fr.tablenames', $qb->createNamedParameter('tx_vitec_domain_model_event', ParameterType::STRING)),
$qb->expr()->eq('fr.fieldname', $qb->createNamedParameter('image', ParameterType::STRING)),
$qb->expr()->eq('fr.uid_foreign', $qb->createNamedParameter($eventUid, ParameterType::INTEGER)),
$qb->expr()->eq('fr.deleted', 0),
$qb->expr()->eq('f.missing', 0)
)
->executeQuery()
->fetchAllAssociative();
$urls = [];
foreach ($rows as $row) {
$identifier = (string)($row['identifier'] ?? '');
if ($identifier !== '') {
$urls[] = '/fileadmin' . $identifier;
}
}
return $urls;
} catch (\Throwable $e) {
return [];
}
}
} }
@@ -9,6 +9,7 @@ use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Domain\Repository\ProductRepository; use Evomedien\Vitec\Domain\Repository\ProductRepository;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\Connection; use TYPO3\CMS\Core\Database\Connection;
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection; use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\FileReference; use TYPO3\CMS\Core\Resource\FileReference;
@@ -141,6 +142,7 @@ class ProductListJsonRenderer
'categoryUids' => $categoryUids, 'categoryUids' => $categoryUids,
'productCount' => count($productsData), 'productCount' => count($productsData),
'settings' => $settings, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromProducts($productsData),
], ],
]); ]);
} }
@@ -413,6 +415,168 @@ class ProductListJsonRenderer
}, $categories); }, $categories);
} }
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.filetype')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0),
$qb->expr()->eq('c.parent', $qb->createNamedParameter(4, ParameterType::INTEGER))
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($categories as $category) {
$filetype = trim((string)($category['filetype'] ?? ''));
if ($filetype !== '') {
return $filetype;
}
}
return '';
}
/**
* @return array<string,mixed>|null
*/
private function resolveFileByConvention(string $fileprefix, string $filetype): ?array
{
$fileprefix = trim($fileprefix);
$filetype = trim($filetype);
if ($fileprefix === '' || $filetype === '') {
return null;
}
$baseDir = Environment::getPublicPath() . '/fileadmin/downloads/Collateral';
if (!is_dir($baseDir) || !is_readable($baseDir)) {
return null;
}
$escapedPrefix = preg_quote($fileprefix, '/');
$escapedType = preg_quote($filetype, '/');
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
$bestFile = null;
$bestNumber = -1;
$bestLetterRank = -1;
$entries = scandir($baseDir);
if ($entries === false) {
return null;
}
foreach ($entries as $entry) {
if (!is_string($entry) || $entry === '.' || $entry === '..') {
continue;
}
if (!preg_match($pattern, $entry, $matches)) {
continue;
}
$number = (int)$matches[1];
$letterRank = $this->letterSequenceToRank((string)$matches[2]);
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
$bestNumber = $number;
$bestLetterRank = $letterRank;
$bestFile = $entry;
}
}
if ($bestFile === null) {
return null;
}
$fullPath = $baseDir . '/' . $bestFile;
if (!is_file($fullPath) || !is_readable($fullPath)) {
return null;
}
$extension = strtolower(pathinfo($bestFile, PATHINFO_EXTENSION));
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
return [
'uid' => 0,
'name' => $bestFile,
'url' => '/fileadmin/downloads/Collateral/' . rawurlencode($bestFile),
'size' => (int)(filesize($fullPath) ?: 0),
'extension' => $extension,
'mimeType' => $mimeType,
];
}
private function letterSequenceToRank(string $letters): int
{
$rank = 0;
$letters = strtoupper($letters);
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$char = ord($letters[$i]);
if ($char < 65 || $char > 90) {
continue;
}
$rank = ($rank * 26) + ($char - 64);
}
return $rank;
}
/**
* @param array<int,array<string,mixed>> $products
* @return array<int,array<string,mixed>>
*/
private function collectFallbackFilesFromProducts(array $products): array
{
$fallbackFiles = [];
foreach ($products as $product) {
$downloads = $product['downloads'] ?? null;
if (!is_array($downloads)) {
continue;
}
foreach ($downloads as $download) {
if (!is_array($download)) {
continue;
}
$file = $download['file'] ?? null;
if (!is_array($file) || (int)($file['uid'] ?? 1) !== 0) {
continue;
}
$fallbackFiles[] = [
'productUid' => (int)($product['uid'] ?? 0),
'downloadUid' => (int)($download['uid'] ?? 0),
'name' => (string)($file['name'] ?? ''),
'url' => (string)($file['url'] ?? ''),
'source' => 'naming-convention-fallback',
];
}
}
return $fallbackFiles;
}
protected function getProductDownloads(int $productUid): array protected function getProductDownloads(int $productUid): array
{ {
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) $queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
@@ -440,6 +604,9 @@ class ProductListJsonRenderer
$result = []; $result = [];
foreach ($downloads as $download) { foreach ($downloads as $download) {
$fileInfo = null; $fileInfo = null;
$downloadUid = (int)$download['uid'];
$fileprefix = (string)($download['fileprefix'] ?? '');
$filetype = $this->getDownloadFileType($downloadUid);
if (!empty($download['file'])) { if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) $fileQueryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class)
@@ -450,7 +617,7 @@ class ProductListJsonRenderer
->from('sys_file_reference', 'fr') ->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid') ->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where( ->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)), $fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)), $fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)), $fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0), $fileQueryBuilder->expr()->eq('fr.deleted', 0),
@@ -473,14 +640,20 @@ class ProductListJsonRenderer
} }
} }
if ($fileInfo === null) {
$fileInfo = $this->resolveFileByConvention($fileprefix, $filetype);
}
$result[] = [ $result[] = [
'uid' => (int)$download['uid'], 'uid' => $downloadUid,
'title' => $download['title'] ?? '', 'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '', 'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '', 'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '', 'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '', 'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '', 'icon' => $download['icon'] ?? '',
'fileprefix' => $fileprefix,
'filetype' => $filetype,
'file' => $fileInfo, 'file' => $fileInfo,
]; ];
} }
@@ -7,6 +7,7 @@ namespace Evomedien\Vitec\UserFunc;
use Evomedien\Vitec\Service\StructuredDataService; use Evomedien\Vitec\Service\StructuredDataService;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable; use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Core\Environment;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
@@ -160,6 +161,7 @@ class ProductShowJsonRenderer
'productUid' => $productUid, 'productUid' => $productUid,
'layout' => $layout, 'layout' => $layout,
'settings' => $settings, 'settings' => $settings,
'fallbackFiles' => $this->collectFallbackFilesFromDownloads((array)($response['product']['downloads'] ?? [])),
]; ];
} }
@@ -495,6 +497,160 @@ class ProductShowJsonRenderer
}, $categories); }, $categories);
} }
private function getDownloadFileType(int $downloadUid): string
{
$qb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $qb
->select('c.filetype')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$qb->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING) .
' AND mm.fieldname = ' .
$qb->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$qb->expr()->eq('mm.uid_foreign', $qb->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$qb->expr()->eq('c.deleted', 0),
$qb->expr()->eq('c.hidden', 0),
$qb->expr()->eq('c.parent', $qb->createNamedParameter(4, ParameterType::INTEGER))
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
foreach ($categories as $category) {
$filetype = trim((string)($category['filetype'] ?? ''));
if ($filetype !== '') {
return $filetype;
}
}
return '';
}
/**
* @return array<string,mixed>|null
*/
private function resolveFileByConvention(string $fileprefix, string $filetype): ?array
{
$fileprefix = trim($fileprefix);
$filetype = trim($filetype);
if ($fileprefix === '' || $filetype === '') {
return null;
}
$baseDir = Environment::getPublicPath() . '/fileadmin/downloads/Collateral';
if (!is_dir($baseDir) || !is_readable($baseDir)) {
return null;
}
$escapedPrefix = preg_quote($fileprefix, '/');
$escapedType = preg_quote($filetype, '/');
$pattern = '/^' . $escapedPrefix . '__' . $escapedType . '__(\\d+)-([A-Za-z]+)\\.([A-Za-z0-9]+)$/';
$bestFile = null;
$bestNumber = -1;
$bestLetterRank = -1;
$entries = scandir($baseDir);
if ($entries === false) {
return null;
}
foreach ($entries as $entry) {
if (!is_string($entry) || $entry === '.' || $entry === '..') {
continue;
}
if (!preg_match($pattern, $entry, $matches)) {
continue;
}
$number = (int)$matches[1];
$letterRank = $this->letterSequenceToRank((string)$matches[2]);
if ($number > $bestNumber || ($number === $bestNumber && $letterRank > $bestLetterRank)) {
$bestNumber = $number;
$bestLetterRank = $letterRank;
$bestFile = $entry;
}
}
if ($bestFile === null) {
return null;
}
$fullPath = $baseDir . '/' . $bestFile;
if (!is_file($fullPath) || !is_readable($fullPath)) {
return null;
}
$extension = strtolower(pathinfo($bestFile, PATHINFO_EXTENSION));
$mimeType = function_exists('mime_content_type') ? (string)(mime_content_type($fullPath) ?: '') : '';
return [
'uid' => 0,
'name' => $bestFile,
'url' => '/fileadmin/downloads/Collateral/' . rawurlencode($bestFile),
'size' => (int)(filesize($fullPath) ?: 0),
'extension' => $extension,
'mimeType' => $mimeType,
];
}
private function letterSequenceToRank(string $letters): int
{
$rank = 0;
$letters = strtoupper($letters);
$length = strlen($letters);
for ($i = 0; $i < $length; $i++) {
$char = ord($letters[$i]);
if ($char < 65 || $char > 90) {
continue;
}
$rank = ($rank * 26) + ($char - 64);
}
return $rank;
}
/**
* @param array<int,array<string,mixed>> $downloads
* @return array<int,array<string,mixed>>
*/
private function collectFallbackFilesFromDownloads(array $downloads): array
{
$fallbackFiles = [];
foreach ($downloads as $download) {
if (!is_array($download)) {
continue;
}
$file = $download['file'] ?? null;
if (!is_array($file) || (int)($file['uid'] ?? 1) !== 0) {
continue;
}
$fallbackFiles[] = [
'downloadUid' => (int)($download['uid'] ?? 0),
'name' => (string)($file['name'] ?? ''),
'url' => (string)($file['url'] ?? ''),
'source' => 'naming-convention-fallback',
];
}
return $fallbackFiles;
}
protected function getProductDownloads(int $productUid): array protected function getProductDownloads(int $productUid): array
{ {
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
@@ -522,6 +678,9 @@ class ProductShowJsonRenderer
$result = []; $result = [];
foreach ($downloads as $download) { foreach ($downloads as $download) {
$fileInfo = null; $fileInfo = null;
$downloadUid = (int)$download['uid'];
$fileprefix = (string)($download['fileprefix'] ?? '');
$filetype = $this->getDownloadFileType($downloadUid);
if (!empty($download['file'])) { if (!empty($download['file'])) {
$fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) $fileQueryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
@@ -532,7 +691,7 @@ class ProductShowJsonRenderer
->from('sys_file_reference', 'fr') ->from('sys_file_reference', 'fr')
->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid') ->join('fr', 'sys_file', 'f', 'fr.uid_local = f.uid')
->where( ->where(
$fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter((int)$download['uid'], ParameterType::INTEGER)), $fileQueryBuilder->expr()->eq('fr.uid_foreign', $fileQueryBuilder->createNamedParameter($downloadUid, ParameterType::INTEGER)),
$fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)), $fileQueryBuilder->expr()->eq('fr.tablenames', $fileQueryBuilder->createNamedParameter('tx_vitec_domain_model_download', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)), $fileQueryBuilder->expr()->eq('fr.fieldname', $fileQueryBuilder->createNamedParameter('file', ParameterType::STRING)),
$fileQueryBuilder->expr()->eq('fr.deleted', 0), $fileQueryBuilder->expr()->eq('fr.deleted', 0),
@@ -555,14 +714,20 @@ class ProductShowJsonRenderer
} }
} }
if ($fileInfo === null) {
$fileInfo = $this->resolveFileByConvention($fileprefix, $filetype);
}
$result[] = [ $result[] = [
'uid' => (int)$download['uid'], 'uid' => $downloadUid,
'title' => $download['title'] ?? '', 'title' => $download['title'] ?? '',
'slug' => $download['slug'] ?? '', 'slug' => $download['slug'] ?? '',
'teaser' => $download['teaser'] ?? '', 'teaser' => $download['teaser'] ?? '',
'description' => $download['description'] ?? '', 'description' => $download['description'] ?? '',
'keywords' => $download['keywords'] ?? '', 'keywords' => $download['keywords'] ?? '',
'icon' => $download['icon'] ?? '', 'icon' => $download['icon'] ?? '',
'fileprefix' => $fileprefix,
'filetype' => $filetype,
'file' => $fileInfo, 'file' => $fileInfo,
]; ];
} }
@@ -4,34 +4,33 @@ declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc; namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Service\UsecaseSerializer;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/** /**
* UserFunc to render the usecase list as JSON for headless output. * UserFunc: render the Success Story list as JSON (headless).
* *
* `render()` = page discovery (top-level plugin). `renderForRecord()` = * `render()` = top-level plugin / page discovery. `renderForRecord()` = one
* one specific tt_content row, reused by ContainerChildrenProcessor for * specific tt_content row (reused by ContainerChildrenProcessor for nested
* usecase-list plugins nested in a b13 container. Exception-safe. * usecase-list plugins). All serialisation is delegated to UsecaseSerializer.
* Exception-safe.
*/ */
class UsecaseListJsonRenderer class UsecaseListJsonRenderer
{ {
private const TABLE = 'tx_vitec_domain_model_usecase';
#[AsAllowedCallable] #[AsAllowedCallable]
public function render(string $content, array $conf): string public function render(string $content, array $conf): string
{ {
// 1) cObj data path
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null; $row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaselist') { if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaselist') {
return $this->renderForRecord($row); return $this->renderForRecord($row);
} }
// 2) Page-id via v14 request attribute (TSFE->id is often null in JSON cObj context)
$pageId = 0; $pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null; $request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) { if ($request !== null) {
@@ -47,29 +46,26 @@ class UsecaseListJsonRenderer
return ''; return '';
} }
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
->getQueryBuilderForTable('tt_content'); $ces = $qb
$contentElements = $queryBuilder
->select('*') ->select('*')
->from('tt_content') ->from('tt_content')
->where( ->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('vitec_usecaselist', ParameterType::STRING)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_usecaselist', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0), $qb->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0) $qb->expr()->eq('hidden', 0)
) )
->executeQuery() ->executeQuery()
->fetchAllAssociative(); ->fetchAllAssociative();
if (empty($contentElements)) { if (empty($ces)) {
return ''; return '';
} }
return $this->renderForRecord($contentElements[0]); return $this->renderForRecord($ces[0]);
} }
/** /**
* @param array<string,mixed> $contentElement * @param array<string,mixed> $contentElement
*/ */
@@ -81,167 +77,36 @@ class UsecaseListJsonRenderer
$settings = $flexFormData['settings'] ?? []; $settings = $flexFormData['settings'] ?? [];
$debugMode = (bool)($settings['debug'] ?? false); $debugMode = (bool)($settings['debug'] ?? false);
$usecaseQb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
->getQueryBuilderForTable('tx_vitec_domain_model_usecase'); $rows = $qb
->select('*')
$usecases = $usecaseQb ->from(self::TABLE)
->select('u.*')
->from('tx_vitec_domain_model_usecase', 'u')
->where( ->where(
$usecaseQb->expr()->eq('u.deleted', 0), $qb->expr()->eq('deleted', 0),
$usecaseQb->expr()->eq('u.hidden', 0), $qb->expr()->eq('hidden', 0),
$usecaseQb->expr()->eq('u.hideonwebsite', 0) $qb->expr()->eq('hideonwebsite', 0)
) )
->orderBy('u.title', 'ASC') ->orderBy('featured', 'DESC')
->addOrderBy('title', 'ASC')
->executeQuery() ->executeQuery()
->fetchAllAssociative(); ->fetchAllAssociative();
$usecasesData = []; $serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
foreach ($usecases as $usecase) { $usecases = array_map(
$usecasesData[] = $this->serializeUsecase($usecase); static fn (array $u): array => $serializer->serializeListItem($u),
} $rows
);
if ($debugMode) { if ($debugMode) {
return json_encode([ return (string)json_encode([
'usecases' => $usecasesData, 'usecases' => $usecases,
'debug' => [ 'debug' => ['count' => count($usecases), 'settings' => $settings],
'pageId' => (int)($GLOBALS['TSFE']->id ?? 0),
'usecaseCount' => count($usecasesData),
'settings' => $settings,
],
]); ]);
} }
return json_encode($usecasesData); return (string)json_encode($usecases);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return ''; return '';
} }
} }
/**
* @param array<string,mixed> $usecase
* @return array<string,mixed>
*/
protected function serializeUsecase(array $usecase): array
{
$uid = (int)$usecase['uid'];
return [
'uid' => $uid,
'title' => (string)($usecase['title'] ?? ''),
'slug' => (string)($usecase['slug'] ?? ''),
'subtitle' => (string)($usecase['subtitle'] ?? ''),
'teaser' => (string)($usecase['teaser'] ?? ''),
'description' => (string)($usecase['description'] ?? ''),
'singlepid' => (string)($usecase['singlepid'] ?? ''),
'hideonapp' => (bool)($usecase['hideonapp'] ?? false),
'hideonwebsite' => (bool)($usecase['hideonwebsite'] ?? false),
'categories' => $this->getUsecaseCategories($uid),
'caseimage' => $this->getUsecaseImage($uid, 'caseimage'),
'logoimage' => $this->getUsecaseImage($uid, 'logoimage'),
];
}
/**
* @return array<string,mixed>|null
*/
protected function getUsecaseImage(int $usecaseUid, string $fieldName): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $queryBuilder
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_usecase', ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter($fieldName, ParameterType::STRING)),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
)
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery()
->fetchAssociative();
if (!$fileRefData) {
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$srcset = [];
foreach ([400, 800, 1200, 1600] as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $fileRefData['crop'] ?? null]
);
$srcset[] = [
'url' => $imageService->getImageUri($variant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($default),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
protected function getUsecaseCategories(int $usecaseUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_usecase', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
];
}, $categories);
}
} }
@@ -4,34 +4,32 @@ declare(strict_types=1);
namespace Evomedien\Vitec\UserFunc; namespace Evomedien\Vitec\UserFunc;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use Doctrine\DBAL\ParameterType; use Doctrine\DBAL\ParameterType;
use Evomedien\Vitec\Service\UsecaseSerializer;
use TYPO3\CMS\Core\Attribute\AsAllowedCallable;
use TYPO3\CMS\Core\Database\ConnectionPool; use TYPO3\CMS\Core\Database\ConnectionPool;
use TYPO3\CMS\Core\Resource\ResourceFactory;
use TYPO3\CMS\Core\Service\FlexFormService; use TYPO3\CMS\Core\Service\FlexFormService;
use TYPO3\CMS\Core\Utility\GeneralUtility; use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Service\ImageService;
/** /**
* UserFunc to render a single usecase as JSON for headless output. * UserFunc: render a single Success Story as JSON (headless).
* *
* `render()` = page discovery (top-level plugin). `renderForRecord()` = * The story is chosen from the plugin FlexForm (settings.usecase) or, when
* one specific tt_content row, reused by ContainerChildrenProcessor for * empty, from the request param tx_vitec_usecaseshow[usecase] (uid or slug).
* usecase-show plugins nested in a b13 container. Exception-safe. * Full serialisation is delegated to UsecaseSerializer. Exception-safe.
*/ */
class UsecaseShowJsonRenderer class UsecaseShowJsonRenderer
{ {
private const TABLE = 'tx_vitec_domain_model_usecase';
#[AsAllowedCallable] #[AsAllowedCallable]
public function render(string $content, array $conf): string public function render(string $content, array $conf): string
{ {
// 1) cObj data path
$row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null; $row = is_array($this->cObj->data ?? null) ? $this->cObj->data : null;
if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaseshow') { if ($row && (string)($row['CType'] ?? '') === 'vitec_usecaseshow') {
return $this->renderForRecord($row); return $this->renderForRecord($row);
} }
// 2) Page-id via v14 request attribute (TSFE->id is often null in JSON cObj context)
$pageId = 0; $pageId = 0;
$request = $GLOBALS['TYPO3_REQUEST'] ?? null; $request = $GLOBALS['TYPO3_REQUEST'] ?? null;
if ($request !== null) { if ($request !== null) {
@@ -47,243 +45,104 @@ class UsecaseShowJsonRenderer
return ''; return '';
} }
$queryBuilder = GeneralUtility::makeInstance(\TYPO3\CMS\Core\Database\ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable('tt_content');
->getQueryBuilderForTable('tt_content'); $ces = $qb
$contentElements = $queryBuilder
->select('*') ->select('*')
->from('tt_content') ->from('tt_content')
->where( ->where(
$queryBuilder->expr()->eq('pid', $queryBuilder->createNamedParameter($pageId, ParameterType::INTEGER)), $qb->expr()->eq('pid', $qb->createNamedParameter($pageId, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('CType', $queryBuilder->createNamedParameter('vitec_usecaseshow', ParameterType::STRING)), $qb->expr()->eq('CType', $qb->createNamedParameter('vitec_usecaseshow', ParameterType::STRING)),
$queryBuilder->expr()->eq('deleted', 0), $qb->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('hidden', 0) $qb->expr()->eq('hidden', 0)
) )
->executeQuery() ->executeQuery()
->fetchAllAssociative(); ->fetchAllAssociative();
if (empty($contentElements)) { if (empty($ces)) {
return ''; return '';
} }
return $this->renderForRecord($contentElements[0]); return $this->renderForRecord($ces[0]);
} }
/** /**
* @param array<string,mixed> $contentElement * @param array<string,mixed> $contentElement
*/ */
public function renderForRecord(array $contentElement): string public function renderForRecord(array $contentElement): string
{ {
try { try {
$pageId = (int)($GLOBALS['TSFE']->id ?? 0);
$flexFormService = GeneralUtility::makeInstance(FlexFormService::class); $flexFormService = GeneralUtility::makeInstance(FlexFormService::class);
$flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? ''); $flexFormData = $flexFormService->convertFlexFormContentToArray($contentElement['pi_flexform'] ?? '');
$settings = $flexFormData['settings'] ?? []; $settings = $flexFormData['settings'] ?? [];
$usecaseUid = (int)($settings['usecase'] ?? 0);
$layout = (string)($settings['layout'] ?? 'default'); $layout = (string)($settings['layout'] ?? 'default');
$debugMode = (bool)($settings['debug'] ?? false); $debugMode = (bool)($settings['debug'] ?? false);
$usecaseUid = (int)($settings['usecase'] ?? 0);
// Fallback: uid/slug from the request (React detail route).
if (!$usecaseUid) { if (!$usecaseUid) {
$routeParams = $GLOBALS['TYPO3_REQUEST']->getQueryParams(); $params = $GLOBALS['TYPO3_REQUEST']?->getQueryParams() ?? [];
$usecaseParam = $routeParams['tx_vitec_usecaseshow']['usecase'] ?? null; $param = $params['tx_vitec_usecaseshow']['usecase'] ?? null;
if ($param !== null && $param !== '') {
if ($usecaseParam) { $usecaseUid = is_numeric($param) ? (int)$param : $this->resolveSlug((string)$param);
if (!is_numeric($usecaseParam)) {
$slugQb = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('tx_vitec_domain_model_usecase');
$bySlug = $slugQb
->select('uid')
->from('tx_vitec_domain_model_usecase')
->where(
$slugQb->expr()->eq('slug', $slugQb->createNamedParameter($usecaseParam)),
$slugQb->expr()->eq('deleted', 0),
$slugQb->expr()->eq('hidden', 0)
)
->executeQuery()
->fetchAssociative();
$usecaseUid = (int)($bySlug['uid'] ?? 0);
} else {
$usecaseUid = (int)$usecaseParam;
}
} }
} }
if (!$usecaseUid) { if (!$usecaseUid) {
return $debugMode return $debugMode
? json_encode(['error' => 'No usecase selected or found', 'debug' => ['settings' => $settings]]) ? (string)json_encode(['error' => 'No usecase selected or found', 'debug' => ['settings' => $settings]])
: ''; : '';
} }
$usecaseQb = GeneralUtility::makeInstance(ConnectionPool::class) $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
->getQueryBuilderForTable('tx_vitec_domain_model_usecase'); $usecase = $qb
$usecase = $usecaseQb
->select('*') ->select('*')
->from('tx_vitec_domain_model_usecase') ->from(self::TABLE)
->where( ->where(
$usecaseQb->expr()->eq('uid', $usecaseQb->createNamedParameter($usecaseUid, ParameterType::INTEGER)), $qb->expr()->eq('uid', $qb->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$usecaseQb->expr()->eq('deleted', 0), $qb->expr()->eq('deleted', 0),
$usecaseQb->expr()->eq('hidden', 0) $qb->expr()->eq('hidden', 0)
) )
->executeQuery() ->executeQuery()
->fetchAssociative(); ->fetchAssociative();
if (!$usecase) { if (!$usecase) {
return $debugMode return $debugMode
? json_encode(['error' => 'Usecase not found', 'debug' => ['usecaseUid' => $usecaseUid]]) ? (string)json_encode(['error' => 'Usecase not found', 'debug' => ['usecaseUid' => $usecaseUid]])
: ''; : '';
} }
$serializer = GeneralUtility::makeInstance(UsecaseSerializer::class);
$response = [ $response = [
'usecase' => $this->serializeUsecase($usecase), 'usecase' => $serializer->serializeDetail($usecase),
'layout' => $layout, 'layout' => $layout,
'settings' => [
'layout' => $layout,
],
]; ];
if ($debugMode) { if ($debugMode) {
$response['debug'] = [ $response['debug'] = ['usecaseUid' => $usecaseUid, 'layout' => $layout, 'settings' => $settings];
'pageId' => $pageId,
'usecaseUid' => $usecaseUid,
'layout' => $layout,
'settings' => $settings,
];
} }
return json_encode($response); return (string)json_encode($response);
} catch (\Throwable $e) { } catch (\Throwable $e) {
return ''; return '';
} }
} }
/** private function resolveSlug(string $slug): int
* @param array<string,mixed> $usecase
* @return array<string,mixed>
*/
protected function serializeUsecase(array $usecase): array
{ {
$uid = (int)$usecase['uid']; $qb = GeneralUtility::makeInstance(ConnectionPool::class)->getQueryBuilderForTable(self::TABLE);
$row = $qb
return [ ->select('uid')
'uid' => $uid, ->from(self::TABLE)
'title' => (string)($usecase['title'] ?? ''),
'slug' => (string)($usecase['slug'] ?? ''),
'subtitle' => (string)($usecase['subtitle'] ?? ''),
'teaser' => (string)($usecase['teaser'] ?? ''),
'description' => (string)($usecase['description'] ?? ''),
'singlepid' => (string)($usecase['singlepid'] ?? ''),
'hideonapp' => (bool)($usecase['hideonapp'] ?? false),
'hideonwebsite' => (bool)($usecase['hideonwebsite'] ?? false),
'categories' => $this->getUsecaseCategories($uid),
'caseimage' => $this->getUsecaseImage($uid, 'caseimage'),
'logoimage' => $this->getUsecaseImage($uid, 'logoimage'),
];
}
/**
* @return array<string,mixed>|null
*/
protected function getUsecaseImage(int $usecaseUid, string $fieldName): ?array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_file_reference');
$fileRefData = $queryBuilder
->select('sfr.uid', 'sfr.title', 'sfr.description', 'sfr.alternative', 'sfr.crop')
->from('sys_file_reference', 'sfr')
->where( ->where(
$queryBuilder->expr()->eq('sfr.tablenames', $queryBuilder->createNamedParameter('tx_vitec_domain_model_usecase', ParameterType::STRING)), $qb->expr()->eq('slug', $qb->createNamedParameter($slug)),
$queryBuilder->expr()->eq('sfr.fieldname', $queryBuilder->createNamedParameter($fieldName, ParameterType::STRING)), $qb->expr()->eq('deleted', 0),
$queryBuilder->expr()->eq('sfr.uid_foreign', $queryBuilder->createNamedParameter($usecaseUid, ParameterType::INTEGER)), $qb->expr()->eq('hidden', 0)
$queryBuilder->expr()->eq('sfr.deleted', 0),
$queryBuilder->expr()->eq('sfr.hidden', 0)
) )
->orderBy('sfr.sorting_foreign')
->setMaxResults(1)
->executeQuery() ->executeQuery()
->fetchAssociative(); ->fetchAssociative();
if (!$fileRefData) { return (int)($row['uid'] ?? 0);
return null;
}
try {
$resourceFactory = GeneralUtility::makeInstance(ResourceFactory::class);
$imageService = GeneralUtility::makeInstance(ImageService::class);
$fileReference = $resourceFactory->getFileReferenceObject((int)$fileRefData['uid']);
$srcset = [];
foreach ([400, 800, 1200, 1600] as $width) {
$variant = $imageService->applyProcessingInstructions(
$fileReference,
['width' => $width, 'crop' => $fileRefData['crop'] ?? null]
);
$srcset[] = [
'url' => $imageService->getImageUri($variant),
'width' => $width,
'descriptor' => $width . 'w',
];
}
$default = $imageService->applyProcessingInstructions(
$fileReference,
['width' => 800, 'crop' => $fileRefData['crop'] ?? null]
);
return [
'uid' => (int)$fileRefData['uid'],
'url' => $imageService->getImageUri($default),
'title' => $fileRefData['title'] ?? '',
'alternative' => $fileRefData['alternative'] ?? '',
'description' => $fileRefData['description'] ?? '',
'srcset' => $srcset,
'properties' => [
'width' => $fileReference->getProperty('width'),
'height' => $fileReference->getProperty('height'),
'mimeType' => $fileReference->getProperty('mime_type'),
],
];
} catch (\Exception $e) {
return null;
}
}
protected function getUsecaseCategories(int $usecaseUid): array
{
$queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class)
->getQueryBuilderForTable('sys_category');
$categories = $queryBuilder
->select('c.uid', 'c.title', 'c.description')
->from('sys_category', 'c')
->join(
'c',
'sys_category_record_mm',
'mm',
'mm.uid_local = c.uid AND mm.tablenames = ' .
$queryBuilder->createNamedParameter('tx_vitec_domain_model_usecase', ParameterType::STRING) .
' AND mm.fieldname = ' .
$queryBuilder->createNamedParameter('categories', ParameterType::STRING)
)
->where(
$queryBuilder->expr()->eq('mm.uid_foreign', $queryBuilder->createNamedParameter($usecaseUid, ParameterType::INTEGER)),
$queryBuilder->expr()->eq('c.deleted', 0),
$queryBuilder->expr()->eq('c.hidden', 0)
)
->orderBy('mm.sorting', 'ASC')
->executeQuery()
->fetchAllAssociative();
return array_map(static function ($cat) {
return [
'uid' => (int)$cat['uid'],
'title' => $cat['title'] ?? '',
'description' => $cat['description'] ?? '',
];
}, $categories);
} }
} }
+96
View File
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<T3DataStructure>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Carousel Settings</sheetTitle>
<type>array</type>
<el>
<settings.slidesperview>
<label>Slides per view (desktop)</label>
<config>
<type>select</type>
<renderType>selectSingle</renderType>
<items>
<numIndex index="0">
<label>4</label>
<value>4</value>
</numIndex>
<numIndex index="1">
<label>3</label>
<value>3</value>
</numIndex>
<numIndex index="2">
<label>2</label>
<value>2</value>
</numIndex>
<numIndex index="3">
<label>1</label>
<value>1</value>
</numIndex>
</items>
<default>4</default>
</config>
</settings.slidesperview>
<settings.showarrows>
<label>Show prev/next arrows</label>
<config>
<type>check</type>
<default>1</default>
</config>
</settings.showarrows>
<settings.showindicators>
<label>Show slide indicators (dots)</label>
<config>
<type>check</type>
<default>1</default>
</config>
</settings.showindicators>
<settings.loop>
<label>Loop (wrap around at the end)</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.loop>
<settings.autoplay>
<label>Autoplay</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.autoplay>
<settings.autoplayinterval>
<label>Autoplay interval (ms)</label>
<config>
<type>number</type>
<default>5000</default>
</config>
</settings.autoplayinterval>
<settings.cssClass>
<label>Additional CSS Class</label>
<config>
<type>input</type>
<size>30</size>
<eval>trim</eval>
</config>
</settings.cssClass>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
+48
View File
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<T3DataStructure>
<meta>
<langDisable>1</langDisable>
</meta>
<sheets>
<sDEF>
<ROOT>
<sheetTitle>Event List Settings</sheetTitle>
<type>array</type>
<el>
<settings.daysinadvance>
<label>Days in advance (0 = no limit)</label>
<description>Only show upcoming events starting within the next N days, e.g. 60.</description>
<config>
<type>number</type>
<default>0</default>
</config>
</settings.daysinadvance>
<settings.showpast>
<label>Include past events</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.showpast>
<settings.limit>
<label>Maximum number of events (0 = all)</label>
<config>
<type>number</type>
<default>0</default>
</config>
</settings.limit>
<settings.debug>
<label>Allow Debug Output</label>
<config>
<type>check</type>
<default>0</default>
</config>
</settings.debug>
</el>
</ROOT>
</sDEF>
</sheets>
</T3DataStructure>
+4
View File
@@ -32,6 +32,10 @@ return [
'provider' => SvgIconProvider::class, 'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg', 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-usecaselist.svg',
], ],
'vitec-plugin-eventlist' => [
'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-eventlist.svg',
],
'vitec-plugin-downloadcard' => [ 'vitec-plugin-downloadcard' => [
'provider' => SvgIconProvider::class, 'provider' => SvgIconProvider::class,
'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg', 'source' => 'EXT:vitec/Resources/Public/Icons/vitec-plugin-downloadcard.svg',
@@ -20,7 +20,7 @@ config {
# ============================================================================= # =============================================================================
tt_content { tt_content {
vitec_productlist < lib.contentElement vitec_productlist < lib.contentElementWithHeader
vitec_productlist { vitec_productlist {
fields { fields {
content { content {
@@ -32,7 +32,28 @@ tt_content {
} }
} }
vitec_productshow < lib.contentElement news_pi1 < lib.contentElementWithHeader
news_pi1 {
fields {
content {
fields {
news = USER
news.userFunc = Evomedien\Vitec\UserFunc\NewsJsonRenderer->render
}
}
}
}
news_newsliststicky < tt_content.news_pi1
news_newsselectedlist < tt_content.news_pi1
news_newsdetail < tt_content.news_pi1
news_newsdatemenu < tt_content.news_pi1
news_categorylist < tt_content.news_pi1
news_newssearchform < tt_content.news_pi1
news_newssearchresult < tt_content.news_pi1
news_taglist < tt_content.news_pi1
vitec_productshow < lib.contentElementWithHeader
vitec_productshow { vitec_productshow {
fields { fields {
content { content {
@@ -44,7 +65,7 @@ tt_content {
} }
} }
vitec_usecaselist < lib.contentElement vitec_usecaselist < lib.contentElementWithHeader
vitec_usecaselist { vitec_usecaselist {
fields { fields {
content { content {
@@ -56,7 +77,7 @@ tt_content {
} }
} }
vitec_usecaseshow < lib.contentElement vitec_usecaseshow < lib.contentElementWithHeader
vitec_usecaseshow { vitec_usecaseshow {
fields { fields {
content { content {
@@ -68,7 +89,7 @@ tt_content {
} }
} }
vitec_marketshow < lib.contentElement vitec_marketshow < lib.contentElementWithHeader
vitec_marketshow { vitec_marketshow {
fields { fields {
content { content {
@@ -80,7 +101,7 @@ tt_content {
} }
} }
vitec_solutionshow < lib.contentElement vitec_solutionshow < lib.contentElementWithHeader
vitec_solutionshow { vitec_solutionshow {
fields { fields {
content { content {
@@ -92,7 +113,7 @@ tt_content {
} }
} }
vitec_downloadcard < lib.contentElement vitec_downloadcard < lib.contentElementWithHeader
vitec_downloadcard { vitec_downloadcard {
fields { fields {
content { content {
@@ -104,7 +125,7 @@ tt_content {
} }
} }
vitec_downloadcardcollection < lib.contentElement vitec_downloadcardcollection < lib.contentElementWithHeader
vitec_downloadcardcollection { vitec_downloadcardcollection {
fields { fields {
content { content {
@@ -116,7 +137,7 @@ tt_content {
} }
} }
vitec_datasheets < lib.contentElement vitec_datasheets < lib.contentElementWithHeader
vitec_datasheets { vitec_datasheets {
fields { fields {
content { content {
@@ -127,6 +148,18 @@ tt_content {
} }
} }
} }
vitec_eventlist =< lib.contentElementWithHeader
vitec_eventlist {
fields {
content {
fields {
eventlist = USER
eventlist.userFunc = Evomedien\Vitec\UserFunc\EventlistJsonRenderer->render
}
}
}
}
} }
# Include container (layout) rendering definitions # Include container (layout) rendering definitions
@@ -34,6 +34,7 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
$registerPluginWithFlexForm('Downloadcard', 'Download Card', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcard.xml', 'vitec-plugin-downloadcard'); $registerPluginWithFlexForm('Downloadcard', 'Download Card', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcard.xml', 'vitec-plugin-downloadcard');
$registerPluginWithFlexForm('Downloadcardcollection', 'Download Card Collection', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcardcollection.xml', 'vitec-plugin-downloadcardcollection'); $registerPluginWithFlexForm('Downloadcardcollection', 'Download Card Collection', 'FILE:EXT:vitec/Configuration/FlexForms/Downloadcardcollection.xml', 'vitec-plugin-downloadcardcollection');
$registerPluginWithFlexForm('Datasheets', 'Datasheets', 'FILE:EXT:vitec/Configuration/FlexForms/Datasheets.xml'); $registerPluginWithFlexForm('Datasheets', 'Datasheets', 'FILE:EXT:vitec/Configuration/FlexForms/Datasheets.xml');
$registerPluginWithFlexForm('Eventlist', 'Event List', 'FILE:EXT:vitec/Configuration/FlexForms/Eventlist.xml', 'vitec-plugin-eventlist');
// Change frame_class to allow multiple selections. // Change frame_class to allow multiple selections.
$GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['renderType'] = 'selectCheckBox'; $GLOBALS['TCA']['tt_content']['columns']['frame_class']['config']['renderType'] = 'selectCheckBox';
@@ -50,4 +51,9 @@ use TYPO3\CMS\Extbase\Utility\ExtensionUtility;
['label' => 'Vitec: Highlight Box', 'value' => 'vitec-highlight'], ['label' => 'Vitec: Highlight Box', 'value' => 'vitec-highlight'],
] ]
); );
// Inline parent pointer: content elements owned by a Success Story record.
$GLOBALS['TCA']['tt_content']['columns']['tx_vitec_usecase_content'] = [
'config' => ['type' => 'passthrough'],
];
})(); })();
@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
defined('TYPO3') or die();
use B13\Container\Tca\ContainerConfiguration;
use B13\Container\Tca\Registry;
use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
use TYPO3\CMS\Core\Utility\GeneralUtility;
(static function (): void {
$l = 'LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:';
/** @var Registry $registry */
$registry = GeneralUtility::makeInstance(Registry::class);
$registry->configureContainer(
(new ContainerConfiguration(
'vitec_cards_carousel',
$l . 'cards_carousel.title',
$l . 'cards_carousel.description',
[
[
['name' => $l . 'cards_carousel.column', 'colPos' => 260],
],
]
))
->setIcon('EXT:vitec/Resources/Public/Icons/vitec-cards-carousel.svg')
->setGroup('vitec_custom_components')
->setSaveAndCloseInNewContentElementWizard(true)
);
$GLOBALS['TCA']['tt_content']['types']['vitec_cards_carousel']['showitem'] =
'--palette--;;general,
--palette--;;headers,
tx_vitec_bg_variant,
pi_flexform;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:cards_carousel.flexform.label,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;;frames,
--palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
--palette--;;language,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
--palette--;;hidden,
--palette--;;access';
ExtensionManagementUtility::addPiFlexFormValue(
'*',
'FILE:EXT:vitec/Configuration/FlexForms/CardsCarousel.xml',
'vitec_cards_carousel'
);
$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] = ($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] ?? '') . "\n" . <<<'TSCONFIG'
mod.wizards.newContentElement.wizardItems.vitec_custom_components.elements.vitec_cards_carousel {
iconIdentifier = vitec-cards-carousel
title = LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:cards_carousel.title
description = LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:cards_carousel.description
tt_content_defValues {
CType = vitec_cards_carousel
}
}
TSCONFIG;
})();
@@ -35,10 +35,14 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_cols_25_25_25_25']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_cols_25_25_25_25']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:palette.grid;vitec_grid,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.1;vitec_col1_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.2;vitec_col2_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.3;vitec_col3_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.4;vitec_col4_flex,
--palette--;;frames, --palette--;;frames,
--palette--;;appearanceLinks, --palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
@@ -34,10 +34,13 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_33_33']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_33_33']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:palette.grid;vitec_grid,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.1;vitec_col1_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.2;vitec_col2_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.3;vitec_col3_flex,
--palette--;;frames, --palette--;;frames,
--palette--;;appearanceLinks, --palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
@@ -33,10 +33,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_66']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_cols_33_66']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:palette.grid;vitec_grid,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.sidebar_33;vitec_col1_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.main_66;vitec_col2_flex,
--palette--;;frames, --palette--;;frames,
--palette--;;appearanceLinks, --palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
@@ -33,10 +33,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_cols_50_50']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_cols_50_50']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:palette.grid;vitec_grid,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.1;vitec_col1_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.2;vitec_col2_flex,
--palette--;;frames, --palette--;;frames,
--palette--;;appearanceLinks, --palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
@@ -33,10 +33,12 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_cols_66_33']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_cols_66_33']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:palette.grid;vitec_grid,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.main_66;vitec_col1_flex,
--palette--;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:column.sidebar_33;vitec_col2_flex,
--palette--;;frames, --palette--;;frames,
--palette--;;appearanceLinks, --palette--;;appearanceLinks,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, --div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
@@ -32,8 +32,7 @@ use TYPO3\CMS\Core\Utility\GeneralUtility;
$GLOBALS['TCA']['tt_content']['types']['vitec_container']['showitem'] = $GLOBALS['TCA']['tt_content']['types']['vitec_container']['showitem'] =
'--palette--;;general, '--palette--;;general,
header;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.heading, --palette--;;headers,
subheader;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:section.subline,
tx_vitec_bg_variant, tx_vitec_bg_variant,
pi_flexform;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:container.flexform.label, pi_flexform;LLL:EXT:vitec/Resources/Private/Language/locallang_containers.xlf:container.flexform.label,
--div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance, --div--;LLL:EXT:frontend/Resources/Private/Language/locallang_ttc.xlf:tabs.appearance,
@@ -27,6 +27,87 @@ use TYPO3\CMS\Core\Utility\ExtensionManagementUtility;
], ],
]); ]);
// -------------------------------------------------------------------------
// Shared parent-level setting: column gap scale (1–5) for the whole grid.
// Concerns the container itself (not a single column); maps to a spacing
// token in the React frontend. Rendered in the "Grid" palette on top of the
// Appearance tab, above the per-column rows.
// -------------------------------------------------------------------------
ExtensionManagementUtility::addTCAcolumns('tt_content', [
'tx_vitec_gap' => [
'label' => $l . 'gap.label',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => [
['label' => $l . 'gap.option.1', 'value' => '1'],
['label' => $l . 'gap.option.2', 'value' => '2'],
['label' => $l . 'gap.option.3', 'value' => '3'],
['label' => $l . 'gap.option.4', 'value' => '4'],
['label' => $l . 'gap.option.5', 'value' => '5'],
],
'default' => '3',
],
],
]);
// Parent-element palette: settings that concern the whole grid, not a column.
$GLOBALS['TCA']['tt_content']['palettes']['vitec_grid'] = [
'showitem' => 'tx_vitec_gap',
];
// -------------------------------------------------------------------------
// Shared per-column flex alignment fields for the X-column containers.
// Positional: col1 = first column, col2 = second, ... (max 4 columns).
// "Align" -> CSS align-items (cross-axis alignment of the column's items)
// "Justify" -> CSS justify-content (main-axis distribution within the column)
// Reused across vitec_cols_50_50 / 33_66 / 66_33 / 33_33_33 / 25_25_25_25.
// -------------------------------------------------------------------------
$alignItems = [
['label' => $l . 'flex.align.flex_start', 'value' => 'flex-start'],
['label' => $l . 'flex.align.center', 'value' => 'center'],
['label' => $l . 'flex.align.flex_end', 'value' => 'flex-end'],
['label' => $l . 'flex.align.stretch', 'value' => 'stretch'],
];
$justifyItems = [
['label' => $l . 'flex.justify.flex_start', 'value' => 'flex-start'],
['label' => $l . 'flex.justify.center', 'value' => 'center'],
['label' => $l . 'flex.justify.space_between', 'value' => 'space-between'],
['label' => $l . 'flex.justify.space_around', 'value' => 'space-around'],
['label' => $l . 'flex.justify.space_evenly', 'value' => 'space-evenly'],
];
$columnFields = [];
for ($i = 1; $i <= 4; $i++) {
$columnFields['tx_vitec_col' . $i . '_align'] = [
'label' => $l . 'flex.align.label',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => $alignItems,
'default' => 'stretch',
],
];
$columnFields['tx_vitec_col' . $i . '_justify'] = [
'label' => $l . 'flex.justify.label',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'items' => $justifyItems,
'default' => 'flex-start',
],
];
}
ExtensionManagementUtility::addTCAcolumns('tt_content', $columnFields);
// One palette per column: Align + Justify on a single row. The visible row
// label is overridden per container in its showitem (--palette--;<colLabel>;<palette>).
for ($i = 1; $i <= 4; $i++) {
$GLOBALS['TCA']['tt_content']['palettes']['vitec_col' . $i . '_flex'] = [
'showitem' => 'tx_vitec_col' . $i . '_align, tx_vitec_col' . $i . '_justify',
];
}
// Register VITEC wizard group header // Register VITEC wizard group header
$GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] = ($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] ?? '') . "\n" . <<<'TSCONFIG' $GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] = ($GLOBALS['TYPO3_CONF_VARS']['BE']['defaultPageTSconfig'] ?? '') . "\n" . <<<'TSCONFIG'
mod.wizards.newContentElement.wizardItems.vitec { mod.wizards.newContentElement.wizardItems.vitec {
@@ -0,0 +1,290 @@
<?php
return [
'ctrl' => [
'title' => 'VITEC Event',
'label' => 'title',
'label_alt' => 'city,eventstart',
'tstamp' => 'tstamp',
'crdate' => 'crdate',
'versioningWS' => true,
'languageField' => 'sys_language_uid',
'transOrigPointerField' => 'l10n_parent',
'transOrigDiffSourceField' => 'l10n_diffsource',
'delete' => 'deleted',
'default_sortby' => 'eventstart ASC',
'enablecolumns' => [
'disabled' => 'hidden',
'starttime' => 'starttime',
'endtime' => 'endtime',
],
'searchFields' => 'title,teaser,venue,booth,city,country',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_event.svg',
'security' => [
'ignorePageTypeRestriction' => true,
],
],
'types' => [
'1' => [
'showitem' => 'title, slug, teaser, description,
--div--;Event Details, eventstart, eventend, venue, booth, city, country, attendancemode, eventstatus,
--div--;Links and Media, eventurl, meetinglink, image,
--div--;Visibility, hideonwebsite, hideonapp,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime',
],
],
'columns' => [
'sys_language_uid' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [
'type' => 'language',
],
],
'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0',
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.l18n_parent',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => 0,
'items' => [
['label' => '', 'value' => 0],
],
'foreign_table' => 'tx_vitec_domain_model_event',
'foreign_table_where' => 'AND {#tx_vitec_domain_model_event}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_event}.{#sys_language_uid} IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
],
],
'hidden' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'items' => [
[
'label' => '',
'invertStateDisplay' => true,
],
],
],
],
'starttime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => [
'type' => 'datetime',
'default' => 0,
],
],
'endtime' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => [
'type' => 'datetime',
'default' => 0,
],
],
'title' => [
'exclude' => false,
'label' => 'Event Name',
'config' => [
'type' => 'input',
'size' => 40,
'max' => 255,
'eval' => 'trim',
'required' => true,
],
],
'slug' => [
'exclude' => true,
'label' => 'Slug',
'config' => [
'type' => 'slug',
'size' => 50,
'generatorOptions' => [
'fields' => ['title'],
'replacements' => ['/' => '-'],
],
'fallbackCharacter' => '-',
'eval' => 'uniqueInSite',
'default' => '',
],
],
'teaser' => [
'exclude' => true,
'label' => 'Teaser',
'config' => [
'type' => 'input',
'size' => 40,
'max' => 255,
'eval' => 'trim',
],
],
'description' => [
'exclude' => true,
'label' => 'Description',
'config' => [
'type' => 'text',
'cols' => 40,
'rows' => 8,
'enableRichtext' => true,
],
],
'eventstart' => [
'exclude' => false,
'label' => 'Event Start Date',
'config' => [
'type' => 'datetime',
'format' => 'date',
'default' => 0,
'required' => true,
],
],
'eventend' => [
'exclude' => true,
'label' => 'Event End Date',
'config' => [
'type' => 'datetime',
'format' => 'date',
'default' => 0,
],
],
'venue' => [
'exclude' => true,
'label' => 'Venue',
'description' => 'e.g. "Queen Sirikit National Convention Center (QSNCC)"',
'config' => [
'type' => 'input',
'size' => 40,
'max' => 255,
'eval' => 'trim',
],
],
'booth' => [
'exclude' => true,
'label' => 'Booth / Stand',
'description' => 'e.g. "Stand H10"',
'config' => [
'type' => 'input',
'size' => 20,
'max' => 255,
'eval' => 'trim',
],
],
'city' => [
'exclude' => true,
'label' => 'City',
'config' => [
'type' => 'input',
'size' => 30,
'max' => 255,
'eval' => 'trim',
],
],
'country' => [
'exclude' => true,
'label' => 'Country',
'config' => [
'type' => 'input',
'size' => 30,
'max' => 255,
'eval' => 'trim',
],
],
'attendancemode' => [
'exclude' => true,
'label' => 'Attendance Mode',
'description' => 'Used for schema.org structured data (eventAttendanceMode).',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => 'offline',
'items' => [
['label' => 'On-site (offline)', 'value' => 'offline'],
['label' => 'Online', 'value' => 'online'],
['label' => 'Hybrid (mixed)', 'value' => 'mixed'],
],
],
],
'eventstatus' => [
'exclude' => true,
'label' => 'Event Status',
'description' => 'Used for schema.org structured data (eventStatus).',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'default' => 'scheduled',
'items' => [
['label' => 'Scheduled', 'value' => 'scheduled'],
['label' => 'Cancelled', 'value' => 'cancelled'],
['label' => 'Postponed', 'value' => 'postponed'],
['label' => 'Rescheduled', 'value' => 'rescheduled'],
['label' => 'Moved Online', 'value' => 'movedonline'],
],
],
],
'eventurl' => [
'exclude' => true,
'label' => 'Event Website',
'config' => [
'type' => 'link',
'allowedTypes' => ['url', 'page'],
],
],
'meetinglink' => [
'exclude' => true,
'label' => 'Book-a-Meeting Link',
'description' => 'Link or email for the "Book a Meeting" call-to-action.',
'config' => [
'type' => 'link',
'allowedTypes' => ['url', 'email', 'page'],
],
],
'image' => [
'exclude' => true,
'label' => 'Event Logo / Image',
'config' => [
'type' => 'file',
'maxitems' => 1,
'allowed' => 'common-image-types',
],
],
'hideonwebsite' => [
'exclude' => true,
'label' => 'Hide on Website',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'default' => 0,
],
],
'hideonapp' => [
'exclude' => true,
'label' => 'Hide in App',
'config' => [
'type' => 'check',
'renderType' => 'checkboxToggle',
'default' => 0,
],
],
'categories' => [
'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.categories',
'config' => [
'type' => 'category',
],
],
],
];
@@ -16,26 +16,50 @@ return [
'starttime' => 'starttime', 'starttime' => 'starttime',
'endtime' => 'endtime', 'endtime' => 'endtime',
], ],
'searchFields' => 'title,slug', 'searchFields' => 'title,slug,subtitle,teaser,seo_title,seo_description',
'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif', 'iconfile' => 'EXT:vitec/Resources/Public/Icons/tx_vitec_domain_model_usecase.gif',
'security' => [ 'security' => [
'ignorePageTypeRestriction' => true, 'ignorePageTypeRestriction' => true,
], ],
], ],
'types' => [ 'types' => [
'1' => ['showitem' => 'title, slug, subtitle, teaser, description, singlepid, caseimage, logoimage, '1' => [
--div--;Visibility, hideonapp, hideonwebsite, 'showitem' =>
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language, sys_language_uid, l10n_parent, l10n_diffsource, '--div--;General,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:categories, categories, title, slug, subtitle,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access, hidden, starttime, endtime', ], --div--;List view,
card_image, customer_logo, teaser,
--div--;Detail · Hero,
hero_bgimage, hero_small_image, hero_video, hero_overlay_color, hero_overlay_opacity,
--div--;Detail · Content,
content_elements,
--div--;Detail · Related,
market, solutions, products, categories,
--div--;SEO,
seo_title, seo_description, no_index, no_follow, canonical_link,
--palette--;Open Graph;ogPalette,
--palette--;Twitter;twitterPalette,
--div--;Appearance,
layout_variant, background_variant, accent_color,
featured, show_related, hero_layout, text_theme,
--palette--;Visibility;visibilityPalette,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:language,
sys_language_uid, l10n_parent, l10n_diffsource,
--div--;LLL:EXT:core/Resources/Private/Language/Form/locallang_tabs.xlf:access,
hidden, starttime, endtime',
],
],
'palettes' => [
'ogPalette' => ['showitem' => 'og_title, og_description, --linebreak--, og_image'],
'twitterPalette' => ['showitem' => 'twitter_title, twitter_description, --linebreak--, twitter_image'],
'visibilityPalette' => ['showitem' => 'hideonwebsite, hideonapp'],
], ],
'columns' => [ 'columns' => [
// ------------------------------------------------------------ system
'sys_language_uid' => [ 'sys_language_uid' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.language',
'config' => [ 'config' => ['type' => 'language'],
'type' => 'language',
],
], ],
'l10n_parent' => [ 'l10n_parent' => [
'displayCond' => 'FIELD:sys_language_uid:>:0', 'displayCond' => 'FIELD:sys_language_uid:>:0',
@@ -44,275 +68,273 @@ return [
'type' => 'select', 'type' => 'select',
'renderType' => 'selectSingle', 'renderType' => 'selectSingle',
'default' => 0, 'default' => 0,
'items' => [ 'items' => [['label' => '', 'value' => 0]],
['', 0], 'foreign_table' => 'tx_vitec_domain_model_usecase',
], 'foreign_table_where' => 'AND {#tx_vitec_domain_model_usecase}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_usecase}.{#sys_language_uid} IN (-1,0)',
'foreign_table' => 'tx_vitec_domain_model_download',
'foreign_table_where' => 'AND {#tx_vitec_domain_model_download}.{#pid}=###CURRENT_PID### AND {#tx_vitec_domain_model_download}.{#sys_language_uid} IN (-1,0)',
],
],
'l10n_diffsource' => [
'config' => [
'type' => 'passthrough',
], ],
], ],
'l10n_diffsource' => ['config' => ['type' => 'passthrough']],
'hidden' => [ 'hidden' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.visible',
'config' => [ 'config' => [
'type' => 'check', 'type' => 'check',
'renderType' => 'checkboxToggle', 'renderType' => 'checkboxToggle',
'items' => [ 'items' => [['value' => '', 'label' => '', 'invertStateDisplay' => true]],
[
0 => '',
1 => '',
'invertStateDisplay' => true,
],
],
], ],
], ],
'starttime' => [ 'starttime' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.starttime',
'config' => [ 'config' => ['type' => 'datetime', 'default' => 0, 'behaviour' => ['allowLanguageSynchronization' => true]],
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'behaviour' => [
'allowLanguageSynchronization' => true,
],
],
], ],
'endtime' => [ 'endtime' => [
'exclude' => true, 'exclude' => true,
'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime', 'label' => 'LLL:EXT:core/Resources/Private/Language/locallang_general.xlf:LGL.endtime',
'config' => [ 'config' => ['type' => 'datetime', 'default' => 0, 'range' => ['upper' => mktime(0, 0, 0, 1, 1, 2038)], 'behaviour' => ['allowLanguageSynchronization' => true]],
'type' => 'input',
'renderType' => 'inputDateTime',
'eval' => 'datetime,int',
'default' => 0,
'range' => [
'upper' => mktime(0, 0, 0, 1, 1, 2038),
],
'behaviour' => [
'allowLanguageSynchronization' => true,
],
],
], ],
// ----------------------------------------------------------- General
'title' => [ 'title' => [
'exclude' => false, 'label' => 'Title',
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title', 'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'required' => true, 'default' => ''],
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'required' => true,
'default' => '',
],
], ],
'slug' => [ 'slug' => [
'exclude' => false, 'label' => 'Slug',
'label' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.slug',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.slug.description',
'config' => [ 'config' => [
'type' => 'slug', 'type' => 'slug',
'size' => 50, 'size' => 50,
'generatorOptions' => [ 'generatorOptions' => ['fields' => ['title'], 'fieldSeparator' => '-', 'replacements' => ['/' => '']],
'fields' => ['title'], // TODO: adjust this field to the one you want to use
'fieldSeparator' => '-',
'replacements' => [
'/' => '',
],
],
'fallbackCharacter' => '-', 'fallbackCharacter' => '-',
'eval' => 'uniqueInPid', 'eval' => 'uniqueInPid',
], ],
],
'singlepid' => [
'exclude' => false,
'label' => 'Page ID of Success Story',
'description' => 'LLL:EXT:vitec/Resources/Private/Language/locallang_db.xlf:tx_vitec_domain_model_download.title.description',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'required' => false,
'default' => '',
],
],
'teaser' => [
'exclude' => true,
'label' => 'Short Teaser (optional)',
'config' => [
'type' => 'input',
'size' => 30,
'eval' => 'trim',
'default' => '',
],
], ],
'subtitle' => [ 'subtitle' => [
'exclude' => true,
'label' => 'Subtitle', 'label' => 'Subtitle',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim', 'default' => ''],
],
// --------------------------------------------------------- List view
'card_image' => [
'label' => 'Card Image',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'customer_logo' => [
'label' => 'Customer Logo',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'teaser' => [
'label' => 'Teaser',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 4, 'eval' => 'trim'],
],
// ------------------------------------------------------ Detail · Hero
'hero_bgimage' => [
'label' => 'Background Image',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'hero_small_image' => [
'label' => 'Small Image',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'hero_video' => [
'label' => 'Video',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'mp4,webm,ogv,mov,m4v'],
],
'hero_overlay_color' => [
'label' => 'Overlay Color',
'config' => ['type' => 'color'],
],
'hero_overlay_opacity' => [
'label' => 'Overlay Opacity (0–1)',
'config' => [ 'config' => [
'type' => 'input', 'type' => 'number',
'size' => 30, 'format' => 'decimal',
'eval' => 'trim', 'default' => 0,
'default' => '', 'range' => ['lower' => 0, 'upper' => 1],
'slider' => ['step' => 0.05, 'width' => 200],
], ],
], ],
'hideonapp' => [
'exclude' => true, // --------------------------------------------------- Detail · Content
'label' => 'If checked - Product will not be shown on App', 'content_elements' => [
'label' => 'Content Elements',
'config' => [ 'config' => [
'type' => 'check', 'type' => 'inline',
'renderType' => 'checkboxToggle', 'foreign_table' => 'tt_content',
'foreign_field' => 'tx_vitec_usecase_content',
'foreign_sortby' => 'sorting',
'appearance' => [
'collapseAll' => true,
'expandSingle' => true,
'levelLinksPosition' => 'top',
'showSynchronizationLink' => false,
'showAllLocalizationLink' => false,
'showPossibleLocalizationRecords' => false,
'useSortable' => true,
],
'overrideChildTca' => [
'columns' => [
'colPos' => ['config' => ['default' => 999]],
],
],
],
],
// --------------------------------------------------- Detail · Related
'market' => [
'label' => 'Market',
'config' => [
'type' => 'select',
'renderType' => 'selectSingle',
'foreign_table' => 'tx_vitec_domain_model_market',
'items' => [['label' => '', 'value' => 0]],
'default' => 0,
'maxitems' => 1,
],
],
'solutions' => [
'label' => 'Solutions',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_solution',
'MM' => 'tx_vitec_usecase_solution_mm',
'size' => 6,
'autoSizeMax' => 20,
'maxitems' => 9999,
],
],
'products' => [
'label' => 'Products',
'config' => [
'type' => 'select',
'renderType' => 'selectMultipleSideBySide',
'foreign_table' => 'tx_vitec_domain_model_product',
'MM' => 'tx_vitec_usecase_product_mm',
'size' => 6,
'autoSizeMax' => 20,
'maxitems' => 9999,
],
],
'categories' => ['config' => ['type' => 'category']],
// --------------------------------------------------------------- SEO
'seo_title' => [
'label' => 'SEO Title',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim'],
],
'seo_description' => [
'label' => 'Meta Description',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 3, 'eval' => 'trim'],
],
'no_index' => [
'label' => 'No Index',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
'no_follow' => [
'label' => 'No Follow',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
'canonical_link' => [
'label' => 'Canonical URL',
'config' => ['type' => 'link'],
],
'og_title' => [
'label' => 'og:title',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim'],
],
'og_description' => [
'label' => 'og:description',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 3, 'eval' => 'trim'],
],
'og_image' => [
'label' => 'og:image',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
'twitter_title' => [
'label' => 'twitter:title',
'config' => ['type' => 'input', 'size' => 40, 'eval' => 'trim'],
],
'twitter_description' => [
'label' => 'twitter:description',
'config' => ['type' => 'text', 'cols' => 40, 'rows' => 3, 'eval' => 'trim'],
],
'twitter_image' => [
'label' => 'twitter:image',
'config' => ['type' => 'file', 'maxitems' => 1, 'allowed' => 'common-image-types'],
],
// -------------------------------------------------------- Appearance
'layout_variant' => [
'label' => 'Detail Layout',
'config' => [
'type' => 'select', 'renderType' => 'selectSingle', 'default' => 'standard',
'items' => [ 'items' => [
[ ['label' => 'Standard', 'value' => 'standard'],
0 => '', ['label' => 'Wide', 'value' => 'wide'],
1 => '', ['label' => 'Centered', 'value' => 'centered'],
'invertStateDisplay' => false, ['label' => 'Media-first', 'value' => 'media_first'],
], ],
], ],
], ],
'background_variant' => [
'label' => 'Background',
'config' => [
'type' => 'select', 'renderType' => 'selectSingle', 'default' => 'none',
'items' => [
['label' => 'None', 'value' => 'none'],
['label' => 'Orange', 'value' => 'orange'],
['label' => 'Blue', 'value' => 'blue'],
['label' => 'Graphite', 'value' => 'graphite'],
['label' => 'Midnight', 'value' => 'midnight'],
['label' => 'Light', 'value' => 'light'],
['label' => 'Dark', 'value' => 'dark'],
],
],
],
'accent_color' => [
'label' => 'Accent Color',
'config' => ['type' => 'color'],
], ],
'hideonwebsite' => [ 'hideonwebsite' => [
'exclude' => true, 'exclude' => true,
'label' => 'If checked - Product will not be shown on Website', 'label' => 'Hide on Website',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
'hideonapp' => [
'exclude' => true,
'label' => 'Hide on App',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
'featured' => [
'label' => 'Featured',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'items' => [['value' => '', 'label' => '']]],
],
'show_related' => [
'label' => 'Show Related section',
'config' => ['type' => 'check', 'renderType' => 'checkboxToggle', 'default' => 1, 'items' => [['value' => '', 'label' => '']]],
],
'hero_layout' => [
'label' => 'Hero Layout',
'config' => [ 'config' => [
'type' => 'check', 'type' => 'select', 'renderType' => 'selectSingle', 'default' => 'fullscreen',
'renderType' => 'checkboxToggle',
'items' => [ 'items' => [
[ ['label' => 'Fullscreen', 'value' => 'fullscreen'],
0 => '', ['label' => 'Medium', 'value' => 'md'],
1 => '', ['label' => 'Small', 'value' => 'sm'],
'invertStateDisplay' => false,
], ],
], ],
], ],
], 'text_theme' => [
'description' => [ 'label' => 'Text Theme',
'exclude' => true,
'label' => 'Description',
'config' => [ 'config' => [
'type' => 'text', 'type' => 'select', 'renderType' => 'selectSingle', 'default' => 'light',
'enableRichtext' => 'true', 'items' => [
'eval' => 'trim', ['label' => 'Light', 'value' => 'light'],
'default' => '', ['label' => 'Dark', 'value' => 'dark'],
],
'defaultExtras' => 'richtext:rte_transform[mode=ts_css]',
],
'caseimage' => [
'exclude' => true,
'label' => 'Case Image',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'caseimage',
],
'foreign_label' => 'uid_local',
'foreign_selector' => 'uid_local',
'overrideChildTca' => [
'columns' => [
'uid_local' => [
'config' => [
'appearance' => [
'elementBrowserType' => 'file',
'elementBrowserAllowed' => 'jpg,jpeg,png,gif',
], ],
], ],
], ],
], ],
'types' => [
'0' => [
'showitem' => '
--palette--;;imageoverlayPalette,
--palette--;;filePalette',
],
],
],
'maxitems' => 1,
'appearance' => [
'headerThumbnail' => [
'field' => 'uid_local',
'width' => '45',
'height' => '45',
],
'enabledControls' => [
'info' => true,
'new' => false,
'dragdrop' => true,
'sort' => false,
'hide' => true,
'delete' => true,
],
'fileUploadAllowed' => true,
],
],
],
'logoimage' => [
'exclude' => true,
'label' => 'Customer Logo',
'config' => [
'type' => 'inline',
'foreign_table' => 'sys_file_reference',
'foreign_field' => 'uid_foreign',
'foreign_sortby' => 'sorting_foreign',
'foreign_table_field' => 'tablenames',
'foreign_match_fields' => [
'fieldname' => 'logoimage',
],
'foreign_label' => 'uid_local',
'foreign_selector' => 'uid_local',
'overrideChildTca' => [
'columns' => [
'uid_local' => [
'config' => [
'appearance' => [
'elementBrowserType' => 'file',
'elementBrowserAllowed' => 'jpg,jpeg,png,gif',
],
],
],
],
'types' => [
'0' => [
'showitem' => '
--palette--;;imageoverlayPalette,
--palette--;;filePalette',
],
],
],
'maxitems' => 1,
'appearance' => [
'headerThumbnail' => [
'field' => 'uid_local',
'width' => '45',
'height' => '45',
],
'enabledControls' => [
'info' => true,
'new' => false,
'dragdrop' => true,
'sort' => false,
'hide' => true,
'delete' => true,
],
'fileUploadAllowed' => true,
],
],
],
'categories' => [
'config' => [
'type' => 'category'
]
],
],
]; ];
@@ -2,6 +2,12 @@
# VITEC container content elements — self-contained JSON rendering # VITEC container content elements — self-contained JSON rendering
# Children collected via Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor # Children collected via Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor
# (exception-safe; returns [] on any error, never crashes the outer CE). # (exception-safe; returns [] on any error, never crashes the outer CE).
#
# Per-column flex settings (align/justify) live on the PARENT record
# (tx_vitec_col{1..4}_align/justify) and are attached by the processor to each
# column in `items` (items[].config.align/justify) — so they apply to ALL
# children of that column. The whole-grid gap (tx_vitec_gap) is emitted here as
# the parent-level `gap`.
# ============================================================================= # =============================================================================
tt_content.vitec_cols_50_50 = JSON tt_content.vitec_cols_50_50 = JSON
@@ -70,9 +76,27 @@ tt_content.vitec_cols_50_50 {
header.field = header header.field = header
subheader = TEXT subheader = TEXT
subheader.field = subheader subheader.field = subheader
headerLayout = INT
headerLayout.field = header_layout
headerPosition = TEXT
headerPosition.field = header_position
headerLink = TEXT
headerLink {
htmlSpecialChars = 1
typolink {
parameter.field = header_link
returnLast = result
}
}
tx_vitec_bg_variant = TEXT tx_vitec_bg_variant = TEXT
tx_vitec_bg_variant.field = tx_vitec_bg_variant tx_vitec_bg_variant.field = tx_vitec_bg_variant
# Parent-level: column gap scale (1–5) for the whole grid.
gap = TEXT
gap.field = tx_vitec_gap
# Columns with their children + per-column flex (align/justify) in
# items[].config — assembled by ContainerChildrenProcessor.
items = JSON items = JSON
items { items {
dataProcessing { dataProcessing {
@@ -83,18 +107,48 @@ tt_content.vitec_cols_50_50 {
} }
} }
tt_content.vitec_cols_33_33_33 =< tt_content.vitec_cols_50_50 tt_content.vitec_cols_33_33_33 < tt_content.vitec_cols_50_50
tt_content.vitec_cols_25_25_25_25 =< tt_content.vitec_cols_50_50 tt_content.vitec_cols_25_25_25_25 < tt_content.vitec_cols_50_50
tt_content.vitec_cols_66_33 =< tt_content.vitec_cols_50_50 tt_content.vitec_cols_66_33 < tt_content.vitec_cols_50_50
tt_content.vitec_cols_33_66 =< tt_content.vitec_cols_50_50 tt_content.vitec_cols_33_66 < tt_content.vitec_cols_50_50
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# VITEC · Container — single-column container with a custom CSS class. # VITEC · Container — single-column container with a custom CSS class.
# Inherits the full structure of the grid containers and adds the `cssClass` # Inherits the full structure of the grid containers and adds the `cssClass`
# field, read from the element's FlexForm (settings.cssClass). # field, read from the element's FlexForm (settings.cssClass).
# Single column -> no grid gap.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
tt_content.vitec_container =< tt_content.vitec_cols_50_50 tt_content.vitec_container < tt_content.vitec_cols_50_50
tt_content.vitec_container.fields.gap >
tt_content.vitec_container.fields.cssClass = TEXT tt_content.vitec_container.fields.cssClass = TEXT
tt_content.vitec_container.fields.cssClass.data = flexform:pi_flexform:settings.cssClass tt_content.vitec_container.fields.cssClass.data = flexform:pi_flexform:settings.cssClass
tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject = TEXT tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject = TEXT
tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject.value = tt_content.vitec_container.fields.cssClass.stdWrap.ifEmpty.cObject.value =
# -----------------------------------------------------------------------------
# VITEC · Cards Carousel — container whose children (typically vitec_card
# elements) become the slides. Inherits the grid-container JSON structure
# (`items` via ContainerChildrenProcessor) and adds carousel settings read
# from the element's FlexForm. Single slide track -> no grid gap.
# -----------------------------------------------------------------------------
tt_content.vitec_cards_carousel < tt_content.vitec_cols_50_50
tt_content.vitec_cards_carousel.fields.gap >
tt_content.vitec_cards_carousel.fields.carousel = JSON
tt_content.vitec_cards_carousel.fields.carousel {
fields {
slidesPerView = TEXT
slidesPerView.data = flexform:pi_flexform:settings.slidesperview
showArrows = TEXT
showArrows.data = flexform:pi_flexform:settings.showarrows
showIndicators = TEXT
showIndicators.data = flexform:pi_flexform:settings.showindicators
loop = TEXT
loop.data = flexform:pi_flexform:settings.loop
autoplay = TEXT
autoplay.data = flexform:pi_flexform:settings.autoplay
autoplayInterval = TEXT
autoplayInterval.data = flexform:pi_flexform:settings.autoplayinterval
}
}
tt_content.vitec_cards_carousel.fields.cssClass = TEXT
tt_content.vitec_cards_carousel.fields.cssClass.data = flexform:pi_flexform:settings.cssClass
@@ -0,0 +1,30 @@
# =============================================================================
# VITEC news integration — headless JSON output
#
# All supported EXT:news content element types use the same JSON renderer.
# The renderer returns a stable payload with:
# - mode = list | detail
# - items/news = serialised records
# - settings = the relevant FlexForm settings for the frontend
# =============================================================================
tt_content.news_pi1 =< lib.contentElementWithHeader
tt_content.news_pi1 {
fields {
content {
fields {
news = USER
news.userFunc = Evomedien\Vitec\UserFunc\NewsJsonRenderer->render
}
}
}
}
tt_content.news_newsliststicky < tt_content.news_pi1
tt_content.news_newsselectedlist < tt_content.news_pi1
tt_content.news_newsdetail < tt_content.news_pi1
tt_content.news_newsdatemenu < tt_content.news_pi1
tt_content.news_categorylist < tt_content.news_pi1
tt_content.news_newssearchform < tt_content.news_pi1
tt_content.news_newssearchresult < tt_content.news_pi1
tt_content.news_taglist < tt_content.news_pi1
@@ -80,6 +80,14 @@ mod {
CType = vitec_datasheets CType = vitec_datasheets
} }
} }
eventlist {
iconIdentifier = vitec-plugin-eventlist
title = VITEC Event List
description = Upcoming VITEC events (trade shows) with date, venue, booth and links
tt_content_defValues {
CType = vitec_eventlist
}
}
simplecard { simplecard {
iconIdentifier = vitec-plugin-simplecard iconIdentifier = vitec-plugin-simplecard
title = Simple Card title = Simple Card
@@ -97,6 +105,13 @@ mod {
} }
} }
tx_news.templateLayouts {
1 = Compact List
2 = Featured Story
3 = Editorial Grid
4 = Detail Spotlight
}
############################################################ ############################################################
# Backend Previews für klassische Plugins (list_type) # Backend Previews für klassische Plugins (list_type)
############################################################ ############################################################
@@ -13,13 +13,28 @@ fields:
type: Text type: Text
max: 50 max: 50
- identifier: header_section
type: Palette
label: Header
fields:
- identifier: header - identifier: header
useExistingField: true useExistingField: true
required: true required: true
- type: Linebreak
- identifier: header_layout
useExistingField: true
- identifier: header_position
useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link
useExistingField: true
- type: Linebreak
- identifier: subheader - identifier: subheader
useExistingField: true useExistingField: true
- identifier: bodytext - identifier: bodytext
useExistingField: true useExistingField: true
enableRichtext: true enableRichtext: true
@@ -50,7 +50,7 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#c00;">⚠ Headline fehlt</em> <em style="color:#c00;">⚠ Headline missing</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<rect x="3" y="6" width="32" height="20" rx="2" fill="none"/>
<rect x="3" y="6" width="12" height="20" rx="1.5" fill="#ff6a00"/>
<rect x="17" y="6" width="12" height="20" rx="1.5" fill="#0a3d62"/>
</svg>

After

Width:  |  Height:  |  Size: 294 B

@@ -0,0 +1,91 @@
name: vitec/columns
group: vitec_custom_components
prefixFields: true
prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields:
- identifier: header
useExistingField: true
required: true
- type: Linebreak
- identifier: header_layout
useExistingField: true
- identifier: header_position
useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link
useExistingField: true
- type: Linebreak
- identifier: subheader
useExistingField: true
- identifier: layout
type: Select
renderType: selectSingle
default: cols_50_50
items:
- label: '50 / 50'
value: cols_50_50
- label: '66 / 33'
value: cols_66_33
- label: '33 / 66'
value: cols_33_66
- identifier: items
type: Collection
label: Items
labelField: headline
fields:
- identifier: column
type: Select
renderType: selectSingle
default: left
items:
- label: Left column
value: left
- label: Right column
value: right
- identifier: item_type
type: Select
renderType: selectSingle
default: text
items:
- label: Text
value: text
- label: Image
value: image
- label: Video
value: video
- label: CTA / Button
value: cta
- identifier: headline
type: Text
max: 255
- identifier: text
type: Textarea
enableRichtext: true
- identifier: image
type: File
minitems: 0
maxitems: 1
allowed: common-image-types
- identifier: video
type: File
minitems: 0
maxitems: 1
allowed: mp4,webm,ogv,mov,m4v
- identifier: link
type: Link
allowedTypes:
- page
- url
- file
- email
- identifier: link_label
type: Text
max: 60
@@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2">
<file source-language="en" datatype="plaintext" original="messages">
<body>
<trans-unit id="title">
<source>VITEC · Columns</source>
</trans-unit>
<trans-unit id="description">
<source>Two-column layout (50/50, 66/33, 33/66). Add items and assign each to the left or right column.</source>
</trans-unit>
<trans-unit id="layout.label">
<source>Layout</source>
</trans-unit>
<trans-unit id="items.label">
<source>Items</source>
</trans-unit>
<trans-unit id="items.column.label">
<source>Column</source>
</trans-unit>
<trans-unit id="items.item_type.label">
<source>Type</source>
</trans-unit>
<trans-unit id="items.headline.label">
<source>Headline</source>
</trans-unit>
<trans-unit id="items.text.label">
<source>Text</source>
</trans-unit>
<trans-unit id="items.image.label">
<source>Image</source>
</trans-unit>
<trans-unit id="items.video.label">
<source>Video</source>
</trans-unit>
<trans-unit id="items.link.label">
<source>Link</source>
</trans-unit>
<trans-unit id="items.link_label.label">
<source>Link Label</source>
</trans-unit>
</body>
</file>
</xliff>
@@ -0,0 +1,19 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Preview"/>
<f:section name="Content">
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
<div class="vitec-preview">
<div class="vitec-preview__thumb-placeholder">▦</div>
<div class="vitec-preview__body">
<div class="vitec-preview__label">VITEC · Columns</div>
<h3 class="vitec-preview__headline">Two-column layout — {data.layout}</h3>
</div>
<div class="vitec-preview__settings">
<span class="vitec-badge">{data.layout}</span>
</div>
</div>
</f:section>
</html>
@@ -0,0 +1,3 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<!-- Headless mode: JSON is built by the VITEC UsecaseSerializer / nb-headless-content-blocks -->
</html>
@@ -3,10 +3,27 @@ group: vitec_custom_components
prefixFields: true prefixFields: true
prefixType: vendor prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields: fields:
- identifier: header - identifier: header
useExistingField: true useExistingField: true
required: true required: true
- type: Linebreak
- identifier: header_layout
useExistingField: true
- identifier: header_position
useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link
useExistingField: true
- type: Linebreak
- identifier: subheader
useExistingField: true
- identifier: bodytext - identifier: bodytext
useExistingField: true useExistingField: true
@@ -6,7 +6,7 @@
<source>VITEC · CTA Banner</source> <source>VITEC · CTA Banner</source>
</trans-unit> </trans-unit>
<trans-unit id="description"> <trans-unit id="description">
<source>Call-to-Action Banner mit optionalem zweiten Button und Background-Varianten</source> <source>Call-to-action banner with optional secondary button and background variants</source>
</trans-unit> </trans-unit>
<trans-unit id="primary_cta.label"> <trans-unit id="primary_cta.label">
@@ -35,7 +35,7 @@
<source>Background Image (optional)</source> <source>Background Image (optional)</source>
</trans-unit> </trans-unit>
<trans-unit id="background_image.description"> <trans-unit id="background_image.description">
<source>Optionales Hintergrundbild — überlagert die Background-Variant-Farbe</source> <source>Optional background image — overlays the background variant colour</source>
</trans-unit> </trans-unit>
</body> </body>
</file> </file>
@@ -6,7 +6,7 @@
<div class="vitec-preview vitec-preview--{data.background_variant}"> <div class="vitec-preview vitec-preview--{data.background_variant}">
<f:comment>Thumbnail (Background Image oder Farb-Placeholder)</f:comment> <f:comment>Thumbnail (background image or colour placeholder)</f:comment>
<f:if condition="{data.background_image.0}"> <f:if condition="{data.background_image.0}">
<f:then> <f:then>
<f:image image="{data.background_image.0}" <f:image image="{data.background_image.0}"
@@ -37,7 +37,7 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#c00;">⚠ Headline fehlt</em> <em style="color:#c00;">⚠ Headline missing</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
@@ -75,7 +75,7 @@
</f:switch> </f:switch>
</span> </span>
<f:comment>Layout Variant Badge mit Icon</f:comment> <f:comment>Layout variant badge with icon</f:comment>
<span class="vitec-badge"> <span class="vitec-badge">
<f:switch expression="{data.layout_variant}"> <f:switch expression="{data.layout_variant}">
<f:case value="centered">◎ Centered</f:case> <f:case value="centered">◎ Centered</f:case>
@@ -85,9 +85,9 @@
</f:switch> </f:switch>
</span> </span>
<f:comment>Warnung wenn Primary-CTA-Link fehlt</f:comment> <f:comment>Warning when primary CTA link is missing</f:comment>
<f:if condition="{data.primary_cta.url} == ''"> <f:if condition="{data.primary_cta.url} == ''">
<span class="vitec-badge vitec-badge--warning">⚠ Kein Primary-Link</span> <span class="vitec-badge vitec-badge--warning">⚠ Primary link missing</span>
</f:if> </f:if>
</div> </div>
@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<rect x="1.5" y="2" width="13" height="3.4" rx="0.8" fill="#000" opacity="0.55"/>
<polygon points="12.2,3.2 13.6,3.2 12.9,4.4" fill="#fff"/>
<rect x="1.5" y="6.3" width="13" height="3.4" rx="0.8" fill="none" stroke="#000" stroke-width="1" opacity="0.6"/>
<polygon points="12.4,7.2 12.4,8.6 13.4,7.9" fill="#000" opacity="0.6"/>
<rect x="1.5" y="10.6" width="13" height="3.4" rx="0.8" fill="none" stroke="#000" stroke-width="1" opacity="0.6"/>
<polygon points="12.4,11.5 12.4,12.9 13.4,12.2" fill="#000" opacity="0.6"/>
</svg>

After

Width:  |  Height:  |  Size: 622 B

@@ -0,0 +1,56 @@
name: vitec/faq
group: vitec_custom_components
prefixFields: true
prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields:
- identifier: header
useExistingField: true
required: true
- type: Linebreak
- identifier: header_layout
useExistingField: true
- identifier: header_position
useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link
useExistingField: true
- type: Linebreak
- identifier: subheader
useExistingField: true
- identifier: faq_items
type: Collection
labelField: question
minitems: 1
fields:
- identifier: question
type: Text
required: true
max: 255
- identifier: answer
type: Textarea
enableRichtext: true
- identifier: first_item_open
type: Checkbox
default: 1
- identifier: allow_multiple_open
type: Checkbox
default: 0
- identifier: debug
type: Checkbox
default: 0
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<xliff version="1.2">
<file source-language="en" datatype="plaintext" original="messages">
<body>
<trans-unit id="title">
<source>VITEC · FAQ</source>
</trans-unit>
<trans-unit id="description">
<source>Accordion with repeatable question/answer items. First item can start expanded.</source>
</trans-unit>
<trans-unit id="faq_items.label">
<source>FAQ Entries</source>
</trans-unit>
<trans-unit id="faq_items.question.label">
<source>Question</source>
</trans-unit>
<trans-unit id="faq_items.answer.label">
<source>Answer</source>
</trans-unit>
<trans-unit id="first_item_open.label">
<source>Open first item by default</source>
</trans-unit>
<trans-unit id="allow_multiple_open.label">
<source>Allow multiple items open at once</source>
</trans-unit>
<trans-unit id="debug.label">
<source>Allow Debug Output</source>
</trans-unit>
</body>
</file>
</xliff>
@@ -0,0 +1,66 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:layout name="Preview"/>
<f:section name="Content">
<f:asset.css identifier="vitec-backend-preview" href="EXT:vitec/Resources/Public/Css/backend-preview.css"/>
<div class="vitec-preview">
<div class="vitec-preview__thumb-placeholder">?</div>
<div class="vitec-preview__body">
<div class="vitec-preview__label">VITEC · FAQ</div>
<h3 class="vitec-preview__headline">
<f:if condition="{data.header}">
<f:then>{data.header}</f:then>
<f:else>
<em style="color:#c00;">⚠ Headline missing</em>
</f:else>
</f:if>
</h3>
<f:if condition="{data.subheader}">
<div class="vitec-preview__subline">{data.subheader}</div>
</f:if>
<f:if condition="{data.faq_items}">
<f:then>
<div class="vitec-preview__body-text">
<f:for each="{data.faq_items}" as="item" iteration="i">
<f:if condition="{i.index} < 3">▸ {item.question}<br/></f:if>
</f:for>
</div>
</f:then>
<f:else>
<div class="vitec-preview__body-text">
<em style="color:#c00;">⚠ No FAQ entries</em>
</div>
</f:else>
</f:if>
</div>
<div class="vitec-preview__settings">
<span class="vitec-badge">
❓ <f:count subject="{data.faq_items}"/> entries
</span>
<f:if condition="{data.first_item_open}">
<span class="vitec-badge">▽ First item open</span>
</f:if>
<f:if condition="{data.allow_multiple_open}">
<span class="vitec-badge">≡ Multiple open allowed</span>
</f:if>
<f:if condition="{data.debug}">
<span class="vitec-badge vitec-badge--warning">⚙ Debug ON</span>
</f:if>
</div>
</div>
</f:section>
</html>
@@ -0,0 +1,3 @@
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<!-- Headless mode: JSON is built by nb-headless-content-blocks -->
</html>
@@ -8,22 +8,31 @@ fields:
type: Text type: Text
max: 50 max: 50
- identifier: header_section
type: Palette
label: Header
fields:
- identifier: header - identifier: header
useExistingField: true useExistingField: true
required: true required: true
- type: Linebreak
- identifier: header_layout - identifier: header_layout
useExistingField: true useExistingField: true
- identifier: header_position - identifier: header_position
useExistingField: true useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link - identifier: header_link
useExistingField: true useExistingField: true
- type: Linebreak
- identifier: subheader - identifier: subheader
useExistingField: true useExistingField: true
- identifier: bodytext - identifier: bodytext
useExistingField: true useExistingField: true
enableRichtext: true enableRichtext: true
@@ -31,7 +31,7 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#c00;">⚠ Headline fehlt</em> <em style="color:#c00;">⚠ Headline missing</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
@@ -53,7 +53,7 @@
</f:if> </f:if>
</div> </div>
<f:comment>Settings Sidebar (ohne Partial)</f:comment> <f:comment>Settings sidebar (without partial)</f:comment>
<div class="vitec-preview__settings"> <div class="vitec-preview__settings">
<span class="vitec-badge"> <span class="vitec-badge">
<span class="vitec-badge__dot vitec-badge__dot--{data.background_variant}"></span> <span class="vitec-badge__dot vitec-badge__dot--{data.background_variant}"></span>
@@ -65,7 +65,7 @@
</f:if> </f:if>
<f:if condition="{data.hero_image.0} == ''"> <f:if condition="{data.hero_image.0} == ''">
<span class="vitec-badge vitec-badge--warning">⚠ Kein Bild</span> <span class="vitec-badge vitec-badge--warning">⚠ No image</span>
</f:if> </f:if>
</div> </div>
@@ -3,23 +3,32 @@ group: vitec_custom_components
prefixFields: true prefixFields: true
prefixType: vendor prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields: fields:
- identifier: header - identifier: header
useExistingField: true useExistingField: true
required: true required: true
- type: Linebreak
- identifier: header_layout - identifier: header_layout
useExistingField: true useExistingField: true
- identifier: header_position - identifier: header_position
useExistingField: true useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link - identifier: header_link
useExistingField: true useExistingField: true
- type: Linebreak
- identifier: subheader - identifier: subheader
useExistingField: true useExistingField: true
- identifier: bodytext - identifier: bodytext
useExistingField: true useExistingField: true
enableRichtext: true enableRichtext: true
@@ -12,7 +12,7 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#c00;">⚠ Headline fehlt</em> <em style="color:#c00;">⚠ Headline missing</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
@@ -61,7 +61,7 @@
</f:if> </f:if>
<f:if condition="{data.button_text} && {data.button_link.url} == ''"> <f:if condition="{data.button_text} && {data.button_link.url} == ''">
<span class="vitec-badge vitec-badge--warning">⚠ Button-Text ohne Link</span> <span class="vitec-badge vitec-badge--warning">⚠ Button text without link</span>
</f:if> </f:if>
<f:if condition="{data.debug}"> <f:if condition="{data.debug}">
@@ -3,23 +3,32 @@ group: vitec_custom_components
prefixFields: true prefixFields: true
prefixType: vendor prefixType: vendor
fields:
- identifier: header_section
type: Palette
label: Header
fields: fields:
- identifier: header - identifier: header
useExistingField: true useExistingField: true
required: true required: true
- type: Linebreak
- identifier: header_layout - identifier: header_layout
useExistingField: true useExistingField: true
- identifier: header_position - identifier: header_position
useExistingField: true useExistingField: true
- identifier: date
useExistingField: true
- type: Linebreak
- identifier: header_link - identifier: header_link
useExistingField: true useExistingField: true
- type: Linebreak
- identifier: subheader - identifier: subheader
useExistingField: true useExistingField: true
- identifier: youtube_code - identifier: youtube_code
type: Text type: Text
max: 50 max: 50
@@ -27,7 +27,7 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#c00;">⚠ Headline fehlt</em> <em style="color:#c00;">⚠ Headline missing</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
@@ -59,7 +59,7 @@
</f:if> </f:if>
<f:if condition="!{data.youtube_code} && !{data.custom_video.0}"> <f:if condition="!{data.youtube_code} && !{data.custom_video.0}">
<span class="vitec-badge vitec-badge--warning">⚠ Kein Video-Source</span> <span class="vitec-badge vitec-badge--warning">⚠ No video source</span>
</f:if> </f:if>
<f:if condition="{data.hide_controls}"> <f:if condition="{data.hide_controls}">
<span class="vitec-badge">⏸ Controls hidden</span> <span class="vitec-badge">⏸ Controls hidden</span>
+619
View File
@@ -0,0 +1,619 @@
# VITEC Headless JSON Architecture — Software Architecture and Interface Specification
| | |
|---|---|
| **Document identifier** | EVO‑VITEC‑HL‑001 |
| **Version** | 1.0 |
| **Status** | Released |
| **Date** | 2026‑07‑09 |
| **Applies to** | `evomedien/vitec` on TYPO3 v14.3 (headless) |
| **Owner** | evomedien — VITEC relaunch |
This document is drafted in the style of, and adopts the terminology conventions of,
ISO/IEC/IEEE 42010 (architecture description), ISO/IEC/IEEE 26514 (information for
users) and ISO/IEC 25010 (product quality). The key words **shall**, **should** and
**may** are to be interpreted as normative requirements, recommendations and
permissions respectively.
---
## Foreword
The VITEC web platform is a *headless* TYPO3 installation: the CMS does not render
HTML pages, it emits JSON that is consumed by a separate React front end. This
specification describes the architecture, the public JSON interface, and the
engineering conventions that keep the JSON output **consistent** across all content
types and **maintainable** across TYPO3 and extension upgrades.
It supersedes, as the authoritative reference, the informal tutorial
`Documentation/HeadlessIntegration.md`, which is retained as an informative how‑to.
## Introduction
The platform combines the generic headless page renderer (`friendsoftypo3/headless`)
with three sources of content JSON:
1. **Custom plugins** (product, use case/success story, market, solution, downloads,
datasheets, events, news) rendered by dedicated *UserFunc* classes;
2. **Layout containers** (b13/container based column grids and a card carousel)
rendered by a *DataProcessor*;
3. **Content Blocks** (`friendsoftypo3/content-blocks`) serialised automatically by
`nb-headless-content-blocks`.
All three are unified under a single **content‑element envelope** so that the front
end can consume every element with one predictable shape.
---
## 1 Scope
### 1.1 In scope
This document specifies:
- the runtime environment and the software stack (Clause 5);
- the JSON rendering pipeline and its layers (Clause 6);
- the public JSON interface — envelope, payloads, structured data (Clause 7);
- the catalogue of content types and their JSON keys (Clause 8);
- the mandatory conventions for implementing and extending renderers (Clause 9);
- maintainability and upgrade‑safety requirements (Clause 10);
- conformance criteria (Clause 11).
### 1.2 Out of scope
Front‑end (React) implementation, hosting/deployment, the editorial (backend) TCA
form design except where it determines JSON output, and non‑headless (Fluid) rendering
paths.
## 2 Normative references
The following documents are referred to in the text. For dated references, only the
edition cited applies.
- ISO/IEC/IEEE 42010, *Software, systems and enterprise — Architecture description*
- ISO/IEC 25010, *Systems and software Quality Requirements and Evaluation (SQuaRE) — Product quality model*
- ISO/IEC/IEEE 26514, *Systems and software engineering — Design and development of information for users*
- ISO 8601‑1, *Date and time — Representations for information interchange*
- IETF RFC 8259, *The JavaScript Object Notation (JSON) Data Interchange Format*
- IETF RFC 2119, *Key words for use in RFCs to indicate requirement levels*
- schema.org vocabulary (informative), *https://schema.org*
## 3 Terms and definitions
**3.1 headless** — operating mode in which TYPO3 returns JSON instead of HTML;
enabled per site by `headless: 1` and the headless Site Sets.
**3.2 content element** — a `tt_content` record; the atomic unit of page content.
**3.3 CType** — the content‑element type identifier stored in `tt_content.CType`
(e.g. `vitec_productlist`, `news_pi1`, `vitec_cols_50_50`).
**3.4 envelope** — the invariant outer JSON structure shared by every content element
(Clause 7.2).
**3.5 payload** — the domain‑specific JSON produced for one content element and placed
inside the envelope (Clause 7.3).
**3.6 renderer** — a *UserFunc* class under `Classes/UserFunc/` that produces a payload.
**3.7 serializer** — a service class under `Classes/Service/` that converts a domain
record into JSON, used by one or more renderers.
**3.8 resolver / processor** — `ContentElementResolver` and `ContainerChildrenProcessor`;
they normalise a raw `tt_content` row into an envelope and resolve nested elements.
**3.9 container** — a b13/container CType that owns child content elements via
`tx_container_parent` and emits them as `items`.
**3.10 Content Block** — a declaratively defined content element
(`friendsoftypo3/content-blocks`), serialised by `nb-headless-content-blocks`.
## 4 Symbols and abbreviated terms
| Term | Meaning |
|---|---|
| FAL | File Abstraction Layer (TYPO3 file handling) |
| IRRE | Inline Relational Record Editing (`type: inline`) |
| MM | Many‑to‑many junction table |
| CB | Content Block |
| TS | TypoScript |
| CE | Content element |
---
## 5 Runtime environment (architecture context)
### 5.1 Software stack
| Component | Version | Role |
|---|---|---|
| TYPO3 CMS | ^14.3 | Core CMS |
| PHP | 8.x (per TYPO3 14) | Runtime |
| `friendsoftypo3/headless` | ^5.0@rc | Page‑to‑JSON renderer, `lib.contentElement` |
| `friendsoftypo3/content-blocks` | ^2.4 | Declarative content elements |
| `netzbewegung/nb-headless-content-blocks` | ^0.0.23 | Content Blocks → JSON |
| `b13/container` | ^3.1 | Nested column containers |
| `georgringer/news` | ^14.0 | News records and plugins |
| `evomedien/vitec` | ^1.0 | This project’s custom extension |
> **NOTE** `friendsoftypo3/headless` is pinned to a **release candidate** (`^5.0@rc`).
> This is an upgrade‑sensitivity point; see 10.4.
### 5.2 Site configuration
The headless mode is activated in `config/sites/vitec/config.yaml`:
```yaml
base: /
headless: 1
frontendBase: ''
dependencies:
- friendsoftypo3/headless
- friendsoftypo3/headless-mixed
- nb-headless-content-blocks/headless-content-blocks
- georgringer/news
```
The Site **Sets** listed under `dependencies` load, in order, the headless TypoScript
base, the mixed‑mode overrides, the Content Blocks JSON integration and the News
integration. The VITEC Set (`EXT:vitec/Configuration/Sets/Vitecset`) layers the custom
definitions on top. The headless page response carries
`Content-Type: application/json; charset=utf-8`. Slug routing is configured with route
enhancers for products (`tx_vitec_domain_model_product.slug`) and news detail
(`path_segment`).
### 5.3 Architectural principles (rationale)
- **P1 — One envelope.** Every content element, regardless of source, is exposed with
the same outer shape so the front end has a single rendering contract.
- **P2 — Payload isolation.** Domain JSON is produced in PHP, fully decoupled from
TypoScript, so business logic is testable and versionable.
- **P3 — Dual entry.** Each renderer works both as a top‑level plugin and as a nested
child of a container (Clause 9.3).
- **P4 — Fail soft.** A failing element yields empty output, never a broken page
(Clause 9.5).
---
## 6 Rendering pipeline
The JSON for one page is assembled top‑down through the following layers.
```
HTTP request (Accept: application/json, headless:1)
│
▼
[L1] Page renderer friendsoftypo3/headless
│ builds { meta, content[], … , jsonLd }
▼
[L2] Content‑element envelope lib.contentElement / lib.contentElementWithHeader
│ per CType: id, type, colPos, appearance, content{header,…}
▼
[L3] Payload injection USER cObj → Classes/UserFunc/*JsonRenderer::render
│ places domain JSON under content.<key>
▼
[L4] Containers tt_content.vitec_cols_* = JSON
│ ContainerChildrenProcessor → items[].contentElements[]
▼
[L5] Normalisation & nesting ContentElementResolver / PLUGIN_RENDERERS
▼
[L6] Structured data (JSON‑LD) PageJsonLdRenderer + StructuredDataService
```
### 6.1 L1 — Page renderer
`friendsoftypo3/headless` converts the requested page into a JSON document containing
page metadata, the ordered array of content elements, navigation and the JSON‑LD
graph. VITEC does not replace this layer; it contributes elements to `content[]`
(L2–L5) and the `jsonLd` field (L6).
### 6.2 L2 — Content‑element envelope
Each CType is bound to a headless library object:
```typoscript
tt_content.<ctype> < lib.contentElementWithHeader
```
`lib.contentElement` provides `id`, `type`, `colPos`, `categories`, `appearance`.
`lib.contentElementWithHeader` additionally provides, under `content`, the standard
header fields: `header`, `subheader`, `headerLayout`, `headerPosition`, `headerLink`
(link resolved via typolink). **All VITEC plugins and all News CTypes inherit
`lib.contentElementWithHeader`**, giving a uniform header section in both backend and
JSON (see the companion header convention).
### 6.3 L3 — Payload injection
The domain payload is added as a `USER` content object under `content.fields.<key>`:
```typoscript
tt_content.vitec_productlist < lib.contentElementWithHeader
tt_content.vitec_productlist {
fields {
content {
fields {
products = USER
products.userFunc = Evomedien\Vitec\UserFunc\ProductListJsonRenderer->render
}
}
}
}
```
`render()` returns a JSON string that the headless JSON cObject embeds verbatim at
`content.products`. The key is **plural for list plugins** and **singular for detail
plugins** (Clause 9.6).
### 6.4 L4 — Containers
Column containers are defined as a self‑contained JSON object, not via
`lib.contentElement`:
```typoscript
tt_content.vitec_cols_50_50 = JSON
tt_content.vitec_cols_50_50.fields {
id … type … appearance … header … subheader …
gap = TEXT # tx_vitec_gap (whole‑grid gap)
items = JSON
items.dataProcessing.10 = Evomedien\Vitec\DataProcessing\ContainerChildrenProcessor
}
tt_content.vitec_cols_33_66 < tt_content.vitec_cols_50_50
tt_content.vitec_container < tt_content.vitec_cols_50_50 # single column, no gap
tt_content.vitec_cards_carousel < tt_content.vitec_cols_50_50 # + carousel settings
```
> **RULE (normative)** Container CTypes **shall** be derived with the **copy**
> operator `<`, never the reference operator `=<`. A `tt_content → tt_content`
> reference is not recognised as an independent renderer by the headless content
> mapper and silently falls back to raw output (see 10.5, and Annex B‑1).
### 6.5 L5 — Normalisation and nested plugins
`ContainerChildrenProcessor` queries children by `tx_container_parent`, groups them by
`colPos` and emits, per column, a flex configuration plus the resolved children. Each
child is normalised by the same envelope logic as `ContentElementResolver`. When a
child is itself a VITEC plugin, it is resolved through the shared `PLUGIN_RENDERERS`
map (Clause 7.4).
### 6.6 L6 — Structured data (JSON‑LD)
`PageJsonLdRenderer` (bound at `page…fields.jsonLd`) assembles a schema.org `@graph`
via `StructuredDataService`, encoded with `JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE`.
Node types and their triggers:
| Node | Emitted when |
|---|---|
| `Organization` | every page (from site settings `seo.organization.*`) |
| `WebSite` | only on the site root page |
| `BreadcrumbList` | from the rootline (spacer/folder/recycler doktypes skipped) |
| `NewsArticle` | on news‑detail page layouts (`pages.layout ∈ {13,14,15}`) |
| `FAQPage` | when the page contains `vitec_faq` elements → `vitec_faq_items` collection |
| `ExhibitionEvent` | from `vitec_eventlist` elements → `tx_vitec_domain_model_event` (windowed by the FlexForm `daysinadvance`) |
| `Product`, `VideoObject` | via `ProductShowJsonRenderer` for product detail pages |
FAQ and event nodes are collected from the respective tables; event image URLs are
currently built with a hard‑coded `/fileadmin` prefix (see 10.3 / Annex B‑5).
---
## 7 JSON interface specification
### 7.1 Encoding
Output **shall** be RFC 8259 JSON, UTF‑8. Timestamps **shall** be Unix epoch seconds
(integer); where ISO 8601 strings are required by schema.org they are produced inside
the JSON‑LD layer.
### 7.2 Content‑element envelope
Every element in `content[]` conforms to:
```jsonc
{
"id": 123, // tt_content.uid
"type": "vitec_productlist", // tt_content.CType
"colPos": 0,
"appearance": {
"layout": "0",
"frameClass": "default",
"spaceBefore": "",
"spaceAfter": ""
},
"content": { // present for lib.contentElement(WithHeader) CTypes
"header": "…", "subheader": "…",
"headerLayout": 2, "headerPosition": "", "headerLink": "https://…",
"<key>": { /* payload, Clause 7.3 */ }
}
}
```
Elements produced by the resolver/processor (container children, inline story CEs) use
a lean variant of the envelope:
```jsonc
{ "id": 456, "type": "text", "colPos": 211, "sorting": 1,
"appearance": { … }, "data": { /* non‑system fields */ } }
```
### 7.3 Payload keys
| Kind | Key | Cardinality |
|---|---|---|
| List plugin | plural noun (`products`, `usecases`, `news → items`) | array |
| Detail plugin | singular noun (`product`, `usecase`, `market`, `solution`) | object |
| Container | `items` | array of `{config, contentElements}` |
### 7.4 Container payload
```jsonc
{
"type": "vitec_cols_33_66",
"header": "…", "headerLayout": 2, "headerLink": "…",
"gap": "3",
"items": [
{ "config": { "colPos": 251, "align": "stretch", "justify": "flex-start" },
"contentElements": [ /* normalised children, recursively */ ] },
{ "config": { "colPos": 252, "align": "center", "justify": "space-between" },
"contentElements": [ … ] }
]
}
```
- `gap` is a **parent‑level** property (whole‑grid gap).
- `align`/`justify` are **per‑column** and are read from the parent record
(`tx_vitec_col{N}_align/justify`); they therefore apply to **all** children of that
column. These container‑only fields **shall not** appear in a child’s `data`
(enforced by `CONTAINER_FIELDS` filtering).
### 7.5 Success Story (use case) detail payload
Produced by `UsecaseSerializer::serializeDetail()` — the reference implementation of
the centralised pattern (Clause 9.2):
```jsonc
{
"uid": 1, "title": "…", "slug": "…", "subtitle": "…", "teaser": "…",
"cardImage": { "url": "…", "srcset": [ … ] }, "customerLogo": { … },
"hero": { "bgImage": …, "smallImage": …, "video": …,
"overlayColor": "#000", "overlayOpacity": 0.4,
"layout": "fullscreen", "textTheme": "light" },
"contentElements": [ /* inline CEs; the vitec_columns block is resolved specially */ ],
"related": { "show": true, "market": {…}, "solutions": [ … ], "products": [ … ],
"categories": [ … ] },
"seo": { "title": "…", "description": "…", "canonical": "…",
"robots": { "noIndex": false, "noFollow": false },
"openGraph": { "title": …, "description": …, "image": … },
"twitter": { "title": …, "description": …, "image": … } },
"appearance": { "layoutVariant": "standard", "backgroundVariant": "none",
"accentColor": "", "featured": false, … }
}
```
SEO fields use fallback resolution (`seo_* → title/teaser`; `og_* → seo_* → title`;
`twitter_* → og_*`; images `og_image → card_image → hero_bgimage`) so the front end
always receives complete metadata.
### 7.6 News payload
Produced by `NewsJsonRenderer` under `content.news`:
```jsonc
{ "mode": "list", // "list" | "detail" | "error"
"items": [ { "uid": 1, "title": "…", "alternativeTitle": "…",
"pathSegment": "…", "detailUrl": "/news/…",
"canonicalUrl": "https://…", "teaser": "…", "bodytext": "…",
"datetime": 1625097600, "categories": [ … ], "media": [ … ] } ],
"settings": { "type": "news_pi1", "templateLayout": "1",
"templateLayoutLabel": "Compact List", "detailPid": 45, … } }
```
Ten News CTypes (`news_pi1`, `news_newsdetail`, `news_newsliststicky`, …) share one
renderer; `news_newsdetail` yields `mode: "detail"` with a single `news` object.
### 7.7 Content Blocks
Content Blocks (`card`, `cta-banner`, `hero-section`, `intro-paragraph`, `video`,
`faq`, `columns`) are serialised automatically by `nb-headless-content-blocks`: its
`ContentBlocksJsonDataProcessor` converts the resolved record via `RecordToArray`,
dropping system fields and recursing into files (→ URL + metadata), collections
(e.g. `faq_items` → array of child records) and typolinks. The output uses the lean
envelope (`{id, type, colPos, sorting, appearance, data}`). An optional per‑block
`headless.php` hook may transform the array (none are currently defined). Fields carry
the `vitec_` vendor prefix in storage. The **`vitec_columns`** block is
the single exception: because it is authored **inline inside a Success Story record**,
its collection items are resolved explicitly by `UsecaseSerializer::resolveColumnsElement()`
into `{ header…, layout, columns: { left: [], right: [] } }`, with the collection
storage table discovered from TCA (`foreign_table`) rather than hard‑coded.
---
## 8 Content‑type catalogue
| CType | TS pattern | Renderer / Processor | Payload key | Kind |
|---|---|---|---|---|
| `vitec_productlist` | `< lib.contentElementWithHeader` | ProductListJsonRenderer | `products` | list |
| `vitec_productshow` | `< lib.…WithHeader` | ProductShowJsonRenderer | `product` | detail |
| `vitec_usecaselist` | `< lib.…WithHeader` | UsecaseListJsonRenderer → **UsecaseSerializer** | `usecases` | list |
| `vitec_usecaseshow` | `< lib.…WithHeader` | UsecaseShowJsonRenderer → **UsecaseSerializer** | `usecase` | detail |
| `vitec_marketshow` | `< lib.…WithHeader` | MarketShowJsonRenderer | `market` | detail |
| `vitec_solutionshow` | `< lib.…WithHeader` | SolutionShowJsonRenderer | `solution` | detail |
| `vitec_downloadcard` | `< lib.…WithHeader` | DownloadcardJsonRenderer | `downloadcard` | detail |
| `vitec_downloadcardcollection` | `< lib.…WithHeader` | DownloadcardcollectionJsonRenderer | `downloadcardcollection` | list |
| `vitec_datasheets` | `< lib.…WithHeader` | DatasheetsJsonRenderer | `datasheets` | list |
| `vitec_eventlist` | `=< lib.…WithHeader` ⚠ | EventlistJsonRenderer | `eventlist` | list |
| `news_pi1` (+9 variants) | `=< lib.…WithHeader`; variants `<` | NewsJsonRenderer | `news` | list/detail |
| `vitec_cols_50_50` | `= JSON` | ContainerChildrenProcessor | `items` | container |
| `vitec_cols_33_66 / 66_33 / 33_33_33 / 25_25_25_25` | `< vitec_cols_50_50` | ContainerChildrenProcessor | `items` | container |
| `vitec_container` | `< vitec_cols_50_50` | ContainerChildrenProcessor | `items` | container (1 col) |
| `vitec_cards_carousel` | `< vitec_cols_50_50` | ContainerChildrenProcessor | `items` + `carousel` | container |
| Content Blocks (`vitec_card`, …) | Content Blocks + nb‑headless | — | (auto) | element |
| `vitec_columns` (CB, inline) | Content Blocks | UsecaseSerializer (special) | `columns` | element |
⚠ = uses the reference operator `=<`; see Annex B‑1.
---
## 9 Conventions (normative)
These rules define the **single, uniform way** to implement and extend headless
renderers. New code **shall** comply; existing code **should** be aligned when touched.
### 9.1 Renderer class shape
A payload renderer **shall**:
1. reside in `Classes/UserFunc/` and be named `<Domain><Kind>JsonRenderer`;
2. expose `#[AsAllowedCallable] public function render(string $content, array $conf): string`;
3. expose `public function renderForRecord(array $row): string` for reuse by
containers/resolvers (Clause 9.3);
4. return a JSON **string** (never an array/object).
### 9.2 Serialisation ownership
Domain‑to‑JSON conversion **should** live in a `Classes/Service/*Serializer` class, and
the renderer **should** be a thin wrapper around it. `UsecaseSerializer` is the
reference implementation. New list/detail pairs **shall** share one serializer.
### 9.3 Dual‑entry pattern
`render()` **shall** handle top‑level invocation: use `$this->cObj->data` when it is the
plugin’s own row, otherwise perform page discovery (Clause 9.4). `renderForRecord()`
**shall** accept an explicit `tt_content` row and be free of page/context assumptions,
so it can be called by `ContainerChildrenProcessor` and `ContentElementResolver`.
### 9.4 Page‑id discovery
Renderers **shall** resolve the current page id in this order:
```php
$id = $GLOBALS['TYPO3_REQUEST']?->getAttribute('frontend.page.information')?->getId() ?? 0;
if ($id <= 0) { $id = (int)($GLOBALS['TSFE']->id ?? 0); } // fallback
```
Reliance on `$GLOBALS['TSFE']->id` alone is **prohibited** (it is frequently `null` in
the JSON cObject context).
### 9.5 Fail‑soft error handling
The body of `renderForRecord()` **shall** be wrapped in
`try { … } catch (\Throwable $e) { return ''; }`. Diagnostic output **may** be emitted
only when a FlexForm `debug` flag is set. A failing element **shall not** propagate an
exception to the page.
### 9.6 Naming
- List payload keys **shall** be plural; detail keys **shall** be singular.
- JSON property names **shall** be `camelCase` for computed/composed fields; raw
passthrough fields in `data` retain their database names.
### 9.7 Nested‑plugin registration
A plugin that may appear inside a container **shall** be registered in the
`PLUGIN_RENDERERS` map in **both** `ContentElementResolver` and
`ContainerChildrenProcessor`, as `CType => [RendererClass::class, 'jsonKey']`.
> **NOTE** The duplicated map is a known maintenance hazard (10.3). Until it is
> centralised, both copies **shall** be kept in sync.
### 9.8 Header section
Every custom CE/plugin/container **shall** expose the standard header section (the core
`headers` palette; Content Blocks use a `header_section` palette). See the companion
"header convention". Header fields in JSON use the names `header`, `subheader`,
`headerLayout`, `headerPosition`, `headerLink`.
---
## 10 Maintainability and upgrade‑safety (ISO 25010)
This clause records the quality characteristics *maintainability* and *portability*
and the concrete risks and rules that preserve them.
### 10.1 Modularity — current state
Strengths: uniform envelope, dual‑entry pattern, exception safety, a shared
`PLUGIN_RENDERERS` map, and the `UsecaseSerializer` reference pattern.
Weakness: **substantial duplication** across the inline renderers.
### 10.2 Reusability — duplication register
The following logic is duplicated across many renderers and **should** be extracted
into shared services (target design in parentheses):
| Duplicated logic | Occurrences | Target service |
|---|---|---|
| FAL image/`srcset` resolution | Product(List/Show), Market, Solution, Event, Datasheets | `FalImageResolver` |
| FAL video resolution | Product, Usecase, hero | `FalImageResolver::video()` |
| `sys_category` MM query | ≥ 9 renderers | `CategoryResolver` |
| Custom MM (product↔download, …) | ≥ 5 renderers | `RelationResolver::resolveMany()` |
| Download file‑by‑convention | 5 renderers | `ConventionFileResolver` |
| `letterSequenceToRank()` sort helper | 5 renderers | static utility |
| Page‑id discovery | all renderers | `PageIdResolver::resolve()` |
> **RULE** When a shared resolver service exists, new renderers **shall** use it and
> **shall not** re‑implement the logic inline.
### 10.3 Analysability — single sources of truth
- The `PLUGIN_RENDERERS` map exists in two files (10.2/9.7); it **should** be promoted
to one shared constant/class.
- Table and field names are string literals scattered across renderers. New code
**shall** define table/field names as **class constants** (as `UsecaseSerializer`
and the processors already do) to localise upgrade impact.
### 10.4 Portability — upgrade‑sensitivity points
| Point | Risk | Mitigation |
|---|---|---|
| `friendsoftypo3/headless ^5.0@rc` | RC; `lib.contentElement(WithHeader)` shape may change | Pin exact RC; re‑verify envelope after any bump; keep payloads decoupled (P2) |
| `nb-headless-content-blocks ^0.0.x` | pre‑1.0; CB→JSON shape and collection storage may change | `vitec_columns` resolution reads the table from TCA (`foreign_table`) — do **not** hard‑code CB tables |
| `georgringer/news ^14` | 10 News CTypes hard‑mapped in `NewsJsonRenderer` | Keep the CType→layout map in one place; re‑verify on major news upgrade |
| `b13/container ^3.1` | child linkage via `tx_container_parent`; page‑module grid required | Documented limitation: containers cannot be authored inside IRRE (Annex B‑2) |
### 10.5 Modifiability — mandatory rules distilled
1. Container CTypes **shall** use `<` (copy), never `=<` (reference) — see Annex B‑1.
2. Table/field names **shall** be class constants, not inline literals.
3. Renderers **shall** reuse shared resolver services once they exist.
4. Magic numeric literals (e.g. a hard‑coded parent‑category uid) **shall** be replaced
by named constants or configuration.
---
## 11 Conformance
An implementation conforms to this specification if, for every content type it
exposes:
- **C1** the output validates as RFC 8259 JSON and matches the envelope of 7.2;
- **C2** the responsible renderer satisfies the class shape of 9.1 and the dual‑entry
pattern of 9.3;
- **C3** page‑id discovery follows 9.4 and error handling follows 9.5;
- **C4** payload keys follow 9.6 and the header section follows 9.8;
- **C5** container derivation follows the `<`‑copy rule of 6.4/10.5(1);
- **C6** any container‑nestable plugin is registered per 9.7.
Deviations are recorded in Annex B and **shall** carry a remediation plan.
---
## Annex A (normative) — Checklist: adding a new headless plugin
1. **Model/TCA/SQL** — create the domain table and TCA; define table/field names as
constants.
2. **Serializer** — add `Classes/Service/<Domain>Serializer` with `serializeListItem()`
and/or `serializeDetail()`; reuse existing resolver services.
3. **Renderer** — add `Classes/UserFunc/<Domain><Kind>JsonRenderer` per 9.1, delegating
to the serializer; implement `render()` (9.3/9.4) and `renderForRecord()`.
4. **TypoScript** — in `Configuration/Sets/Vitecset/setup.typoscript`:
`tt_content.<ctype> < lib.contentElementWithHeader` and
`content.fields.<key> = USER` + `.userFunc = …->render`.
5. **Header section** — ensure the `headers` palette is present (9.8).
6. **Nesting** — if the plugin may sit inside a container, register it in
`PLUGIN_RENDERERS` in **both** the resolver and the processor (9.7).
7. **Deploy** — `vendor/bin/typo3 database:updateschema "*.add,*.change"` then
`vendor/bin/typo3 cache:flush`.
8. **Verify** — fetch the page JSON; confirm envelope (C1), keys (C4) and nested output.
---
## Annex B (informative) — Nonconformity and technical‑debt register
The following items were identified during architecture analysis. Items marked
*(to verify)* were reported by static review and **shall** be confirmed before
remediation.
- **B‑1 — `=<` on Event/News CTypes.** `vitec_eventlist` and `news_pi1` use the
reference operator `=< lib.contentElementWithHeader`. Reference *to a `lib.*` object*
is used by headless itself and is generally safe; however, for consistency with
10.5(1) these are VERIFIED SAFE and left as-is (references to lib.* are idiomatic in headless; only tt_content-to-tt_content references are unsafe, and those are already `<` for containers).
- **B‑2 — Containers cannot be authored inline (IRRE).** b13 container children live in
`tx_container_parent` and require the page‑module grid; they cannot be created inside
an inline field. This is a platform limitation, not a defect. The Success Story
"Columns" Content Block (`vitec_columns`) is the sanctioned inline alternative.
- **B‑3 — Duplicated `PLUGIN_RENDERERS` map** in `ContentElementResolver` and
`ContainerChildrenProcessor` (9.7/10.3).
- **B‑4 — Inline duplication** of image/category/MM/page‑id logic (10.2).
- **B‑5 — Hard‑coded literals** — model/MM table names, a `/fileadmin` prefix for
event JSON‑LD image URLs (`PageJsonLdRenderer`), and at least one magic
parent‑category uid appear as inline literals across download/datasheet/structured‑data
code *(to verify and extract to constants)*. `COLPOS_TO_COLUMN` in
`ContainerChildrenProcessor` must be kept in sync with the container TCA colPos values.
- **B‑6 — `DownloadcardcollectionJsonRenderer`**: two misplaced thumbnail output lines referenced an undefined variable in the image resolver. FIXED 2026-07-09 (removed; the PDF-thumbnail method is unaffected).
- **B‑7 — v13→v14 `CType`/`list_type` compatibility branches** in some download/
datasheet renderers FIXED 2026-07-09: the unsatisfiable legacy OR branch was removed; the query now filters on the v14 CType only.
---
## Annex C (informative) — Related documents
- `Documentation/HeadlessIntegration.md` — informal how‑to (superseded as the
authoritative reference by this document).
- Header convention — the uniform header section across CEs, plugins and containers.
- `Configuration/Sets/Vitecset/setup.typoscript` — the single TypoScript entry point.
*End of document EVO‑VITEC‑HL‑001 v1.0.*
@@ -111,6 +111,73 @@
<trans-unit id="column.sidebar_33" resname="column.sidebar_33"> <trans-unit id="column.sidebar_33" resname="column.sidebar_33">
<source>Sidebar (33%)</source> <source>Sidebar (33%)</source>
</trans-unit> </trans-unit>
<trans-unit id="cards_carousel.title" resname="cards_carousel.title">
<source>VITEC · Cards Carousel</source>
</trans-unit>
<trans-unit id="cards_carousel.description" resname="cards_carousel.description">
<source>Horizontal carousel/slider. Place VITEC Card elements (or other content) inside; arrows, dots, autoplay and slides-per-view are configurable.</source>
</trans-unit>
<trans-unit id="cards_carousel.column" resname="cards_carousel.column">
<source>Slides</source>
</trans-unit>
<trans-unit id="cards_carousel.flexform.label" resname="cards_carousel.flexform.label">
<source>Carousel Settings</source>
</trans-unit>
<trans-unit id="flex.align.label" resname="flex.align.label">
<source>Align</source>
</trans-unit>
<trans-unit id="flex.align.flex_start" resname="flex.align.flex_start">
<source>flex-start</source>
</trans-unit>
<trans-unit id="flex.align.center" resname="flex.align.center">
<source>center</source>
</trans-unit>
<trans-unit id="flex.align.flex_end" resname="flex.align.flex_end">
<source>flex-end</source>
</trans-unit>
<trans-unit id="flex.align.stretch" resname="flex.align.stretch">
<source>stretch</source>
</trans-unit>
<trans-unit id="flex.justify.label" resname="flex.justify.label">
<source>Justify</source>
</trans-unit>
<trans-unit id="flex.justify.flex_start" resname="flex.justify.flex_start">
<source>flex-start</source>
</trans-unit>
<trans-unit id="flex.justify.center" resname="flex.justify.center">
<source>center</source>
</trans-unit>
<trans-unit id="flex.justify.space_between" resname="flex.justify.space_between">
<source>space-between</source>
</trans-unit>
<trans-unit id="flex.justify.space_around" resname="flex.justify.space_around">
<source>space-around</source>
</trans-unit>
<trans-unit id="flex.justify.space_evenly" resname="flex.justify.space_evenly">
<source>space-evenly</source>
</trans-unit>
<trans-unit id="palette.grid" resname="palette.grid">
<source>Grid</source>
</trans-unit>
<trans-unit id="gap.label" resname="gap.label">
<source>Gap</source>
</trans-unit>
<trans-unit id="gap.option.1" resname="gap.option.1">
<source>1</source>
</trans-unit>
<trans-unit id="gap.option.2" resname="gap.option.2">
<source>2</source>
</trans-unit>
<trans-unit id="gap.option.3" resname="gap.option.3">
<source>3</source>
</trans-unit>
<trans-unit id="gap.option.4" resname="gap.option.4">
<source>4</source>
</trans-unit>
<trans-unit id="gap.option.5" resname="gap.option.5">
<source>5</source>
</trans-unit>
</body> </body>
</file> </file>
</xliff> </xliff>
@@ -13,16 +13,16 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#888;">Download Card (kein Header)</em> <em style="color:#888;">Download Card (no header)</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
<div class="vitec-preview__body-text"> <div class="vitec-preview__body-text">
<small> <small>
Konfiguration im FlexForm: Configured via FlexForm:
<strong>Produkt</strong> oder <strong>Download</strong> wählen, Select a <strong>product</strong> or <strong>download</strong>,
Layout-Variante setzen. set the layout variant.
</small> </small>
</div> </div>
</div> </div>
@@ -13,15 +13,15 @@
<f:if condition="{data.header}"> <f:if condition="{data.header}">
<f:then>{data.header}</f:then> <f:then>{data.header}</f:then>
<f:else> <f:else>
<em style="color:#888;">Download Card Collection (kein Header)</em> <em style="color:#888;">Download Card Collection (no header)</em>
</f:else> </f:else>
</f:if> </f:if>
</h3> </h3>
<div class="vitec-preview__body-text"> <div class="vitec-preview__body-text">
<small> <small>
Multi-Download-Auswahl mit optionalem Header-Image. Multi-download selection with optional header image.
Konfiguration im FlexForm. Configured via FlexForm.
</small> </small>
</div> </div>
</div> </div>
@@ -0,0 +1,22 @@
/* =============================================================
VITEC — container collapse/expand in the Page module
============================================================= */
/* Hide the container body (nested child columns) when collapsed. */
.t3-page-ce.vitec-ce-collapsed > .t3-page-ce-element > .t3-page-ce-body {
display: none;
}
/* Optional: dim/round the collapsed container header a touch. */
.t3-page-ce.vitec-ce-collapsed > .t3-page-ce-element > .t3-page-ce-header {
border-bottom: 1px dashed var(--typo3-component-border-color, #ccc);
}
/* Chevron rotates when collapsed. */
.vitec-collapse-toggle typo3-backend-icon {
display: inline-flex;
transition: transform 0.15s ease-in-out;
}
.t3-page-ce.vitec-ce-collapsed .vitec-collapse-toggle typo3-backend-icon {
transform: rotate(180deg);
}
View File
File mode changed.
@@ -0,0 +1,11 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<rect x="1.5" y="2.5" width="13" height="11.5" rx="1.2" fill="none" stroke="#26358c" stroke-width="1.2"/>
<rect x="1.5" y="2.5" width="13" height="3" rx="1.2" fill="#26358c"/>
<rect x="4" y="1" width="1.6" height="3" rx="0.8" fill="#26358c"/>
<rect x="10.4" y="1" width="1.6" height="3" rx="0.8" fill="#26358c"/>
<rect x="3.5" y="7" width="2.4" height="2.2" rx="0.3" fill="#f47937"/>
<rect x="6.9" y="7" width="2.4" height="2.2" rx="0.3" fill="#26358c" opacity="0.35"/>
<rect x="10.3" y="7" width="2.4" height="2.2" rx="0.3" fill="#26358c" opacity="0.35"/>
<rect x="3.5" y="10.2" width="2.4" height="2.2" rx="0.3" fill="#26358c" opacity="0.35"/>
<rect x="6.9" y="10.2" width="2.4" height="2.2" rx="0.3" fill="#26358c" opacity="0.35"/>
</svg>

After

Width:  |  Height:  |  Size: 844 B

@@ -0,0 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<polygon points="1,8 3,6.4 3,9.6" fill="#000" opacity="0.5"/>
<polygon points="15,8 13,6.4 13,9.6" fill="#000" opacity="0.5"/>
<rect x="3.8" y="3.5" width="3.4" height="8" rx="0.6" fill="none" stroke="#000" stroke-width="1" opacity="0.7"/>
<rect x="4.4" y="4.2" width="2.2" height="2.4" rx="0.3" fill="#000" opacity="0.3"/>
<rect x="4.4" y="9.6" width="2.2" height="1" rx="0.4" fill="#f47937"/>
<rect x="8.6" y="3.5" width="3.4" height="8" rx="0.6" fill="none" stroke="#000" stroke-width="1" opacity="0.7"/>
<rect x="9.2" y="4.2" width="2.2" height="2.4" rx="0.3" fill="#000" opacity="0.3"/>
<rect x="9.2" y="9.6" width="2.2" height="1" rx="0.4" fill="#f47937"/>
<circle cx="6.6" cy="13.4" r="0.7" fill="#000" opacity="0.8"/>
<circle cx="8.6" cy="13.4" r="0.7" fill="#000" opacity="0.3"/>
<circle cx="10.6" cy="13.4" r="0.7" fill="#000" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 966 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 349 B

After

Width:  |  Height:  |  Size: 349 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 278 B

After

Width:  |  Height:  |  Size: 278 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 201 B

After

Width:  |  Height:  |  Size: 201 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 203 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 202 B

After

Width:  |  Height:  |  Size: 202 B

View File
File mode changed.

Before

Width:  |  Height:  |  Size: 447 B

After

Width:  |  Height:  |  Size: 447 B

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<rect x="3" y="1.5" width="10" height="13" rx="1" fill="none" stroke="#26358c" stroke-width="1.2"/>
<rect x="4.6" y="4" width="6.8" height="1.1" rx="0.3" fill="#26358c" opacity="0.5"/>
<rect x="4.6" y="6.2" width="6.8" height="1.1" rx="0.3" fill="#26358c" opacity="0.5"/>
<rect x="4.6" y="8.4" width="4.5" height="1.1" rx="0.3" fill="#26358c" opacity="0.5"/>
<rect x="4.6" y="11" width="4" height="1.6" rx="0.3" fill="#f47937"/>
</svg>

After

Width:  |  Height:  |  Size: 530 B

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<rect x="1.5" y="2.5" width="13" height="11.5" rx="1.2" fill="none" stroke="#26358c" stroke-width="1.2"/>
<rect x="1.5" y="2.5" width="13" height="3" rx="1.2" fill="#26358c"/>
<rect x="4" y="1" width="1.6" height="3" rx="0.8" fill="#26358c"/>
<rect x="10.4" y="1" width="1.6" height="3" rx="0.8" fill="#26358c"/>
<rect x="3.5" y="7" width="9" height="1.4" rx="0.3" fill="#f47937"/>
<rect x="3.5" y="9.4" width="9" height="1.4" rx="0.3" fill="#26358c" opacity="0.4"/>
<rect x="3.5" y="11.8" width="6" height="1.4" rx="0.3" fill="#26358c" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 658 B

@@ -0,0 +1,131 @@
/**
* VITEC — collapse/expand toggle for container content elements in the
* TYPO3 v14 Page module.
*
* Adds a chevron button into the `.btn-toolbar` of every VITEC container
* (vitec_container, vitec_cols_*, vitec_cards_carousel — anything that
* renders a nested b13 grid). Clicking it hides/shows the container body
* (the nested child columns). State is persisted per element uid in
* localStorage, so it survives reloads.
*
* Pure DOM enhancement — no core or b13 template overrides.
*/
class VitecContainerCollapse {
constructor() {
this.storagePrefix = 'vitec-ce-collapsed-';
this.ready();
}
ready() {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => this.boot());
} else {
this.boot();
}
}
boot() {
this.processAll();
// Page module can re-render columns via AJAX; watch for new elements.
const root = document.querySelector('.t3js-page-columns-wrapper, .module, body');
if (root) {
const observer = new MutationObserver(() => {
window.clearTimeout(this._debounce);
this._debounce = window.setTimeout(() => this.processAll(), 150);
});
observer.observe(root, { childList: true, subtree: true });
}
}
processAll() {
document.querySelectorAll('.t3-page-ce[data-uid]').forEach((ce) => this.enhance(ce));
}
isCollapsed(uid) {
try {
return window.localStorage.getItem(this.storagePrefix + uid) === '1';
} catch (e) {
return false;
}
}
setCollapsed(uid, collapsed) {
try {
if (collapsed) {
window.localStorage.setItem(this.storagePrefix + uid, '1');
} else {
window.localStorage.removeItem(this.storagePrefix + uid);
}
} catch (e) {
/* storage unavailable — ignore */
}
}
enhance(ce) {
if (ce.dataset.vitecCollapseReady === '1') {
return;
}
// The direct body of THIS element (not a nested child's body).
const element = ce.querySelector(':scope > .t3-page-ce-element');
if (!element) {
return;
}
const body = element.querySelector(':scope > .t3-page-ce-body');
if (!body) {
return;
}
// A container is recognised by a nested b13 grid inside its body.
const hasNestedGrid = body.querySelector('.t3-grid-container') !== null;
if (!hasNestedGrid) {
return;
}
// Best-effort VITEC filter via the header icon identifier
// (b13 sets the icon identifier to the CType, e.g. "vitec_container").
const iconEl = ce.querySelector(
'.t3-page-ce-header-left typo3-backend-icon[identifier], .t3-page-ce-header-left [data-identifier]'
);
if (iconEl) {
const identifier =
iconEl.getAttribute('identifier') || iconEl.getAttribute('data-identifier') || '';
if (identifier && identifier.indexOf('vitec') === -1) {
return; // a non-VITEC container — leave it alone
}
}
const toolbar = element.querySelector(
':scope > .t3-page-ce-header .btn-toolbar .btn-group'
);
if (!toolbar) {
return;
}
ce.dataset.vitecCollapseReady = '1';
const uid = ce.dataset.uid;
const button = document.createElement('button');
button.type = 'button';
button.className = 'btn btn-sm btn-default btn-borderless vitec-collapse-toggle';
button.title = 'Collapse / expand container';
button.setAttribute('aria-label', 'Collapse or expand container');
button.innerHTML =
'<typo3-backend-icon identifier="actions-chevron-up" size="small"></typo3-backend-icon>';
toolbar.insertBefore(button, toolbar.firstChild);
if (this.isCollapsed(uid)) {
ce.classList.add('vitec-ce-collapsed');
}
button.addEventListener('click', (event) => {
event.preventDefault();
event.stopPropagation();
const collapsed = ce.classList.toggle('vitec-ce-collapsed');
this.setCollapsed(uid, collapsed);
});
}
}
export default new VitecContainerCollapse();
File mode changed.
+10
View File
@@ -147,4 +147,14 @@ $GLOBALS['TYPO3_CONF_VARS']['SYS']['formEngine']['nodeRegistry'][1750000000] = [
] ]
); );
ExtensionUtility::configurePlugin(
'Vitec',
'Eventlist',
[
\Evomedien\Vitec\Controller\EventController::class => 'list'
],
[
\Evomedien\Vitec\Controller\EventController::class => 'list'
]
);
})(); })();
+89 -17
View File
@@ -93,27 +93,51 @@ CREATE TABLE tx_vitec_product_related_mm (
); );
CREATE TABLE tx_vitec_domain_model_usecase ( CREATE TABLE tx_vitec_domain_model_usecase (
uid int(11) NOT NULL auto_increment,
pid int(11) DEFAULT '0' NOT NULL,
tstamp int(11) DEFAULT '0' NOT NULL,
crdate int(11) DEFAULT '0' NOT NULL,
cruser_id int(11) DEFAULT '0' NOT NULL,
deleted tinyint(4) DEFAULT '0' NOT NULL,
hidden tinyint(4) DEFAULT '0' NOT NULL,
starttime int(11) DEFAULT '0' NOT NULL,
endtime int(11) DEFAULT '0' NOT NULL,
title varchar(255) DEFAULT '' NOT NULL, title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL, slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
subtitle varchar(255) DEFAULT '' NOT NULL, subtitle varchar(255) DEFAULT '' NOT NULL,
description text,
caseimage INTEGER, card_image int(11) unsigned DEFAULT '0' NOT NULL,
logoimage INTEGER, customer_logo int(11) unsigned DEFAULT '0' NOT NULL,
singlepid varchar(255) DEFAULT '' NOT NULL, teaser text,
market int(11) unsigned DEFAULT '0' NOT NULL,
hero_bgimage int(11) unsigned DEFAULT '0' NOT NULL,
hero_small_image int(11) unsigned DEFAULT '0' NOT NULL,
hero_video int(11) unsigned DEFAULT '0' NOT NULL,
hero_overlay_color varchar(20) DEFAULT '' NOT NULL,
hero_overlay_opacity decimal(3,2) DEFAULT '0.00' NOT NULL,
content_elements int(11) unsigned DEFAULT '0' NOT NULL,
solutions int(11) unsigned DEFAULT '0' NOT NULL,
products int(11) unsigned DEFAULT '0' NOT NULL,
categories int(11) unsigned DEFAULT '0' NOT NULL,
seo_title varchar(255) DEFAULT '' NOT NULL,
seo_description text,
no_index smallint(5) unsigned DEFAULT '0' NOT NULL,
no_follow smallint(5) unsigned DEFAULT '0' NOT NULL,
canonical_link varchar(1024) DEFAULT '' NOT NULL,
og_title varchar(255) DEFAULT '' NOT NULL,
og_description text,
og_image int(11) unsigned DEFAULT '0' NOT NULL,
twitter_title varchar(255) DEFAULT '' NOT NULL,
twitter_description text,
twitter_image int(11) unsigned DEFAULT '0' NOT NULL,
layout_variant varchar(30) DEFAULT 'standard' NOT NULL,
background_variant varchar(20) DEFAULT 'none' NOT NULL,
accent_color varchar(20) DEFAULT '' NOT NULL,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL, hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL, hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
PRIMARY KEY (uid), featured smallint(5) unsigned DEFAULT '0' NOT NULL,
KEY parent (pid) show_related smallint(5) unsigned DEFAULT '1' NOT NULL,
hero_layout varchar(20) DEFAULT 'fullscreen' NOT NULL,
text_theme varchar(20) DEFAULT 'light' NOT NULL,
description text,
singlepid varchar(255) DEFAULT '' NOT NULL
); );
CREATE TABLE tx_vitec_domain_model_solution ( CREATE TABLE tx_vitec_domain_model_solution (
@@ -169,5 +193,53 @@ ADD type VARCHAR(255) DEFAULT '' NOT NULL;
CREATE TABLE tt_content ( CREATE TABLE tt_content (
tx_vitec_bg_variant VARCHAR(20) DEFAULT 'none' NOT NULL tx_vitec_bg_variant VARCHAR(20) DEFAULT 'none' NOT NULL,
tx_vitec_gap VARCHAR(5) DEFAULT '3' NOT NULL,
tx_vitec_col1_align VARCHAR(20) DEFAULT 'stretch' NOT NULL,
tx_vitec_col1_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
tx_vitec_col2_align VARCHAR(20) DEFAULT 'stretch' NOT NULL,
tx_vitec_col2_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
tx_vitec_col3_align VARCHAR(20) DEFAULT 'stretch' NOT NULL,
tx_vitec_col3_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
tx_vitec_col4_align VARCHAR(20) DEFAULT 'stretch' NOT NULL,
tx_vitec_col4_justify VARCHAR(20) DEFAULT 'flex-start' NOT NULL,
tx_vitec_usecase_content int(11) unsigned DEFAULT '0' NOT NULL
);
CREATE TABLE tx_vitec_domain_model_event (
title varchar(255) DEFAULT '' NOT NULL,
slug varchar(255) DEFAULT '' NOT NULL,
teaser varchar(255) DEFAULT '' NOT NULL,
description text,
eventstart int(11) DEFAULT '0' NOT NULL,
eventend int(11) DEFAULT '0' NOT NULL,
venue varchar(255) DEFAULT '' NOT NULL,
booth varchar(255) DEFAULT '' NOT NULL,
city varchar(255) DEFAULT '' NOT NULL,
country varchar(255) DEFAULT '' NOT NULL,
attendancemode varchar(20) DEFAULT 'offline' NOT NULL,
eventstatus varchar(20) DEFAULT 'scheduled' NOT NULL,
eventurl varchar(1024) DEFAULT '' NOT NULL,
meetinglink varchar(1024) DEFAULT '' NOT NULL,
image int(11) unsigned DEFAULT '0' NOT NULL,
hideonwebsite smallint(5) unsigned DEFAULT '0' NOT NULL,
hideonapp smallint(5) unsigned DEFAULT '0' NOT NULL
);
CREATE TABLE tx_vitec_usecase_solution_mm (
uid_local int(11) unsigned DEFAULT '0' NOT NULL,
uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
sorting int(11) unsigned DEFAULT '0' NOT NULL,
sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
);
CREATE TABLE tx_vitec_usecase_product_mm (
uid_local int(11) unsigned DEFAULT '0' NOT NULL,
uid_foreign int(11) unsigned DEFAULT '0' NOT NULL,
sorting int(11) unsigned DEFAULT '0' NOT NULL,
sorting_foreign int(11) unsigned DEFAULT '0' NOT NULL,
KEY uid_local (uid_local),
KEY uid_foreign (uid_foreign)
); );
+9 -4
View File
@@ -318,6 +318,11 @@ AddDefaultCharset utf-8
RewriteCond %{SCRIPT_FILENAME} -f RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule (?:^|/)\. - [F] RewriteRule (?:^|/)\. - [F]
# Redirect missing hardcoded Collateral file URLs to a resolver that
# chooses the newest available version by filename convention.
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^fileadmin/downloads/Collateral/(.+)$ %{ENV:CWD}collateral-fallback.php?requested=$1 [QSA,L,NC]
# Stop rewrite processing, if we are in any known directory # Stop rewrite processing, if we are in any known directory
# NOTE: Add your additional local storages here # NOTE: Add your additional local storages here
RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|uploads/) - [L] RewriteRule ^(?:fileadmin/|typo3conf/|typo3temp/|uploads/) - [L]
@@ -392,7 +397,7 @@ FileETag None
# Add your own rules here. # Add your own rules here.
AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd" #AuthUserFile "/usr/www/users/vitecevo/live/public/.htpasswd"
AuthName "DEV" #AuthName "DEV"
AuthType Basic #AuthType Basic
require valid-user #require valid-user
+13 -2
View File
@@ -9,8 +9,19 @@ RewriteRule ^ - [L]
RewriteRule ^ index.html [L] RewriteRule ^ index.html [L]
# ── Cache headers ────────────────────────────────────────────────────────────
# index.html: never cache — always fetch fresh so the browser picks up
# the latest hashed JS/CSS filenames after a deploy.
<Files "index.html"> <Files "index.html">
Header set Cache-Control "no-cache, no-store, must-revalidate" Header set Cache-Control "no-store, no-cache, must-revalidate"
Header set Pragma "no-cache" Header set Pragma "no-cache"
Header set Expires 0 Header set Expires "0"
</Files> </Files>
# Hashed assets (JS, CSS, fonts, images inside /assets/):
# Vite appends a content hash to every filename, so these are immutable.
# Cache them for 1 year — a new deploy produces new filenames automatically.
<FilesMatch "\.(js|css|woff2?|ttf|eot|otf)$">
Header set Cache-Control "public, max-age=31536000, immutable"
</FilesMatch>
@@ -1 +0,0 @@
import{B as e,E as t,F as n,I as r,K as i,L as a,M as o,N as s,O as c,P as l,R as u,U as d,V as f,j as p,k as m,r as h,w as g,z as _}from"./index-BI31wk8i.js";var v=i(d());function y(e){let{controlled:t,default:n,name:r,state:i=`value`}=e,{current:a}=v.useRef(t!==void 0),[o,s]=v.useState(n);return[a?t:o,v.useCallback(e=>{a||s(e)},[])]}var b=y,x=v.createContext(void 0);function S(){return v.useContext(x)}function C(e){return _(`PrivateSwitchBase`,e)}u(`PrivateSwitchBase`,[`root`,`checked`,`disabled`,`input`,`edgeStart`,`edgeEnd`]);var w=f(),T=e=>{let{classes:t,checked:n,disabled:r,edge:i}=e;return a({root:[`root`,n&&`checked`,r&&`disabled`,i&&`edge${l(i)}`],input:[`input`]},C,t)},E=n(m,{name:`MuiSwitchBase`})({padding:9,borderRadius:`50%`,variants:[{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:({edge:e,ownerState:t})=>e===`start`&&t.size!==`small`,style:{marginLeft:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}},{props:({edge:e,ownerState:t})=>e===`end`&&t.size!==`small`,style:{marginRight:-12}}]}),D=n(`input`,{name:`MuiSwitchBase`,shouldForwardProp:r})({cursor:`inherit`,position:`absolute`,opacity:0,width:`100%`,height:`100%`,top:0,left:0,margin:0,padding:0,zIndex:1}),O=v.forwardRef(function(e,t){let{autoFocus:n,checked:r,checkedIcon:i,defaultChecked:a,disabled:o,disableFocusRipple:s=!1,edge:c=!1,icon:l,id:u,inputProps:d,inputRef:f,name:m,onBlur:h,onChange:g,onFocus:_,readOnly:v,required:y=!1,tabIndex:x,type:C,value:O,slots:k={},slotProps:A={},...j}=e,[M,N]=b({controlled:r,default:!!a,name:`SwitchBase`,state:`checked`}),P=S(),F=e=>{_&&_(e),P&&P.onFocus&&P.onFocus(e)},I=e=>{h&&h(e),P&&P.onBlur&&P.onBlur(e)},L=e=>{if(e.nativeEvent.defaultPrevented)return;let t=e.target.checked;N(t),g&&g(e,t)},R=o;P&&R===void 0&&(R=P.disabled);let z=C===`checkbox`||C===`radio`,B={...e,checked:M,disabled:R,disableFocusRipple:s,edge:c},V=T(B),H={slots:k,slotProps:{input:d,...A}},[U,W]=p(`root`,{ref:t,elementType:E,className:V.root,shouldForwardComponentProp:!0,externalForwardedProps:{...H,component:`span`,...j},getSlotProps:e=>({...e,onFocus:t=>{e.onFocus?.(t),F(t)},onBlur:t=>{e.onBlur?.(t),I(t)}}),ownerState:B,additionalProps:{centerRipple:!0,focusRipple:!s,disabled:R,role:void 0,tabIndex:null}}),[G,K]=p(`input`,{ref:f,elementType:D,className:V.input,externalForwardedProps:H,getSlotProps:e=>({...e,onChange:t=>{e.onChange?.(t),L(t)}}),ownerState:B,additionalProps:{autoFocus:n,checked:r,defaultChecked:a,disabled:R,id:z?u:void 0,name:m,readOnly:v,required:y,tabIndex:x,type:C,...C===`checkbox`&&O===void 0?{}:{value:O}}});return(0,w.jsxs)(U,{...W,children:[(0,w.jsx)(G,{...K}),M?i:l]})});function k(e){return _(`MuiSwitch`,e)}var A=u(`MuiSwitch`,[`root`,`edgeStart`,`edgeEnd`,`switchBase`,`colorPrimary`,`colorSecondary`,`sizeSmall`,`sizeMedium`,`checked`,`disabled`,`input`,`thumb`,`track`]),j=e=>{let{classes:t,edge:n,size:r,color:i,checked:o,disabled:s}=e,c=a({root:[`root`,n&&`edge${l(n)}`,`size${l(r)}`],switchBase:[`switchBase`,`color${l(i)}`,o&&`checked`,s&&`disabled`],thumb:[`thumb`],track:[`track`],input:[`input`]},k,t);return{...t,...c}},M=n(`span`,{name:`MuiSwitch`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.edge&&t[`edge${l(n.edge)}`],t[`size${l(n.size)}`]]}})({display:`inline-flex`,width:58,height:38,overflow:`hidden`,padding:12,boxSizing:`border-box`,position:`relative`,flexShrink:0,zIndex:0,verticalAlign:`middle`,"@media print":{colorAdjust:`exact`},variants:[{props:{edge:`start`},style:{marginLeft:-8}},{props:{edge:`end`},style:{marginRight:-8}},{props:{size:`small`},style:{width:40,height:24,padding:7,[`& .${A.thumb}`]:{width:16,height:16},[`& .${A.switchBase}`]:{padding:4,[`&.${A.checked}`]:{transform:`translateX(16px)`}}}}]}),N=n(O,{name:`MuiSwitch`,slot:`SwitchBase`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.switchBase,{[`& .${A.input}`]:t.input},n.color!==`default`&&t[`color${l(n.color)}`]]}})(s(({theme:e})=>({position:`absolute`,top:0,left:0,zIndex:1,color:e.vars?e.vars.palette.Switch.defaultColor:`${e.palette.mode===`light`?e.palette.common.white:e.palette.grey[300]}`,transition:e.transitions.create([`left`,`transform`],{duration:e.transitions.duration.shortest}),[`&.${A.checked}`]:{transform:`translateX(20px)`},[`&.${A.disabled}`]:{color:e.vars?e.vars.palette.Switch.defaultDisabledColor:`${e.palette.mode===`light`?e.palette.grey[100]:e.palette.grey[600]}`},[`&.${A.checked} + .${A.track}`]:{opacity:.5},[`&.${A.disabled} + .${A.track}`]:{opacity:e.vars?e.vars.opacity.switchTrackDisabled:`${e.palette.mode===`light`?.12:.2}`},[`& .${A.input}`]:{left:`-100%`,width:`300%`}})),s(({theme:e})=>({"&:hover":{backgroundColor:e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},variants:[...Object.entries(e.palette).filter(c([`light`])).map(([t])=>({props:{color:t},style:{[`&.${A.checked}`]:{color:(e.vars||e).palette[t].main,"&:hover":{backgroundColor:e.alpha((e.vars||eLine truncated
@@ -1 +0,0 @@
import{B as e,E as t,F as n,I as r,K as i,L as a,M as o,N as s,O as c,P as l,R as u,U as d,V as f,j as p,k as m,r as h,w as g,z as _}from"./index-jouuqNaB.js";var v=i(d());function y(e){let{controlled:t,default:n,name:r,state:i=`value`}=e,{current:a}=v.useRef(t!==void 0),[o,s]=v.useState(n);return[a?t:o,v.useCallback(e=>{a||s(e)},[])]}var b=y,x=v.createContext(void 0);function S(){return v.useContext(x)}function C(e){return _(`PrivateSwitchBase`,e)}u(`PrivateSwitchBase`,[`root`,`checked`,`disabled`,`input`,`edgeStart`,`edgeEnd`]);var w=f(),T=e=>{let{classes:t,checked:n,disabled:r,edge:i}=e;return a({root:[`root`,n&&`checked`,r&&`disabled`,i&&`edge${l(i)}`],input:[`input`]},C,t)},E=n(m,{name:`MuiSwitchBase`})({padding:9,borderRadius:`50%`,variants:[{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:({edge:e,ownerState:t})=>e===`start`&&t.size!==`small`,style:{marginLeft:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}},{props:({edge:e,ownerState:t})=>e===`end`&&t.size!==`small`,style:{marginRight:-12}}]}),D=n(`input`,{name:`MuiSwitchBase`,shouldForwardProp:r})({cursor:`inherit`,position:`absolute`,opacity:0,width:`100%`,height:`100%`,top:0,left:0,margin:0,padding:0,zIndex:1}),O=v.forwardRef(function(e,t){let{autoFocus:n,checked:r,checkedIcon:i,defaultChecked:a,disabled:o,disableFocusRipple:s=!1,edge:c=!1,icon:l,id:u,inputProps:d,inputRef:f,name:m,onBlur:h,onChange:g,onFocus:_,readOnly:v,required:y=!1,tabIndex:x,type:C,value:O,slots:k={},slotProps:A={},...j}=e,[M,N]=b({controlled:r,default:!!a,name:`SwitchBase`,state:`checked`}),P=S(),F=e=>{_&&_(e),P&&P.onFocus&&P.onFocus(e)},I=e=>{h&&h(e),P&&P.onBlur&&P.onBlur(e)},L=e=>{if(e.nativeEvent.defaultPrevented)return;let t=e.target.checked;N(t),g&&g(e,t)},R=o;P&&R===void 0&&(R=P.disabled);let z=C===`checkbox`||C===`radio`,B={...e,checked:M,disabled:R,disableFocusRipple:s,edge:c},V=T(B),H={slots:k,slotProps:{input:d,...A}},[U,W]=p(`root`,{ref:t,elementType:E,className:V.root,shouldForwardComponentProp:!0,externalForwardedProps:{...H,component:`span`,...j},getSlotProps:e=>({...e,onFocus:t=>{e.onFocus?.(t),F(t)},onBlur:t=>{e.onBlur?.(t),I(t)}}),ownerState:B,additionalProps:{centerRipple:!0,focusRipple:!s,disabled:R,role:void 0,tabIndex:null}}),[G,K]=p(`input`,{ref:f,elementType:D,className:V.input,externalForwardedProps:H,getSlotProps:e=>({...e,onChange:t=>{e.onChange?.(t),L(t)}}),ownerState:B,additionalProps:{autoFocus:n,checked:r,defaultChecked:a,disabled:R,id:z?u:void 0,name:m,readOnly:v,required:y,tabIndex:x,type:C,...C===`checkbox`&&O===void 0?{}:{value:O}}});return(0,w.jsxs)(U,{...W,children:[(0,w.jsx)(G,{...K}),M?i:l]})});function k(e){return _(`MuiSwitch`,e)}var A=u(`MuiSwitch`,[`root`,`edgeStart`,`edgeEnd`,`switchBase`,`colorPrimary`,`colorSecondary`,`sizeSmall`,`sizeMedium`,`checked`,`disabled`,`input`,`thumb`,`track`]),j=e=>{let{classes:t,edge:n,size:r,color:i,checked:o,disabled:s}=e,c=a({root:[`root`,n&&`edge${l(n)}`,`size${l(r)}`],switchBase:[`switchBase`,`color${l(i)}`,o&&`checked`,s&&`disabled`],thumb:[`thumb`],track:[`track`],input:[`input`]},k,t);return{...t,...c}},M=n(`span`,{name:`MuiSwitch`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.edge&&t[`edge${l(n.edge)}`],t[`size${l(n.size)}`]]}})({display:`inline-flex`,width:58,height:38,overflow:`hidden`,padding:12,boxSizing:`border-box`,position:`relative`,flexShrink:0,zIndex:0,verticalAlign:`middle`,"@media print":{colorAdjust:`exact`},variants:[{props:{edge:`start`},style:{marginLeft:-8}},{props:{edge:`end`},style:{marginRight:-8}},{props:{size:`small`},style:{width:40,height:24,padding:7,[`& .${A.thumb}`]:{width:16,height:16},[`& .${A.switchBase}`]:{padding:4,[`&.${A.checked}`]:{transform:`translateX(16px)`}}}}]}),N=n(O,{name:`MuiSwitch`,slot:`SwitchBase`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.switchBase,{[`& .${A.input}`]:t.input},n.color!==`default`&&t[`color${l(n.color)}`]]}})(s(({theme:e})=>({position:`absolute`,top:0,left:0,zIndex:1,color:e.vars?e.vars.palette.Switch.defaultColor:`${e.palette.mode===`light`?e.palette.common.white:e.palette.grey[300]}`,transition:e.transitions.create([`left`,`transform`],{duration:e.transitions.duration.shortest}),[`&.${A.checked}`]:{transform:`translateX(20px)`},[`&.${A.disabled}`]:{color:e.vars?e.vars.palette.Switch.defaultDisabledColor:`${e.palette.mode===`light`?e.palette.grey[100]:e.palette.grey[600]}`},[`&.${A.checked} + .${A.track}`]:{opacity:.5},[`&.${A.disabled} + .${A.track}`]:{opacity:e.vars?e.vars.opacity.switchTrackDisabled:`${e.palette.mode===`light`?.12:.2}`},[`& .${A.input}`]:{left:`-100%`,width:`300%`}})),s(({theme:e})=>({"&:hover":{backgroundColor:e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},variants:[...Object.entries(e.palette).filter(c([`light`])).map(([t])=>({props:{color:t},style:{[`&.${A.checked}`]:{color:(e.vars||e).palette[t].main,"&:hover":{backgroundColor:e.alpha((e.vars||eLine truncated
@@ -0,0 +1 @@
import{t as e}from"./useControlled-OB0I4oAK.js";import{B as t,D as n,H as r,J as i,M as a,P as o,Q as s,U as c,W as l,X as u,Y as d,Z as f,j as p,k as m,q as h,r as g,tt as _,z as v}from"./index-7LYai_r-.js";var y=_(s()),b=y.createContext(void 0);function x(){return y.useContext(b)}function S(e){return d(`PrivateSwitchBase`,e)}i(`PrivateSwitchBase`,[`root`,`checked`,`disabled`,`input`,`edgeStart`,`edgeEnd`]);var C=f(),w=e=>{let{classes:n,checked:r,disabled:i,edge:a}=e;return h({root:[`root`,r&&`checked`,i&&`disabled`,a&&`edge${t(a)}`],input:[`input`]},S,n)},T=c(a,{name:`MuiSwitchBase`})({padding:9,borderRadius:`50%`,variants:[{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:({edge:e,ownerState:t})=>e===`start`&&t.size!==`small`,style:{marginLeft:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}},{props:({edge:e,ownerState:t})=>e===`end`&&t.size!==`small`,style:{marginRight:-12}}]}),E=c(`input`,{name:`MuiSwitchBase`,shouldForwardProp:l})({cursor:`inherit`,position:`absolute`,opacity:0,width:`100%`,height:`100%`,top:0,left:0,margin:0,padding:0,zIndex:1}),D=y.forwardRef(function(t,n){let{autoFocus:r,checked:i,checkedIcon:a,defaultChecked:s,disabled:c,disableFocusRipple:l=!1,edge:u=!1,icon:d,id:f,inputProps:p,inputRef:m,name:h,onBlur:g,onChange:_,onFocus:v,readOnly:y,required:b=!1,tabIndex:S,type:D,value:O,slots:k={},slotProps:A={},...j}=t,[M,N]=e({controlled:i,default:!!s,name:`SwitchBase`,state:`checked`}),P=x(),F=e=>{v&&v(e),P&&P.onFocus&&P.onFocus(e)},I=e=>{g&&g(e),P&&P.onBlur&&P.onBlur(e)},L=e=>{if(e.nativeEvent.defaultPrevented)return;let t=e.target.checked;N(t),_&&_(e,t)},R=c;P&&R===void 0&&(R=P.disabled);let z=D===`checkbox`||D===`radio`,B={...t,checked:M,disabled:R,disableFocusRipple:l,edge:u},V=w(B),H={slots:k,slotProps:{input:p,...A}},[U,W]=o(`root`,{ref:n,elementType:T,className:V.root,shouldForwardComponentProp:!0,externalForwardedProps:{...H,component:`span`,...j},getSlotProps:e=>({...e,onFocus:t=>{e.onFocus?.(t),F(t)},onBlur:t=>{e.onBlur?.(t),I(t)}}),ownerState:B,additionalProps:{centerRipple:!0,focusRipple:!l,disabled:R,role:void 0,tabIndex:null}}),[G,K]=o(`input`,{ref:m,elementType:E,className:V.input,externalForwardedProps:H,getSlotProps:e=>({...e,onChange:t=>{e.onChange?.(t),L(t)}}),ownerState:B,additionalProps:{autoFocus:r,checked:i,defaultChecked:s,disabled:R,id:z?f:void 0,name:h,readOnly:y,required:b,tabIndex:S,type:D,...D===`checkbox`&&O===void 0?{}:{value:O}}});return(0,C.jsxs)(U,{...W,children:[(0,C.jsx)(G,{...K}),M?a:d]})});function O(e){return d(`MuiSwitch`,e)}var k=i(`MuiSwitch`,[`root`,`edgeStart`,`edgeEnd`,`switchBase`,`colorPrimary`,`colorSecondary`,`sizeSmall`,`sizeMedium`,`checked`,`disabled`,`input`,`thumb`,`track`]),A=e=>{let{classes:n,edge:r,size:i,color:a,checked:o,disabled:s}=e,c=h({root:[`root`,r&&`edge${t(r)}`,`size${t(i)}`],switchBase:[`switchBase`,`color${t(a)}`,o&&`checked`,s&&`disabled`],thumb:[`thumb`],track:[`track`],input:[`input`]},O,n);return{...n,...c}},j=c(`span`,{name:`MuiSwitch`,slot:`Root`,overridesResolver:(e,n)=>{let{ownerState:r}=e;return[n.root,r.edge&&n[`edge${t(r.edge)}`],n[`size${t(r.size)}`]]}})({display:`inline-flex`,width:58,height:38,overflow:`hidden`,padding:12,boxSizing:`border-box`,position:`relative`,flexShrink:0,zIndex:0,verticalAlign:`middle`,"@media print":{colorAdjust:`exact`},variants:[{props:{edge:`start`},style:{marginLeft:-8}},{props:{edge:`end`},style:{marginRight:-8}},{props:{size:`small`},style:{width:40,height:24,padding:7,[`& .${k.thumb}`]:{width:16,height:16},[`& .${k.switchBase}`]:{padding:4,[`&.${k.checked}`]:{transform:`translateX(16px)`}}}}]}),M=c(D,{name:`MuiSwitch`,slot:`SwitchBase`,overridesResolver:(e,n)=>{let{ownerState:r}=e;return[n.switchBase,{[`& .${k.input}`]:n.input},r.color!==`default`&&n[`color${t(r.color)}`]]}})(v(({theme:e})=>({position:`absolute`,top:0,left:0,zIndex:1,color:e.vars?e.vars.palette.Switch.defaultColor:`${e.palette.mode===`light`?e.palette.common.white:e.palette.grey[300]}`,transition:e.transitions.create([`left`,`transform`],{duration:e.transitions.duration.shortest}),[`&.${k.checked}`]:{transform:`translateX(20px)`},[`&.${k.disabled}`]:{color:e.vars?e.vars.palette.Switch.defaultDisabledColor:`${e.palette.mode===`light`?e.palette.grey[100]:e.palette.grey[600]}`},[`&.${k.checked} + .${k.track}`]:{opacity:.5},[`&.${k.disabled} + .${k.track}`]:{opacity:e.vars?e.vars.opacity.switchTrackDisabled:`${e.palette.mode===`light`?.12:.2}`},[`& .${k.input}`]:{left:`-100%`,width:`300%`}})),v(({theme:e})=>({"&:hover":{backgroundColor:e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},variants:[...Object.entries(e.palette).filter(p([`light`])).map(([t])=>({props:{color:t},style:{[`&.${k.checked}`]:{color:(e.vars||e).palette[t].main,"&:hover":{backgroundColor:e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},[`&.${k.dLine truncated
@@ -1 +0,0 @@
import{B as e,E as t,F as n,I as r,K as i,L as a,M as o,N as s,O as c,P as l,R as u,U as d,V as f,j as p,k as m,r as h,w as g,z as _}from"./index-BXbyImyv.js";var v=i(d());function y(e){let{controlled:t,default:n,name:r,state:i=`value`}=e,{current:a}=v.useRef(t!==void 0),[o,s]=v.useState(n);return[a?t:o,v.useCallback(e=>{a||s(e)},[])]}var b=y,x=v.createContext(void 0);function S(){return v.useContext(x)}function C(e){return _(`PrivateSwitchBase`,e)}u(`PrivateSwitchBase`,[`root`,`checked`,`disabled`,`input`,`edgeStart`,`edgeEnd`]);var w=f(),T=e=>{let{classes:t,checked:n,disabled:r,edge:i}=e;return a({root:[`root`,n&&`checked`,r&&`disabled`,i&&`edge${l(i)}`],input:[`input`]},C,t)},E=n(m,{name:`MuiSwitchBase`})({padding:9,borderRadius:`50%`,variants:[{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:({edge:e,ownerState:t})=>e===`start`&&t.size!==`small`,style:{marginLeft:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}},{props:({edge:e,ownerState:t})=>e===`end`&&t.size!==`small`,style:{marginRight:-12}}]}),D=n(`input`,{name:`MuiSwitchBase`,shouldForwardProp:r})({cursor:`inherit`,position:`absolute`,opacity:0,width:`100%`,height:`100%`,top:0,left:0,margin:0,padding:0,zIndex:1}),O=v.forwardRef(function(e,t){let{autoFocus:n,checked:r,checkedIcon:i,defaultChecked:a,disabled:o,disableFocusRipple:s=!1,edge:c=!1,icon:l,id:u,inputProps:d,inputRef:f,name:m,onBlur:h,onChange:g,onFocus:_,readOnly:v,required:y=!1,tabIndex:x,type:C,value:O,slots:k={},slotProps:A={},...j}=e,[M,N]=b({controlled:r,default:!!a,name:`SwitchBase`,state:`checked`}),P=S(),F=e=>{_&&_(e),P&&P.onFocus&&P.onFocus(e)},I=e=>{h&&h(e),P&&P.onBlur&&P.onBlur(e)},L=e=>{if(e.nativeEvent.defaultPrevented)return;let t=e.target.checked;N(t),g&&g(e,t)},R=o;P&&R===void 0&&(R=P.disabled);let z=C===`checkbox`||C===`radio`,B={...e,checked:M,disabled:R,disableFocusRipple:s,edge:c},V=T(B),H={slots:k,slotProps:{input:d,...A}},[U,W]=p(`root`,{ref:t,elementType:E,className:V.root,shouldForwardComponentProp:!0,externalForwardedProps:{...H,component:`span`,...j},getSlotProps:e=>({...e,onFocus:t=>{e.onFocus?.(t),F(t)},onBlur:t=>{e.onBlur?.(t),I(t)}}),ownerState:B,additionalProps:{centerRipple:!0,focusRipple:!s,disabled:R,role:void 0,tabIndex:null}}),[G,K]=p(`input`,{ref:f,elementType:D,className:V.input,externalForwardedProps:H,getSlotProps:e=>({...e,onChange:t=>{e.onChange?.(t),L(t)}}),ownerState:B,additionalProps:{autoFocus:n,checked:r,defaultChecked:a,disabled:R,id:z?u:void 0,name:m,readOnly:v,required:y,tabIndex:x,type:C,...C===`checkbox`&&O===void 0?{}:{value:O}}});return(0,w.jsxs)(U,{...W,children:[(0,w.jsx)(G,{...K}),M?i:l]})});function k(e){return _(`MuiSwitch`,e)}var A=u(`MuiSwitch`,[`root`,`edgeStart`,`edgeEnd`,`switchBase`,`colorPrimary`,`colorSecondary`,`sizeSmall`,`sizeMedium`,`checked`,`disabled`,`input`,`thumb`,`track`]),j=e=>{let{classes:t,edge:n,size:r,color:i,checked:o,disabled:s}=e,c=a({root:[`root`,n&&`edge${l(n)}`,`size${l(r)}`],switchBase:[`switchBase`,`color${l(i)}`,o&&`checked`,s&&`disabled`],thumb:[`thumb`],track:[`track`],input:[`input`]},k,t);return{...t,...c}},M=n(`span`,{name:`MuiSwitch`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.edge&&t[`edge${l(n.edge)}`],t[`size${l(n.size)}`]]}})({display:`inline-flex`,width:58,height:38,overflow:`hidden`,padding:12,boxSizing:`border-box`,position:`relative`,flexShrink:0,zIndex:0,verticalAlign:`middle`,"@media print":{colorAdjust:`exact`},variants:[{props:{edge:`start`},style:{marginLeft:-8}},{props:{edge:`end`},style:{marginRight:-8}},{props:{size:`small`},style:{width:40,height:24,padding:7,[`& .${A.thumb}`]:{width:16,height:16},[`& .${A.switchBase}`]:{padding:4,[`&.${A.checked}`]:{transform:`translateX(16px)`}}}}]}),N=n(O,{name:`MuiSwitch`,slot:`SwitchBase`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.switchBase,{[`& .${A.input}`]:t.input},n.color!==`default`&&t[`color${l(n.color)}`]]}})(s(({theme:e})=>({position:`absolute`,top:0,left:0,zIndex:1,color:e.vars?e.vars.palette.Switch.defaultColor:`${e.palette.mode===`light`?e.palette.common.white:e.palette.grey[300]}`,transition:e.transitions.create([`left`,`transform`],{duration:e.transitions.duration.shortest}),[`&.${A.checked}`]:{transform:`translateX(20px)`},[`&.${A.disabled}`]:{color:e.vars?e.vars.palette.Switch.defaultDisabledColor:`${e.palette.mode===`light`?e.palette.grey[100]:e.palette.grey[600]}`},[`&.${A.checked} + .${A.track}`]:{opacity:.5},[`&.${A.disabled} + .${A.track}`]:{opacity:e.vars?e.vars.opacity.switchTrackDisabled:`${e.palette.mode===`light`?.12:.2}`},[`& .${A.input}`]:{left:`-100%`,width:`300%`}})),s(({theme:e})=>({"&:hover":{backgroundColor:e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},variants:[...Object.entries(e.palette).filter(c([`light`])).map(([t])=>({props:{color:t},style:{[`&.${A.checked}`]:{color:(e.vars||e).palette[t].main,"&:hover":{backgroundColor:e.alpha((e.vars||eLine truncated
@@ -1 +0,0 @@
import{B as e,E as t,F as n,I as r,K as i,L as a,M as o,N as s,O as c,P as l,R as u,U as d,V as f,j as p,k as m,r as h,w as g,z as _}from"./index-Cq4vmvLV.js";var v=i(d());function y(e){let{controlled:t,default:n,name:r,state:i=`value`}=e,{current:a}=v.useRef(t!==void 0),[o,s]=v.useState(n);return[a?t:o,v.useCallback(e=>{a||s(e)},[])]}var b=y,x=v.createContext(void 0);function S(){return v.useContext(x)}function C(e){return _(`PrivateSwitchBase`,e)}u(`PrivateSwitchBase`,[`root`,`checked`,`disabled`,`input`,`edgeStart`,`edgeEnd`]);var w=f(),T=e=>{let{classes:t,checked:n,disabled:r,edge:i}=e;return a({root:[`root`,n&&`checked`,r&&`disabled`,i&&`edge${l(i)}`],input:[`input`]},C,t)},E=n(m,{name:`MuiSwitchBase`})({padding:9,borderRadius:`50%`,variants:[{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:({edge:e,ownerState:t})=>e===`start`&&t.size!==`small`,style:{marginLeft:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}},{props:({edge:e,ownerState:t})=>e===`end`&&t.size!==`small`,style:{marginRight:-12}}]}),D=n(`input`,{name:`MuiSwitchBase`,shouldForwardProp:r})({cursor:`inherit`,position:`absolute`,opacity:0,width:`100%`,height:`100%`,top:0,left:0,margin:0,padding:0,zIndex:1}),O=v.forwardRef(function(e,t){let{autoFocus:n,checked:r,checkedIcon:i,defaultChecked:a,disabled:o,disableFocusRipple:s=!1,edge:c=!1,icon:l,id:u,inputProps:d,inputRef:f,name:m,onBlur:h,onChange:g,onFocus:_,readOnly:v,required:y=!1,tabIndex:x,type:C,value:O,slots:k={},slotProps:A={},...j}=e,[M,N]=b({controlled:r,default:!!a,name:`SwitchBase`,state:`checked`}),P=S(),F=e=>{_&&_(e),P&&P.onFocus&&P.onFocus(e)},I=e=>{h&&h(e),P&&P.onBlur&&P.onBlur(e)},L=e=>{if(e.nativeEvent.defaultPrevented)return;let t=e.target.checked;N(t),g&&g(e,t)},R=o;P&&R===void 0&&(R=P.disabled);let z=C===`checkbox`||C===`radio`,B={...e,checked:M,disabled:R,disableFocusRipple:s,edge:c},V=T(B),H={slots:k,slotProps:{input:d,...A}},[U,W]=p(`root`,{ref:t,elementType:E,className:V.root,shouldForwardComponentProp:!0,externalForwardedProps:{...H,component:`span`,...j},getSlotProps:e=>({...e,onFocus:t=>{e.onFocus?.(t),F(t)},onBlur:t=>{e.onBlur?.(t),I(t)}}),ownerState:B,additionalProps:{centerRipple:!0,focusRipple:!s,disabled:R,role:void 0,tabIndex:null}}),[G,K]=p(`input`,{ref:f,elementType:D,className:V.input,externalForwardedProps:H,getSlotProps:e=>({...e,onChange:t=>{e.onChange?.(t),L(t)}}),ownerState:B,additionalProps:{autoFocus:n,checked:r,defaultChecked:a,disabled:R,id:z?u:void 0,name:m,readOnly:v,required:y,tabIndex:x,type:C,...C===`checkbox`&&O===void 0?{}:{value:O}}});return(0,w.jsxs)(U,{...W,children:[(0,w.jsx)(G,{...K}),M?i:l]})});function k(e){return _(`MuiSwitch`,e)}var A=u(`MuiSwitch`,[`root`,`edgeStart`,`edgeEnd`,`switchBase`,`colorPrimary`,`colorSecondary`,`sizeSmall`,`sizeMedium`,`checked`,`disabled`,`input`,`thumb`,`track`]),j=e=>{let{classes:t,edge:n,size:r,color:i,checked:o,disabled:s}=e,c=a({root:[`root`,n&&`edge${l(n)}`,`size${l(r)}`],switchBase:[`switchBase`,`color${l(i)}`,o&&`checked`,s&&`disabled`],thumb:[`thumb`],track:[`track`],input:[`input`]},k,t);return{...t,...c}},M=n(`span`,{name:`MuiSwitch`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.edge&&t[`edge${l(n.edge)}`],t[`size${l(n.size)}`]]}})({display:`inline-flex`,width:58,height:38,overflow:`hidden`,padding:12,boxSizing:`border-box`,position:`relative`,flexShrink:0,zIndex:0,verticalAlign:`middle`,"@media print":{colorAdjust:`exact`},variants:[{props:{edge:`start`},style:{marginLeft:-8}},{props:{edge:`end`},style:{marginRight:-8}},{props:{size:`small`},style:{width:40,height:24,padding:7,[`& .${A.thumb}`]:{width:16,height:16},[`& .${A.switchBase}`]:{padding:4,[`&.${A.checked}`]:{transform:`translateX(16px)`}}}}]}),N=n(O,{name:`MuiSwitch`,slot:`SwitchBase`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.switchBase,{[`& .${A.input}`]:t.input},n.color!==`default`&&t[`color${l(n.color)}`]]}})(s(({theme:e})=>({position:`absolute`,top:0,left:0,zIndex:1,color:e.vars?e.vars.palette.Switch.defaultColor:`${e.palette.mode===`light`?e.palette.common.white:e.palette.grey[300]}`,transition:e.transitions.create([`left`,`transform`],{duration:e.transitions.duration.shortest}),[`&.${A.checked}`]:{transform:`translateX(20px)`},[`&.${A.disabled}`]:{color:e.vars?e.vars.palette.Switch.defaultDisabledColor:`${e.palette.mode===`light`?e.palette.grey[100]:e.palette.grey[600]}`},[`&.${A.checked} + .${A.track}`]:{opacity:.5},[`&.${A.disabled} + .${A.track}`]:{opacity:e.vars?e.vars.opacity.switchTrackDisabled:`${e.palette.mode===`light`?.12:.2}`},[`& .${A.input}`]:{left:`-100%`,width:`300%`}})),s(({theme:e})=>({"&:hover":{backgroundColor:e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"@media (hover: none)":{backgroundColor:`transparent`}},variants:[...Object.entries(e.palette).filter(c([`light`])).map(([t])=>({props:{color:t},style:{[`&.${A.checked}`]:{color:(e.vars||e).palette[t].main,"&:hover":{backgroundColor:e.alpha((e.vars||eLine truncated
Binary file not shown.
Binary file not shown.
@@ -1 +0,0 @@
import{E as e,V as t,_ as n,b as r,c as i,d as a,h as o,l as s,m as c,n as l,p as u,u as d,x as f}from"./index-BXbyImyv.js";var p=t(),m={1:`h1`,2:`h2`,3:`h3`,4:`h4`,5:`h5`,6:`h6`};function h({element:t,className:h,nested:g,devInfoTags:_,uid:v,spaceBefore:y,spaceAfter:b}){let x=i(t),S=a(t),C=Number(s(t)??0),w=m[C]??`h3`,T=n(d(t)),E=T===`center`?`center`:T===`right`?`right`:`left`,D=T===`center`?`center`:T===`right`?`flex-end`:`flex-start`,O=u(t.content?.headerLink),k=o(t.content?.headerLink),A=c(t.content?.headerLink,`Mehr erfahren`);return C===100?null:(0,p.jsx)(l,{className:h,devInfoTags:_,nested:g,uid:v,spaceBefore:y,spaceAfter:b,children:(0,p.jsx)(`div`,{className:`header__inner`,children:(0,p.jsxs)(f,{spacing:.75,sx:{textAlign:E,alignItems:D},children:[x&&(0,p.jsx)(e,{variant:w,children:x}),S&&(0,p.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:S}),O&&(0,p.jsx)(r,{href:O,target:k??`_self`,variant:`outlined`,size:`sm`,children:A})]})})})}export{h as VitecHeader};
@@ -0,0 +1 @@
import{C as e,S as t,Z as n,_ as r,c as i,d as a,h as o,k as s,l as c,m as l,n as u,p as d,u as f,v as p,y as m}from"./index-7LYai_r-.js";var h=n();function g({element:n,className:g,nested:_,devInfoTags:v,uid:y,spaceBefore:b,spaceAfter:x}){let S=i(n),C=a(n),w=Number(c(n)??0),T=p(w),E=m(w),D=r(f(n)),O=D===`center`?`center`:D===`right`?`right`:`left`,k=D===`center`?`center`:D===`right`?`flex-end`:`flex-start`,A=d(n.content?.headerLink),j=o(n.content?.headerLink),M=l(n.content?.headerLink,`Mehr erfahren`);return w===100?null:(0,h.jsx)(u,{className:g,devInfoTags:v,nested:_,uid:y,spaceBefore:b,spaceAfter:x,children:(0,h.jsx)(`div`,{className:`header__inner`,children:(0,h.jsxs)(e,{spacing:.75,sx:{textAlign:O,alignItems:k},children:[S&&(0,h.jsx)(s,{variant:T,children:S}),C&&(0,h.jsx)(s,{variant:E,color:`text.secondary`,children:C}),A&&(0,h.jsx)(t,{href:A,target:j??`_self`,variant:`outlined`,size:`sm`,children:M})]})})})}export{g as VitecHeader};
@@ -1 +0,0 @@
import{E as e,V as t,_ as n,b as r,c as i,d as a,h as o,l as s,m as c,n as l,p as u,u as d,x as f}from"./index-Cq4vmvLV.js";var p=t(),m={1:`h1`,2:`h2`,3:`h3`,4:`h4`,5:`h5`,6:`h6`};function h({element:t,className:h,nested:g,devInfoTags:_,uid:v,spaceBefore:y,spaceAfter:b}){let x=i(t),S=a(t),C=Number(s(t)??0),w=m[C]??`h3`,T=n(d(t)),E=T===`center`?`center`:T===`right`?`right`:`left`,D=T===`center`?`center`:T===`right`?`flex-end`:`flex-start`,O=u(t.content?.headerLink),k=o(t.content?.headerLink),A=c(t.content?.headerLink,`Mehr erfahren`);return C===100?null:(0,p.jsx)(l,{className:h,devInfoTags:_,nested:g,uid:v,spaceBefore:y,spaceAfter:b,children:(0,p.jsx)(`div`,{className:`header__inner`,children:(0,p.jsxs)(f,{spacing:.75,sx:{textAlign:E,alignItems:D},children:[x&&(0,p.jsx)(e,{variant:w,children:x}),S&&(0,p.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:S}),O&&(0,p.jsx)(r,{href:O,target:k??`_self`,variant:`outlined`,size:`sm`,children:A})]})})})}export{h as VitecHeader};
@@ -1 +0,0 @@
import{E as e,V as t,_ as n,b as r,c as i,d as a,h as o,l as s,m as c,n as l,p as u,u as d,x as f}from"./index-jouuqNaB.js";var p=t(),m={1:`h1`,2:`h2`,3:`h3`,4:`h4`,5:`h5`,6:`h6`};function h({element:t,className:h,nested:g,devInfoTags:_,uid:v,spaceBefore:y,spaceAfter:b}){let x=i(t),S=a(t),C=Number(s(t)??0),w=m[C]??`h3`,T=n(d(t)),E=T===`center`?`center`:T===`right`?`right`:`left`,D=T===`center`?`center`:T===`right`?`flex-end`:`flex-start`,O=u(t.content?.headerLink),k=o(t.content?.headerLink),A=c(t.content?.headerLink,`Mehr erfahren`);return C===100?null:(0,p.jsx)(l,{className:h,devInfoTags:_,nested:g,uid:v,spaceBefore:y,spaceAfter:b,children:(0,p.jsx)(`div`,{className:`header__inner`,children:(0,p.jsxs)(f,{spacing:.75,sx:{textAlign:E,alignItems:D},children:[x&&(0,p.jsx)(e,{variant:w,children:x}),S&&(0,p.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:S}),O&&(0,p.jsx)(r,{href:O,target:k??`_self`,variant:`outlined`,size:`sm`,children:A})]})})})}export{h as VitecHeader};
@@ -1 +0,0 @@
import{E as e,V as t,_ as n,b as r,c as i,d as a,h as o,l as s,m as c,n as l,p as u,u as d,x as f}from"./index-BI31wk8i.js";var p=t(),m={1:`h1`,2:`h2`,3:`h3`,4:`h4`,5:`h5`,6:`h6`};function h({element:t,className:h,nested:g,devInfoTags:_,uid:v,spaceBefore:y,spaceAfter:b}){let x=i(t),S=a(t),C=Number(s(t)??0),w=m[C]??`h3`,T=n(d(t)),E=T===`center`?`center`:T===`right`?`right`:`left`,D=T===`center`?`center`:T===`right`?`flex-end`:`flex-start`,O=u(t.content?.headerLink),k=o(t.content?.headerLink),A=c(t.content?.headerLink,`Mehr erfahren`);return C===100?null:(0,p.jsx)(l,{className:h,devInfoTags:_,nested:g,uid:v,spaceBefore:y,spaceAfter:b,children:(0,p.jsx)(`div`,{className:`header__inner`,children:(0,p.jsxs)(f,{spacing:.75,sx:{textAlign:E,alignItems:D},children:[x&&(0,p.jsx)(e,{variant:w,children:x}),S&&(0,p.jsx)(e,{variant:`subtitle1`,color:`text.secondary`,children:S}),O&&(0,p.jsx)(r,{href:O,target:k??`_self`,variant:`outlined`,size:`sm`,children:A})]})})})}export{h as VitecHeader};
+148
View File
@@ -0,0 +1,148 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vitec_downloadcardcollection-7D90DISQ.js","assets/jszip.min-B_jfdBDa.js","assets/vitec_datasheets-Bu643D_W.js","assets/vitec_faq-Bii0IQAm.js","assets/useControlled-OB0I4oAK.js","assets/VitecDebugToggle-C76AUijU.js"])))=>i.map(i=>d[i]);
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),l=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function.")});(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function O(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var te=/\/+/g;function k(e,t){return typeof e==`object`&&e&&e.key!=null?O(``+e.key):t.toString(36)}function A(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function j(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,j(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+k(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(te,`$&/`)+`/`),j(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(te,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u<e.length;u++)a=e[u],s=l+k(a,u),c+=j(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+k(a,u++),c+=j(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return j(A(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function ne(e,t,n){if(e==null)return e;var r=[],i=0;return j(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function M(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(e._statLine truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+ge+e+_e}var ye=!1;function be(e,t){if(!e||ye)return``;ye=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,`props`,{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,`name`,{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{ye=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?ve(n):``}function xe(e,t){switch(e.tag){case 26:case 27:case 5:return ve(e.type);case 16:return ve(`Lazy`);case 13:return e.child!==t&&t!==null?ve(`Suspense Fallback`):ve(`Suspense`);case 19:return ve(`SuspenseList`);case 0:case 15:return be(e.type,!1);case 11:return be(e.type.render,!1);case 1:return be(e.type,!0);case 31:return ve(`Activity`);default:return``}}function Se(e){try{var t=``,n=null;do t+=xe(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var Ce=Object.prototype.hasOwnProperty,we=t.unstable_scheduleCallback,Te=t.unstable_cancelCallback,Ee=t.unstable_shouldYield,De=t.unstable_requestPaint,Oe=t.unstable_now,ke=t.unstable_getCurrentPriorityLevel,Ae=t.unstable_ImmediatePriority,je=t.unstable_UserBlockingPriority,Me=t.unstable_NormalPriority,Ne=t.unstable_LowPriority,Pe=t.unstable_IdlePriority,Fe=t.log,Ie=t.unstable_setDisableYieldValue,Le=null,Re=null;function ze(e){if(typeof Fe==`function`&&Ie(e),Re&&typeof Re.setStrictMode==`function`)try{Re.setStrictMode(Le,e)}catch{}}var Be=Math.clz32?Math.clz32:Ue,Ve=Math.log,He=Math.LN2;function Ue(e){return e>>>=0,e===0?32:31-(Ve(e)/He|0)|0}var We=256,Ge=262144,Ke=4194304;function qe(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Je(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=qe(n))):i=qe(o):i=qe(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=qe(n))):i=qe(o)):i=qe(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ye(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Xe(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function Ze(){var e=Ke;return Ke<<=1,!(Ke&62914560)&&(Ke=4194304),e}function Qe(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function $e(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function et(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-Be(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&tt(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function tt(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-Be(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function nt(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-Be(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function rt(e,t){var n=t&-t;return n=n&42?1:it(n),(n&(e.suspendedLanes|t))===0?n:0}function it(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function at(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function ot(){var e=P.p;return e===0?(e=window.event,e===void 0?32:hp(e.type)):e}function st(e,t){var n=P.p;try{return P.p=e,t()}finally{P.p=n}}var ct=Math.random().toString(36).slice(2),lt=`__reactFiber$`+ct,ut=`__reactProps$`+ct,dt=`__reactContainer$`+ct,ft=`__reactEvents$`+ct,pt=`__reactListeners$`+ct,mt=`__reactHandles$`+ct,ht=`__reactResources$`+ct,gt=`__reactMarker$`+ct;function _t(e){delete e[lt],delete e[ut],delete e[ft],delete e[pt],delete e[mt]}function vt(e){var t=e[lt];if(t)return t;for(var n=e.parentNode;n;){if(t=n[dt]||n[lt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=ff(e);e!==null;){if(n=e[lt])return n;e=ff(e)}return t}e=n,n=e.parentNode}return null}function yt(e){if(e=e[lt]||e[dt]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function bt(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function xt(e){var t=e[ht];return t||=e[ht]={hoistableStyles:new Map,hoistableScripts:new Map},t}function St(e){e[gt]=!0}var Ct=new Set,wt={};function Tt(e,t){Et(e,t),Et(e+`Capture`,t)}function Et(e,t){for(wt[e]=t,e=0;e<t.length;e++)Ct.add(t[e])}var Dt=RegExp(`^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001Line truncated
`).replace(jd,``)}function Nd(e,t){return t=Md(t),Md(e)===t}function Pd(e,t,n,r,a,o){switch(n){case`children`:typeof r==`string`?t===`body`||t===`textarea`&&r===``||Jt(e,r):(typeof r==`number`||typeof r==`bigint`)&&t!==`body`&&Jt(e,``+r);break;case`className`:Mt(e,`class`,r);break;case`tabIndex`:Mt(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:Mt(e,n,r);break;case`style`:Zt(e,r,o);break;case`data`:if(t!==`object`){Mt(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=tn(``+r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}else typeof o==`function`&&(n===`formAction`?(t!==`input`&&Pd(e,t,`name`,a.name,a,null),Pd(e,t,`formEncType`,a.formEncType,a,null),Pd(e,t,`formMethod`,a.formMethod,a,null),Pd(e,t,`formTarget`,a.formTarget,a,null)):(Pd(e,t,`encType`,a.encType,a,null),Pd(e,t,`method`,a.method,a,null),Pd(e,t,`target`,a.target,a,null)));if(r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=tn(``+r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=nn);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=tn(``+r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``+r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Z(`beforetoggle`,e),Z(`toggle`,e),jt(e,`popover`,r);break;case`xlinkActuate`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:Nt(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:Nt(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:jt(e,`is`,r);break;case`innerText`:case`textContent`:break;default:(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)&&(n=$t.get(n)||n,jt(e,n,r))}}function Fd(e,t,n,r,a,o){switch(n){case`style`:Zt(e,r,o);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`children`:typeof r==`string`?Jt(e,r):(typeof r==`number`||typeof r==`bigint`)&&Jt(e,``+r);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`onClick`:r!=null&&(e.onclick=nn);break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`innerHTML` Line truncated
Please change the parent <Route path="${e}"> to <Route path="${e===`/`?`*`:`${e}/*`}">.`)}let u=cn(),d;if(t){let e=typeof t==`string`?tt(t):t;Ye(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):it(e,{pathname:p});Xe(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),Xe(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);let h=xn(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:Nt([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:Nt([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?S.createElement(Zt.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},h):h}function hn(){let e=An(),t=zt(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=S.createElement(S.Fragment,null,S.createElement(`p`,null,`💿 Hey developer 👋`),S.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,S.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,S.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),S.createElement(S.Fragment,null,S.createElement(`h2`,null,`Unexpected Application Error!`),S.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?S.createElement(`pre`,{style:i},n):null,o)}var gn=S.createElement(hn,null),_n=class extends S.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=an(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:S.createElement(Qt.Provider,{value:this.props.routeContext},S.createElement($t.Provider,{value:e,children:this.props.component}));return this.context?S.createElement(yn,{error:e},t):t}};_n.contextType=Gt;var vn=new WeakMap;function yn({children:e,error:t}){let{basename:n}=S.useContext(Xt);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=rn(t.digest);if(e){let r=vn.get(t);if(r)throw r;let i=Ht(e.location,n);if(Vt&&!vn.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw vn.set(t,n),n}return S.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function bn({routeContext:e,match:t,children:n}){let r=S.useContext(Ut);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),S.createElement(Qt.Provider,{value:e},n)}function xn(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);Ye(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e<i.length;e++){let t=i[e];if((t.route.HydrateFallback||t.route.hydrateFallbackElement)&&(s=e),t.route.id){lLine truncated
`)),(yc===void 0?vc:bc)(l,a,o,s,c)}}xc();function Sc(e,t=-(2**53-1),n=2**53-1){return Math.max(t,Math.min(e,n))}var Cc=Sc;function wc(e,t=0,n=1){return Cc(e,t,n)}function Tc(e){e=e.slice(1);let t=RegExp(`.{1,${e.length>=6?2:1}}`,`g`),n=e.match(t);return n&&n[0].length===1&&(n=n.map(e=>e+e)),n?`rgb${n.length===4?`a`:``}(${n.map((e,t)=>t<3?parseInt(e,16):Math.round(parseInt(e,16)/255*1e3)/1e3).join(`, `)})`:``}function Ec(e){if(e.type)return e;if(e.charAt(0)===`#`)return Ec(Tc(e));let t=e.indexOf(`(`),n=e.substring(0,t);if(![`rgb`,`rgba`,`hsl`,`hsla`,`color`].includes(n))throw Error(Ur(9,e));let r=e.substring(t+1,e.length-1),i;if(n===`color`){if(r=r.split(` `),i=r.shift(),r.length===4&&r[3].charAt(0)===`/`&&(r[3]=r[3].slice(1)),![`srgb`,`display-p3`,`a98-rgb`,`prophoto-rgb`,`rec-2020`].includes(i))throw Error(Ur(10,i))}else r=r.split(`,`);return r=r.map(e=>parseFloat(e)),{type:n,values:r,colorSpace:i}}const Dc=e=>{let t=Ec(e);return t.values.slice(0,3).map((e,n)=>t.type.includes(`hsl`)&&n!==0?`${e}%`:e).join(` `)},Oc=(e,t)=>{try{return Dc(e)}catch{return e}};function kc(e){let{type:t,colorSpace:n}=e,{values:r}=e;return t.includes(`rgb`)?r=r.map((e,t)=>t<3?parseInt(e,10):e):t.includes(`hsl`)&&(r[1]=`${r[1]}%`,r[2]=`${r[2]}%`),r=t.includes(`color`)?`${n} ${r.join(` `)}`:`${r.join(`, `)}`,`${t}(${r})`}function Ac(e){e=Ec(e);let{values:t}=e,n=t[0],r=t[1]/100,i=t[2]/100,a=r*Math.min(i,1-i),o=(e,t=(e+n/30)%12)=>i-a*Math.max(Math.min(t-3,9-t,1),-1),s=`rgb`,c=[Math.round(o(0)*255),Math.round(o(8)*255),Math.round(o(4)*255)];return e.type===`hsla`&&(s+=`a`,c.push(t[3])),kc({type:s,values:c})}function jc(e){e=Ec(e);let t=e.type===`hsl`||e.type===`hsla`?Ec(Ac(e)).values:e.values;return t=t.map(t=>(e.type!==`color`&&(t/=255),t<=.03928?t/12.92:((t+.055)/1.055)**2.4)),Number((.2126*t[0]+.7152*t[1]+.0722*t[2]).toFixed(3))}function Mc(e,t){let n=jc(e),r=jc(t);return(Math.max(n,r)+.05)/(Math.min(n,r)+.05)}function Nc(e,t){return e=Ec(e),t=wc(t),(e.type===`rgb`||e.type===`hsl`)&&(e.type+=`a`),e.type===`color`?e.values[3]=`/${t}`:e.values[3]=t,kc(e)}function Pc(e,t,n){try{return Nc(e,t)}catch{return e}}function Fc(e,t){if(e=Ec(e),t=wc(t),e.type.includes(`hsl`))e.values[2]*=1-t;else if(e.type.includes(`rgb`)||e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]*=1-t;return kc(e)}function H(e,t,n){try{return Fc(e,t)}catch{return e}}function Ic(e,t){if(e=Ec(e),t=wc(t),e.type.includes(`hsl`))e.values[2]+=(100-e.values[2])*t;else if(e.type.includes(`rgb`))for(let n=0;n<3;n+=1)e.values[n]+=(255-e.values[n])*t;else if(e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]+=(1-e.values[n])*t;return kc(e)}function U(e,t,n){try{return Ic(e,t)}catch{return e}}function Lc(e,t=.15){return jc(e)>.5?Fc(e,t):Ic(e,t)}function Rc(e,t,n){try{return Lc(e,t)}catch{return e}}var zc=S.createContext(null);function Bc(){return S.useContext(zc)}var Vc=typeof Symbol==`function`&&Symbol.for?Symbol.for(`mui.nested`):`__THEME_NESTED__`;function Hc(e,t){return typeof t==`function`?t(e):{...e,...t}}function Uc(e){let{children:t,theme:n}=e,r=Bc(),i=S.useMemo(()=>{let e=r===null?{...n}:Hc(r,n);return e!=null&&(e[Vc]=r!==null),e},[n,r]);return(0,z.jsx)(zc.Provider,{value:i,children:t})}var Wc=Uc,Gc=S.createContext();function Kc({value:e,...t}){return(0,z.jsx)(Gc.Provider,{value:e??!0,...t})}const qc=()=>S.useContext(Gc)??!1;var Jc=Kc,Yc=S.createContext(void 0);function Xc({value:e,children:t}){return(0,z.jsx)(Yc.Provider,{value:e,children:t})}function Zc(e){let{theme:t,name:n,props:r}=e;if(!t||!t.components||!t.components[n])return r;let i=t.components[n];return i.defaultProps?mc(i.defaultProps,r,t.components.mergeClassNameAndStyle):!i.styleOverrides&&!i.variants?mc(i,r,t.components.mergeClassNameAndStyle):r}function Qc({props:e,name:t}){return Zc({props:e,name:t,theme:{components:S.useContext(Yc)}})}var $c=Xc,el=0;function tl(e){let[t,n]=S.useState(e),r=e||t;return S.useEffect(()=>{t??(el+=1,n(`mui-${el}`))},[t]),r}var nl={...S}.useId;function rl(e){if(nl!==void 0){let t=nl();return e??t}return tl(e)}function il(e){let t=Rs(),n=rl()||``,{modularCssLayers:r}=e,i=`mui.global, mui.components, mui.theme, mui.custom, mui.sx`;return i=!r||t!==null?``:typeof r==`string`?r.replace(/mui(?!\.)/g,i):`@layer ${i};`,_c(()=>{let e=document.querySelector(`head`);if(!e)return;let t=e.firstChild;if(i){if(t&&t.hasAttribute?.(`data-mui-layer-order`)&&t.getAttribute(`data-mui-layer-order`)===n)return;let r=document.createElement(`style`);r.setAttribute(`data-mui-layer-order`,n),r.textContent=i,e.prepend(r)}else e.querySelector(`style[data-mui-layer-order="${n}"]`)?.remove()},[i,n]),i?(0,z.jsx)(Ws,{styles:i}):null}var al={};function ol(e,t,n,r=!1){return S.useMemo(()=>{let i=e&&t[e]||t;if(typeof n==`function`){let a=n(i),o=e?{...t,[e]:a}:a;return r?()=>o:o}return e?{...t,[e]:n}:{...t,...n}},[e,t,n,r])}function sl(e){let{children:t,theme:n,themeId:r}=e,i=Rs(al),a=Bc()||al,o=ol(r,i,n),s=ol(r,a,n,!0),c=(r?o[r]:o).direction===`rtl`,l=il(o);return(0,z.jsx)(Wc,{theme:s,children:( Line truncated
${s}.classList.add('${e}'.replace('%s', colorScheme));`}let d=u.match(/\[([^[\]]+)\]/);if(d){let[e,t]=d[1].split(`=`);t||(l+=`${s}.removeAttribute('${e}'.replace('%s', light));
${s}.removeAttribute('${e}'.replace('%s', dark));`),l+=`
${s}.setAttribute('${e}'.replace('%s', colorScheme), ${t?`${t}.replace('%s', colorScheme)`:`""`});`}else l+=`${s}.setAttribute('${u}', colorScheme);`;return(0,z.jsx)(`script`,{suppressHydrationWarning:!0,nonce:typeof window>`u`?c:``,dangerouslySetInnerHTML:{__html:`(function() {
try {
let colorScheme = '';
const mode = localStorage.getItem('${i}') || '${t}';
const dark = localStorage.getItem('${a}-dark') || '${r}';
const light = localStorage.getItem('${a}-light') || '${n}';
if (mode === 'system') {
// handle system mode
const mql = window.matchMedia('(prefers-color-scheme: dark)');
if (mql.matches) {
colorScheme = dark
} else {
colorScheme = light
}
}
if (mode === 'light') {
colorScheme = light;
}
if (mode === 'dark') {
colorScheme = dark;
}
if (colorScheme) {
${l}
}
} catch(e){}})();`}},`mui-color-scheme-init`)}function ml(){}var hl=({key:e,storageWindow:t})=>(!t&&typeof window<`u`&&(t=window),{get(n){if(typeof window>`u`)return;if(!t)return n;let r;try{r=t.localStorage.getItem(e)}catch{}return r||n},set:n=>{if(t)try{t.localStorage.setItem(e,n)}catch{}},subscribe:n=>{if(!t)return ml;let r=t=>{let r=t.newValue;t.key===e&&n(r)};return t.addEventListener(`storage`,r),()=>{t.removeEventListener(`storage`,r)}}});function gl(){}function _l(e){if(typeof window<`u`&&typeof window.matchMedia==`function`&&e===`system`)return window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}function vl(e,t){if(e.mode===`light`||e.mode===`system`&&e.systemMode===`light`)return t(`light`);if(e.mode===`dark`||e.mode===`system`&&e.systemMode===`dark`)return t(`dark`)}function yl(e){return vl(e,t=>{if(t===`light`)return e.lightColorScheme;if(t===`dark`)return e.darkColorScheme})}function bl(e){let{defaultMode:t=`light`,defaultLightColorScheme:n,defaultDarkColorScheme:r,supportedColorSchemes:i=[],modeStorageKey:a=dl,colorSchemeStorageKey:o=fl,storageWindow:s=typeof window>`u`?void 0:window,storageManager:c=hl,noSsr:l=!1}=e,u=i.join(`,`),d=i.length>1,f=S.useMemo(()=>c?.({key:a,storageWindow:s}),[c,a,s]),p=S.useMemo(()=>c?.({key:`${o}-light`,storageWindow:s}),[c,o,s]),m=S.useMemo(()=>c?.({key:`${o}-dark`,storageWindow:s}),[c,o,s]),[h,g]=S.useState(()=>{let e=f?.get(t)||t,i=p?.get(n)||n,a=m?.get(r)||r;return{mode:e,systemMode:_l(e),lightColorScheme:i,darkColorScheme:a}}),[_,v]=S.useState(l||!d);S.useEffect(()=>{v(!0)},[]);let y=yl(h),b=S.useCallback(e=>{g(n=>{if(e===n.mode)return n;let r=e??t;return f?.set(r),{...n,mode:r,systemMode:_l(r)}})},[f,t]),x=S.useCallback(e=>{e?typeof e==`string`?e&&!u.includes(e)?console.error(`\`${e}\` does not exist in \`theme.colorSchemes\`.`):g(t=>{let n={...t};return vl(t,t=>{t===`light`&&(p?.set(e),n.lightColorScheme=e),t===`dark`&&(m?.set(e),n.darkColorScheme=e)}),n}):g(t=>{let i={...t},a=e.light===null?n:e.light,o=e.dark===null?r:e.dark;return a&&(u.includes(a)?(i.lightColorScheme=a,p?.set(a)):console.error(`\`${a}\` does not exist in \`theme.colorSchemes\`.`)),o&&(u.includes(o)?(i.darkColorScheme=o,m?.set(o)):console.error(`\`${o}\` does not exist in \`theme.colorSchemes\`.`)),i}):g(e=>(p?.set(n),m?.set(r),{...e,lightColorScheme:n,darkColorScheme:r}))},[u,p,m,n,r]),C=S.useCallback(e=>{h.mode===`system`&&g(t=>{let n=e?.matches?`dark`:`light`;return t.systemMode===n?t:{...t,systemMode:n}})},[h.mode]),w=S.useRef(C);return w.current=C,S.useEffect(()=>{if(typeof window.matchMedia!=`function`||!d)return;let e=(...e)=>w.current(...e),t=window.matchMedia(`(prefers-color-scheme: dark)`);return t.addListener(e),e(t),()=>{t.removeListener(e)}},[d]),S.useEffect(()=>{if(d){let e=f?.subscribe(e=>{(!e||[`light`,`dark`,`system`].includes(e))&&b(e||t)})||gl,n=p?.subscribe(e=>{(!e||u.match(e))&&x({light:e})})||gl,r=m?.subscribe(e=>{(!e||u.match(e))&&x({dark:e})})||gl;return()=>{e(),n(),r()}}},[x,b,u,t,s,d,f,p,m]),{...h,mode:_?h.mode:void 0,systemMode:_?h.systemMode:void 0,colorScheme:_?y:void 0,setMode:b,setColorScheme:x}}function xl(e){let{themeId:t,theme:n={},modeStorageKey:r=dl,colorSchemeStorageKey:i=fl,disableTransitionOnChange:a=!1,defaultColorScheme:o,resolveTheme:s}=e,c={allColorSchemes:[],colorScheme:void 0,darkColorScheme:void 0,lightColorScheme:void 0,mode:void 0,setColorScheme:()=>{},setMode:()=>{},systemMode:void 0},l=S.createContext(void 0),u=()=>S.useContext(l)||c,d={},f={};function p(e){let{children:c,theme:u,modeStorageKey:p=r,colorSchemeStorageKey:m=i,disableTransitionOnChange:h=a,storageManager:g,storageWindow:_=typeof window>`u`?void 0:window,documentNode:v=typeof document>`u`?void 0:document,colorSchemeNode:y=typeof document>`u`?void 0:document.documentElement,disableNestedContext:b=!1,disableStyleSheetGeneration:x=!1,defaultMode:C=`system`,forceThemeRerender:w=!1,noSsr:T}=e,E=S.useRef(!1),ee=Bc(),D=S.useContext(l),O=!!D&&!b,te=S.useMemo(()=>u||(typeof n==`function`?n():n),[u]),k=te[t],A=k||te,{colorSchemes:j=d,components:ne=f,cssVarPrefix:M}=A,N=Object.keys(j).filter(e=>!!j[e]).join(`,`),P=S.useMemo(()=>N.split(`,`),[N]),re=typeof o==`string`?o:o.light,ie=typeof o==`string`?o:o.dark,{mode:ae,setMode:oe,systemMode:se,lightColorScheme:F,darkColorScheme:ce,colorScheme:le,setColorScheme:ue}=bl({supportedColorSchemes:P,defaultLightColorScheme:re,defaultDarkColorScheme:ie,modeStorageKey:p,colorSchemeStorageKey:m,defaultMode:j[re]&&j[ie]?C:j[A.defaultColorScheme]?.palette?.mode||A.palette?.mode,storageManager:g,storageWindow:_,noSsr:T}),de=ae,fe=le;O&&(de=D.mode,fe=D.colorScheme);let pe=fe||A.defaultColorScheme;A.vars&&!w&&(pe=A.defaultColorScheme);let me=S.useMemo(()=>{let e=A.generateThemeVars?.()||A.vars,t={...A,components:ne,colorSchemes:j,cssVarPrefix:M,vars:e};if(typeof t.generateSpacing==`function`&&(t.spacing=t.generateSpacing()),pe){let e=j[pe];e&&typeof e==`object`&&Object.keys(e).forEach(n=>{e[n]&&typeof e[n]==`object`?t[n]={...t[n],...Line truncated
const theme = ${JSON.stringify(t,null,2)};
theme.breakpoints = createBreakpoints(theme.breakpoints || {});
theme.transitions = createTransitions(theme.transitions || {});
export default theme;`}function J(e){return typeof e==`number`?`${(e*100).toFixed(0)}%`:`calc((${e}) * 100%)`}var Ju=e=>{if(!Number.isNaN(+e))return+e;let t=e.match(/\d*\.?\d+/g);if(!t)return 0;let n=0;for(let e=0;e<t.length;e+=1)n+=+t[e];return n};function Yu(e){Object.assign(e,{alpha(t,n){let r=this||e;return r.colorSpace?`oklch(from ${t} l c h / ${typeof n==`string`?`calc(${n})`:n})`:r.vars?`rgba(${t.replace(/var\(--([^,\s)]+)(?:,[^)]+)?\)+/g,`var(--$1Channel)`)} / ${typeof n==`string`?`calc(${n})`:n})`:Nc(t,Ju(n))},lighten(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #fff ${J(n)})`:Ic(t,n)},darken(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #000 ${J(n)})`:Fc(t,n)}})}function Xu(e={},...t){let{breakpoints:n,mixins:r={},spacing:i,palette:a={},transitions:o={},typography:s={},shape:c,colorSpace:l,...u}=e;if(e.vars&&e.generateThemeVars===void 0)throw Error(Ur(20));let d=Ou({...a,colorSpace:l}),f=Fs(e),p=fo(f,{mixins:Au(f.breakpoints,r),palette:d,shadows:zu.slice(),typography:Pu(d,s),transitions:Wu(o),zIndex:{...Gu}});return p=fo(p,u),p=t.reduce((e,t)=>fo(e,t),p),p.unstable_sxConfig={...Ds,...u?.unstable_sxConfig},p.unstable_sx=function(e){return Ms({sx:e,theme:this})},p.toRuntimeSource=qu,Yu(p),p}var Zu=Xu;function Qu(e){let t;return t=e<1?5.11916*e**2:4.5*Math.log(e+1)+2,Math.round(t*10)/1e3}var $u=[...Array(25)].map((e,t)=>{if(t===0)return`none`;let n=Qu(t);return`linear-gradient(rgba(255 255 255 / ${n}), rgba(255 255 255 / ${n}))`});function ed(e){return{inputPlaceholder:e===`dark`?.5:.42,inputUnderline:e===`dark`?.7:.42,switchTrackDisabled:e===`dark`?.2:.12,switchTrack:e===`dark`?.3:.38}}function td(e){return e===`dark`?$u:[]}function nd(e){let{palette:t={mode:`light`},opacity:n,overlays:r,colorSpace:i,...a}=e,o=Ou({...t,colorSpace:i});return{palette:o,opacity:{...ed(o.mode),...n},overlays:r||td(o.mode),...a}}function rd(e){return!!e[0].match(/(cssVarPrefix|colorSchemeSelector|modularCssLayers|rootSelector|typography|mixins|breakpoints|direction|transitions)/)||!!e[0].match(/sxConfig$/)||e[0]===`palette`&&!!e[1]?.match(/(mode|contrastThreshold|tonalOffset)/)}var id=e=>[...[...Array(25)].map((t,n)=>`--${e?`${e}-`:``}overlays-${n}`),`--${e?`${e}-`:``}palette-AppBar-darkBg`,`--${e?`${e}-`:``}palette-AppBar-darkColor`],ad=e=>(t,n)=>{let r=e.rootSelector||`:root`,i=e.colorSchemeSelector,a=i;if(i===`class`&&(a=`.%s`),i===`data`&&(a=`[data-%s]`),i?.startsWith(`data-`)&&!i.includes(`%s`)&&(a=`[${i}="%s"]`),e.defaultColorScheme===t){if(t===`dark`){let i={};return id(e.cssVarPrefix).forEach(e=>{i[e]=n[e],delete n[e]}),a===`media`?{[r]:n,"@media (prefers-color-scheme: dark)":{[r]:i}}:a?{[a.replace(`%s`,t)]:i,[`${r}, ${a.replace(`%s`,t)}`]:n}:{[r]:{...n,...i}}}if(a&&a!==`media`)return`${r}, ${a.replace(`%s`,String(t))}`}else if(t){if(a===`media`)return{[`@media (prefers-color-scheme: ${String(t)})`]:{[r]:n}};if(a)return a.replace(`%s`,String(t))}return r};function od(e,t){t.forEach(t=>{e[t]||(e[t]={})})}function Y(e,t,n){!e[t]&&n&&(e[t]=n)}function sd(e){return typeof e!=`string`||!e.startsWith(`hsl`)?e:Ac(e)}function cd(e,t){`${t}Channel`in e||(e[`${t}Channel`]=Oc(sd(e[t]),`MUI: Can't create \`palette.${t}Channel\` because \`palette.${t}\` is not one of these formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().
To suppress this warning, you need to explicitly provide the \`palette.${t}Channel\` as a string (in rgb format, for example "12 12 12") or undefined if you want to remove the channel token.`))}function ld(e){return typeof e==`number`?`${e}px`:typeof e==`string`||typeof e==`function`||Array.isArray(e)?e:`8px`}var ud=e=>{try{return e()}catch{}};const dd=(e=`mui`)=>Sl(e);function fd(e,t,n,r,i){if(!n)return;n=n===!0?{}:n;let a=i===`dark`?`dark`:`light`;if(!r){t[i]=nd({...n,palette:{mode:a,...n?.palette},colorSpace:e});return}let{palette:o,...s}=Zu({...r,palette:{mode:a,...n?.palette},colorSpace:e});return t[i]={...n,palette:o,opacity:{...ed(a),...n?.opacity},overlays:n?.overlays||td(a)},s}function pd(e={},...t){let{colorSchemes:n={light:!0},defaultColorScheme:r,disableCssColorScheme:i=!1,cssVarPrefix:a=`mui`,nativeColor:o=!1,shouldSkipGeneratingVar:s=rd,colorSchemeSelector:c=n.light&&n.dark?`media`:void 0,rootSelector:l=`:root`,...u}=e,d=Object.keys(n)[0],f=r||(n.light&&d!==`light`?`light`:d),p=dd(a),{[f]:m,light:h,dark:g,..._}=n,v={..._},y=m;if((f===`dark`&&!(`dark`in n)||f===`light`&&!(`light`in n))&&(y=!0),!y)throw Error(Ur(21,f));let b;o&&(b=`oklch`);let x=fd(b,v,y,u,f);h&&!v.light&&fd(b,v,h,void 0,`light`),g&&!v.dark&&fd(b,v,g,void 0,`dark`);let S={defaultColorScheme:f,...x,cssVarPrefix:a,colorSchemeSelector:c,rootSelector:l,getCssVar:p,colorSchemes:v,font:{...ku(x.typography),...x.font},spacing:ld(u.spacing)};Object.keys(S.colorSchemes).forEach(e=>{let t=S.colorSchemes[e].palette,n=e=>{let n=e.split(`-`),r=n[1],i=n[2];return p(e,t[r][i])};t.mode===`light`&&(Y(t.common,`background`,`#fff`),Y(t.common,`onBackground`,`#000`)),t.mode===`dark`&&(Y(t.common,`background`,`#000`),Y(t.common,`onBackground`,`#fff`));function r(e,t,n){if(b){let r;return e===Pc&&(r=`transparent ${((1-n)*100).toFixed(0)}%`),e===H&&(r=`#000 ${(n*100).toFixed(0)}%`),e===U&&(r=`#fff ${(n*100).toFixed(0)}%`),`color-mix(in ${b}, ${t}, ${r})`}return e(t,n)}if(od(t,[`Alert`,`AppBar`,`Avatar`,`Button`,`Chip`,`FilledInput`,`LinearProgress`,`Skeleton`,`Slider`,`SnackbarContent`,`SpeedDialAction`,`StepConnector`,`StepContent`,`Switch`,`TableCell`,`Tooltip`]),t.mode===`light`){Y(t.Alert,`errorColor`,r(H,t.error.light,.6)),Y(t.Alert,`infoColor`,r(H,t.info.light,.6)),Y(t.Alert,`successColor`,r(H,t.success.light,.6)),Y(t.Alert,`warningColor`,r(H,t.warning.light,.6)),Y(t.Alert,`errorFilledBg`,n(`palette-error-main`)),Y(t.Alert,`infoFilledBg`,n(`palette-info-main`)),Y(t.Alert,`successFilledBg`,n(`palette-success-main`)),Y(t.Alert,`warningFilledBg`,n(`palette-warning-main`)),Y(t.Alert,`errorFilledColor`,ud(()=>t.getContrastText(t.error.main))),Y(t.Alert,`infoFilledColor`,ud(()=>t.getContrastText(t.info.main))),Y(t.Alert,`successFilledColor`,ud(()=>t.getContrastText(t.success.main))),Y(t.Alert,`warningFilledColor`,ud(()=>t.getContrastText(t.warning.main))),Y(t.Alert,`errorStandardBg`,r(U,t.error.light,.9)),Y(t.Alert,`infoStandardBg`,r(U,t.info.light,.9)),Y(t.Alert,`successStandardBg`,r(U,t.success.light,.9)),Y(t.Alert,`warningStandardBg`,r(U,t.warning.light,.9)),Y(t.Alert,`errorIconColor`,n(`palette-error-main`)),Y(t.Alert,`infoIconColor`,n(`palette-info-main`)),Y(t.Alert,`successIconColor`,n(`palette-success-main`)),Y(t.Alert,`warningIconColor`,n(`palette-warning-main`)),Y(t.AppBar,`defaultBg`,n(`palette-grey-100`)),Y(t.Avatar,`defaultBg`,n(`palette-grey-400`)),Y(t.Button,`inheritContainedBg`,n(`palette-grey-300`)),Y(t.Button,`inheritContainedHoverBg`,n(`palette-grey-A100`)),Y(t.Chip,`defaultBorder`,n(`palette-grey-400`)),Y(t.Chip,`defaultAvatarColor`,n(`palette-grey-700`)),Y(t.Chip,`defaultIconColor`,n(`palette-grey-700`)),Y(t.FilledInput,`bg`,`rgba(0, 0, 0, 0.06)`),Y(t.FilledInput,`hoverBg`,`rgba(0, 0, 0, 0.09)`),Y(t.FilledInput,`disabledBg`,`rgba(0, 0, 0, 0.12)`),Y(t.LinearProgress,`primaryBg`,r(U,t.primary.main,.62)),Y(t.LinearProgress,`secondaryBg`,r(U,t.secondary.main,.62)),Y(t.LinearProgress,`errorBg`,r(U,t.error.main,.62)),Y(t.LinearProgress,`infoBg`,r(U,t.info.main,.62)),Y(t.LinearProgress,`successBg`,r(U,t.success.main,.62)),Y(t.LinearProgress,`warningBg`,r(U,t.warning.main,.62)),Y(t.Skeleton,`bg`,b?r(Pc,t.text.primary,.11):`rgba(${n(`palette-text-primaryChannel`)} / 0.11)`),Y(t.Slider,`primaryTrack`,r(U,t.primary.main,.62)),Y(t.Slider,`secondaryTrack`,r(U,t.secondary.main,.62)),Y(t.Slider,`errorTrack`,r(U,t.error.main,.62)),Y(t.Slider,`infoTrack`,r(U,t.info.main,.62)),Y(t.Slider,`successTrack`,r(U,t.success.main,.62)),Y(t.Slider,`warningTrack`,r(U,t.warning.main,.62));let e=b?r(H,t.background.default,.6825):Rc(t.background.default,.8);Y(t.SnackbarContent,`bg`,e),Y(t.SnackbarContent,`color`,ud(()=>b?vu.text.primary:t.getContrastText(e))),Y(t.SpeedDialAction,`fabHoverBg`,Rc(t.background.paper,.15)),Y(t.StepConnector,`border`,n(`palette-grey-400`)),Y(t.StepContent,`border`,n(`palette-grey-400`)),Y(t.Switch,`defaultColor`,n(`palette-common-white`)),Y(t.Switch,`defaultDisabledColor`,n(`palette-grey-100`)),Y(t.Switch,`primaryDisabledColor`,r(U,tLine truncated
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
`,Ep=Va`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`,Dp=Va`
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
`;const Op=X(`span`,{name:`MuiTouchRipple`,slot:`Root`})({overflow:`hidden`,pointerEvents:`none`,position:`absolute`,zIndex:0,top:0,right:0,bottom:0,left:0,borderRadius:`inherit`}),kp=X(Sp,{name:`MuiTouchRipple`,slot:`Ripple`})`
opacity: 0;
position: absolute;
&.${Cp.rippleVisible} {
opacity: 0.3;
transform: scale(1);
animation-name: ${Tp};
animation-duration: ${wp}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
&.${Cp.ripplePulsate} {
animation-duration: ${({theme:e})=>e.transitions.duration.shorter}ms;
}
& .${Cp.child} {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
& .${Cp.childLeaving} {
opacity: 0;
animation-name: ${Ep};
animation-duration: ${wp}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
& .${Cp.childPulsate} {
position: absolute;
/* @noflip */
left: 0px;
top: 0;
animation-name: ${Dp};
animation-duration: 2500ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
animation-iteration-count: infinite;
animation-delay: 200ms;
}
`;var Ap=S.forwardRef(function(e,t){let{center:n=!1,classes:r={},className:i,...a}=jd({props:e,name:`MuiTouchRipple`}),[o,s]=S.useState([]),c=S.useRef(0),l=S.useRef(null);S.useEffect(()=>{l.current&&=(l.current(),null)},[o]);let u=S.useRef(!1),d=Zf(),f=S.useRef(null),p=S.useRef(null),m=S.useCallback(e=>{let{pulsate:t,rippleX:n,rippleY:i,rippleSize:a,cb:o}=e;s(e=>[...e,(0,z.jsx)(kp,{classes:{ripple:V(r.ripple,Cp.ripple),rippleVisible:V(r.rippleVisible,Cp.rippleVisible),ripplePulsate:V(r.ripplePulsate,Cp.ripplePulsate),child:V(r.child,Cp.child),childLeaving:V(r.childLeaving,Cp.childLeaving),childPulsate:V(r.childPulsate,Cp.childPulsate)},timeout:wp,pulsate:t,rippleX:n,rippleY:i,rippleSize:a},c.current)]),c.current+=1,l.current=o},[r]),h=S.useCallback((e={},t={},r=()=>{})=>{let{pulsate:i=!1,center:a=n||t.pulsate,fakeElement:o=!1}=t;if(e?.type===`mousedown`&&u.current){u.current=!1;return}e?.type===`touchstart`&&(u.current=!0);let s=o?null:p.current,c=s?s.getBoundingClientRect():{width:0,height:0,left:0,top:0},l,h,g;if(a||e===void 0||e.clientX===0&&e.clientY===0||!e.clientX&&!e.touches)l=Math.round(c.width/2),h=Math.round(c.height/2);else{let{clientX:t,clientY:n}=e.touches&&e.touches.length>0?e.touches[0]:e;l=Math.round(t-c.left),h=Math.round(n-c.top)}if(a)g=Math.sqrt((2*c.width**2+c.height**2)/3),g%2==0&&(g+=1);else{let e=Math.max(Math.abs((s?s.clientWidth:0)-l),l)*2+2,t=Math.max(Math.abs((s?s.clientHeight:0)-h),h)*2+2;g=Math.sqrt(e**2+t**2)}e?.touches?f.current===null&&(f.current=()=>{m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},d.start(80,()=>{f.current&&=(f.current(),null)})):m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},[n,m,d]),g=S.useCallback(()=>{h({},{pulsate:!0})},[h]),_=S.useCallback((e,t)=>{if(d.clear(),e?.type===`touchend`&&f.current){f.current(),f.current=null,d.start(0,()=>{_(e,t)});return}f.current=null,s(e=>e.length>0?e.slice(1):e),l.current=t},[d]);return S.useImperativeHandle(t,()=>({pulsate:g,start:h,stop:_}),[g,h,_]),(0,z.jsx)(Op,{className:V(Cp.root,r.root,i),ref:p,...a,children:(0,z.jsx)(Gf,{component:null,exit:!0,children:o})})});function jp(e){return $s(`MuiButtonBase`,e)}var Mp=ec(`MuiButtonBase`,[`root`,`disabled`,`focusVisible`]),Np=e=>{let{disabled:t,focusVisible:n,focusVisibleClassName:r,classes:i}=e,a=Al({root:[`root`,t&&`disabled`,n&&`focusVisible`]},jp,i);return n&&r&&(a.root+=` ${r}`),a};const Pp=X(`button`,{name:`MuiButtonBase`,slot:`Root`})({display:`inline-flex`,alignItems:`center`,justifyContent:`center`,position:`relative`,boxSizing:`border-box`,WebkitTapHighlightColor:`transparent`,backgroundColor:`transparent`,outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:`pointer`,userSelect:`none`,verticalAlign:`middle`,MozAppearance:`none`,WebkitAppearance:`none`,textDecoration:`none`,color:`inherit`,"&::-moz-focus-inner":{borderStyle:`none`},[`&.${Mp.disabled}`]:{pointerEvents:`none`,cursor:`default`},"@media print":{colorAdjust:`exact`}});var Fp=S.forwardRef(function(e,t){let n=jd({props:e,name:`MuiButtonBase`}),{action:r,centerRipple:i=!1,children:a,className:o,component:s=`button`,disabled:c=!1,disableRipple:l=!1,disableTouchRipple:u=!1,focusRipple:d=!1,focusVisibleClassName:f,LinkComponent:p=`a`,onBlur:m,onClick:h,onContextMenu:g,onDragLeave:_,onFocus:v,onFocusVisible:y,onKeyDown:b,onKeyUp:x,onMouseDown:C,onMouseLeave:w,onMouseUp:T,onTouchEnd:E,onTouchMove:ee,onTouchStart:D,tabIndex:O=0,TouchRippleProps:te,touchRippleRef:k,type:A,...j}=n,ne=S.useRef(null),M=yp(),N=vf(M.ref,k),[P,re]=S.useState(!1);c&&P&&re(!1),S.useImperativeHandle(r,()=>({focusVisible:()=>{re(!0),ne.current.focus()}}),[]);let ie=M.shouldMount&&!l&&!c;S.useEffect(()=>{P&&d&&!l&&M.pulsate()},[l,d,P,M]);let ae=Ip(M,`start`,C,u),oe=Ip(M,`stop`,g,u),se=Ip(M,`stop`,_,u),F=Ip(M,`stop`,T,u),ce=Ip(M,`stop`,e=>{P&&e.preventDefault(),w&&w(e)},u),le=Ip(M,`start`,D,u),ue=Ip(M,`stop`,E,u),de=Ip(M,`stop`,ee,u),fe=Ip(M,`stop`,e=>{_p(e.target)||re(!1),m&&m(e)},!1),pe=gf(e=>{ne.current||=e.currentTarget,_p(e.target)&&(re(!0),y&&y(e)),v&&v(e)}),me=()=>{let e=ne.current;return s&&s!==`button`&&!(e.tagName===`A`&&e.href)},he=gf(e=>{d&&!e.repeat&&P&&e.key===` `&&M.stop(e,()=>{M.start(e)}),e.target===e.currentTarget&&me()&&e.key===` `&&e.preventDefault(),b&&b(e),e.target===e.currentTarget&&me()&&e.key===`Enter`&&!c&&(e.preventDefault(),h&&h(e))}),ge=gf(e=>{d&&e.key===` `&&P&&!e.defaultPrevented&&M.stop(e,()=>{M.pulsate(e)}),x&&x(e),h&&e.target===e.currentTarget&&me()&&e.key===` `&&!e.defaultPrevented&&h(e)}),_e=s;_e===`button`&&(j.href||j.to)&&(_e=p);let ve={};_e===`button`?(ve.type=A===void 0?`button`:A,ve.disabled=c):(!j.href&&!j.to&&(ve.role=`button`),c&&(ve[`aria-disabled`]=c));let ye=vf(t,ne),be={...n,centerRipple:i,component:s,disabled:c,disableRipple:l,disableTouchRipple:u,focusRipple:d,tabIndex:O,focusVisible:P},xe=Np(be);return(0,z.jsxs)(Pp,{as:_e,className:V(xe.root,o),ownerState:be,onBlur:fe,onClick:h,onContextMenu:oe,onFocus:pe,onKeyDown:he,onKeyUp:ge,onMouseDown:ae,onMouseLeave:ce,onMouseUp Line truncated
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`,Kp=Va`
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: -126px;
}
`,qp=typeof Gp==`string`?null:Ba`
animation: ${Gp} 1.4s linear infinite;
`,Jp=typeof Kp==`string`?null:Ba`
animation: ${Kp} 1.4s ease-in-out infinite;
`,Yp=e=>{let{classes:t,variant:n,color:r,disableShrink:i}=e;return Al({root:[`root`,n,`color${Q(r)}`],svg:[`svg`],track:[`track`],circle:[`circle`,`circle${Q(n)}`,i&&`circleDisableShrink`]},Up,t)},Xp=X(`span`,{name:`MuiCircularProgress`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,t[n.variant],t[`color${Q(n.color)}`]]}})(Zd(({theme:e})=>({display:`inline-block`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`transform`)}},{props:{variant:`indeterminate`},style:qp||{animation:`${Gp} 1.4s linear infinite`}},...Object.entries(e.palette).filter(Bp()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}}))]}))),Zp=X(`svg`,{name:`MuiCircularProgress`,slot:`Svg`})({display:`block`}),Qp=X(`circle`,{name:`MuiCircularProgress`,slot:`Circle`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.circle,t[`circle${Q(n.variant)}`],n.disableShrink&&t.circleDisableShrink]}})(Zd(({theme:e})=>({stroke:`currentColor`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`stroke-dashoffset`)}},{props:{variant:`indeterminate`},style:{strokeDasharray:`80px, 200px`,strokeDashoffset:0}},{props:({ownerState:e})=>e.variant===`indeterminate`&&!e.disableShrink,style:Jp||{animation:`${Kp} 1.4s ease-in-out infinite`}}]}))),$p=X(`circle`,{name:`MuiCircularProgress`,slot:`Track`})(Zd(({theme:e})=>({stroke:`currentColor`,opacity:(e.vars||e).palette.action.activatedOpacity}))),em=S.forwardRef(function(e,t){let n=jd({props:e,name:`MuiCircularProgress`}),{className:r,color:i=`primary`,disableShrink:a=!1,enableTrackSlot:o=!1,size:s=40,style:c,thickness:l=3.6,value:u=0,variant:d=`indeterminate`,...f}=n,p={...n,color:i,disableShrink:a,size:s,thickness:l,value:u,variant:d,enableTrackSlot:o},m=Yp(p),h={},g={},_={};if(d===`determinate`){let e=2*Math.PI*((Wp-l)/2);h.strokeDasharray=e.toFixed(3),_[`aria-valuenow`]=Math.round(u),h.strokeDashoffset=`${((100-u)/100*e).toFixed(3)}px`,g.transform=`rotate(-90deg)`}return(0,z.jsx)(Xp,{className:V(m.root,r),style:{width:s,height:s,...g,...c},ownerState:p,ref:t,role:`progressbar`,..._,...f,children:(0,z.jsxs)(Zp,{className:m.svg,ownerState:p,viewBox:`${Wp/2} ${Wp/2} ${Wp} ${Wp}`,children:[o?(0,z.jsx)($p,{className:m.track,ownerState:p,cx:Wp,cy:Wp,r:(Wp-l)/2,fill:`none`,strokeWidth:l,"aria-hidden":`true`}):null,(0,z.jsx)(Qp,{className:m.circle,style:h,ownerState:p,cx:Wp,cy:Wp,r:(Wp-l)/2,fill:`none`,strokeWidth:l})]})})});function tm(e){return $s(`MuiIconButton`,e)}var nm=ec(`MuiIconButton`,[`root`,`disabled`,`colorInherit`,`colorPrimary`,`colorSecondary`,`colorError`,`colorInfo`,`colorSuccess`,`colorWarning`,`edgeStart`,`edgeEnd`,`sizeSmall`,`sizeMedium`,`sizeLarge`,`loading`,`loadingIndicator`,`loadingWrapper`]),rm=e=>{let{classes:t,disabled:n,color:r,edge:i,size:a,loading:o}=e;return Al({root:[`root`,o&&`loading`,n&&`disabled`,r!==`default`&&`color${Q(r)}`,i&&`edge${Q(i)}`,`size${Q(a)}`],loadingIndicator:[`loadingIndicator`],loadingWrapper:[`loadingWrapper`]},tm,t)},im=X(Lp,{name:`MuiIconButton`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.loading&&t.loading,n.color!==`default`&&t[`color${Q(n.color)}`],n.edge&&t[`edge${Q(n.edge)}`],t[`size${Q(n.size)}`]]}})(Zd(({theme:e})=>({textAlign:`center`,flex:`0 0 auto`,fontSize:e.typography.pxToRem(24),padding:8,borderRadius:`50%`,color:(e.vars||e).palette.action.active,transition:e.transitions.create(`background-color`,{duration:e.transitions.duration.shortest}),variants:[{props:e=>!e.disableRipple,style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"&:hover":{backgroundColor:`var(--IconButton-hoverBg)`,"@media (hover: none)":{backgroundColor:`transparent`}}}},{props:{edge:`start`},style:{marginLeft:-12}},{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:{edge:`end`},style:{marginRight:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}}]})),Zd(({theme:e})=>({variants:[{props:{color:`inherit`},style:{color:`inherit`}},...Object.entries(e.palette).filter(Bp()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}})),...Object.entries(e.palette).filter(Bp()).map(([t])=>({props:{color:t},style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity)}})),{props:{size:`small`},style:{padding:5,fontSize:e.typography.pxToRem(18)}},{props:{size:`large`},style:{padding:12,fontSize:e.typography.pxToRem(28)}}],[`&.${nm.disabled}`]:{backgroundColor:`transparent`,color:(e.vars||e).palette.action.disabled},[`&.${nm.loading}`]:{color:`transparent`}}))),am=X(`span`,{name:`MuiIconButton`,slot:`LoadingIndicator`})(({theme:e})=>({display:`none`,position:`absolute`,visibility:`visible`,top:`50%`,left:`50%`,transform:`translate(-50%, -50%)`,color:(e.vars||e).palette.action.disabled,variants:[{props:{loading:!0},style:{display:`flex`}}]})),om=S.forwardRef(function(e,t){let n=jd({props:e,name:`MuiIconButtonLine truncated
-121
View File
@@ -1,121 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vitec_downloadcardcollection-CPiNsMSZ.js","assets/jszip.min-PsHYDUlW.js","assets/vitec_datasheets-Chy9IC_0.js"])))=>i.map(i=>d[i]);
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),l=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function.")});(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function O(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var k=/\/+/g;function A(e,t){return typeof e==`object`&&e&&e.key!=null?O(``+e.key):t.toString(36)}function te(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ne(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ne(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+A(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(k,`$&/`)+`/`),ne(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(k,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u<e.length;u++)a=e[u],s=l+A(a,u),c+=ne(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+A(a,u++),c+=ne(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return ne(te(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function re(e,t,n){if(e==null)return e;var r=[],i=0;return ne(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function j(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(eLine truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+ve+e+ye}var xe=!1;function Se(e,t){if(!e||xe)return``;xe=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,`props`,{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,`name`,{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{xe=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?be(n):``}function Ce(e,t){switch(e.tag){case 26:case 27:case 5:return be(e.type);case 16:return be(`Lazy`);case 13:return e.child!==t&&t!==null?be(`Suspense Fallback`):be(`Suspense`);case 19:return be(`SuspenseList`);case 0:case 15:return Se(e.type,!1);case 11:return Se(e.type.render,!1);case 1:return Se(e.type,!0);case 31:return be(`Activity`);default:return``}}function we(e){try{var t=``,n=null;do t+=Ce(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var Te=Object.prototype.hasOwnProperty,Ee=t.unstable_scheduleCallback,De=t.unstable_cancelCallback,Oe=t.unstable_shouldYield,ke=t.unstable_requestPaint,Ae=t.unstable_now,je=t.unstable_getCurrentPriorityLevel,Me=t.unstable_ImmediatePriority,Ne=t.unstable_UserBlockingPriority,Pe=t.unstable_NormalPriority,Fe=t.unstable_LowPriority,Ie=t.unstable_IdlePriority,Le=t.log,Re=t.unstable_setDisableYieldValue,ze=null,Be=null;function Ve(e){if(typeof Le==`function`&&Re(e),Be&&typeof Be.setStrictMode==`function`)try{Be.setStrictMode(ze,e)}catch{}}var He=Math.clz32?Math.clz32:Ge,Ue=Math.log,We=Math.LN2;function Ge(e){return e>>>=0,e===0?32:31-(Ue(e)/We|0)|0}var Ke=256,qe=262144,Je=4194304;function Ye(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Xe(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ye(n))):i=Ye(o):i=Ye(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ye(n))):i=Ye(o)):i=Ye(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ze(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Qe(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function $e(){var e=Je;return Je<<=1,!(Je&62914560)&&(Je=4194304),e}function et(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function tt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function nt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-He(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&rt(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function rt(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-He(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function it(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-He(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function at(e,t){var n=t&-t;return n=n&42?1:ot(n),(n&(e.suspendedLanes|t))===0?n:0}function ot(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function st(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function ct(){var e=N.p;return e===0?(e=window.event,e===void 0?32:hp(e.type)):e}function lt(e,t){var n=N.p;try{return N.p=e,t()}finally{N.p=n}}var ut=Math.random().toString(36).slice(2),dt=`__reactFiber$`+ut,ft=`__reactProps$`+ut,pt=`__reactContainer$`+ut,mt=`__reactEvents$`+ut,ht=`__reactListeners$`+ut,gt=`__reactHandles$`+ut,_t=`__reactResources$`+ut,vt=`__reactMarker$`+ut;function yt(e){delete e[dt],delete e[ft],delete e[mt],delete e[ht],delete e[gt]}function bt(e){var t=e[dt];if(t)return t;for(var n=e.parentNode;n;){if(t=n[pt]||n[dt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=ff(e);e!==null;){if(n=e[dt])return n;e=ff(e)}return t}e=n,n=e.parentNode}return null}function xt(e){if(e=e[dt]||e[pt]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function St(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function Ct(e){var t=e[_t];return t||=e[_t]={hoistableStyles:new Map,hoistableScripts:new Map},t}function wt(e){e[vt]=!0}var Tt=new Set,Et={};function Dt(e,t){Ot(e,t),Ot(e+`Capture`,t)}function Ot(e,t){for(Et[e]=t,e=0;e<t.length;e++)Tt.add(t[e])}var kt=RegExp(`^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001Line truncated
`).replace(jd,``)}function Nd(e,t){return t=Md(t),Md(e)===t}function Q(e,t,n,r,a,o){switch(n){case`children`:typeof r==`string`?t===`body`||t===`textarea`&&r===``||Xt(e,r):(typeof r==`number`||typeof r==`bigint`)&&t!==`body`&&Xt(e,``+r);break;case`className`:Pt(e,`class`,r);break;case`tabIndex`:Pt(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:Pt(e,n,r);break;case`style`:$t(e,r,o);break;case`data`:if(t!==`object`){Pt(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=rn(``+r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}else typeof o==`function`&&(n===`formAction`?(t!==`input`&&Q(e,t,`name`,a.name,a,null),Q(e,t,`formEncType`,a.formEncType,a,null),Q(e,t,`formMethod`,a.formMethod,a,null),Q(e,t,`formTarget`,a.formTarget,a,null)):(Q(e,t,`encType`,a.encType,a,null),Q(e,t,`method`,a.method,a,null),Q(e,t,`target`,a.target,a,null)));if(r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=rn(``+r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=an);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=rn(``+r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``+r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Z(`beforetoggle`,e),Z(`toggle`,e),Nt(e,`popover`,r);break;case`xlinkActuate`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:Nt(e,`is`,r);break;case`innerText`:case`textContent`:break;default:(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)&&(n=tn.get(n)||n,Nt(e,n,r))}}function Pd(e,t,n,r,a,o){switch(n){case`style`:$t(e,r,o);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`children`:typeof r==`string`?Xt(e,r):(typeof r==`number`||typeof r==`bigint`)&&Xt(e,``+r);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`onClick`:r!=null&&(e.onclick=an);break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`innerHTML`:case`re Line truncated
Please change the parent <Route path="${e}"> to <Route path="${e===`/`?`*`:`${e}/*`}">.`)}let u=un(),d;if(t){let e=typeof t==`string`?rt(t):t;Ze(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):ot(e,{pathname:p});Qe(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),Qe(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);let h=Cn(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:Ft([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:Ft([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?S.createElement($t.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},h):h}function _n(){let e=Mn(),t=Vt(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=S.createElement(S.Fragment,null,S.createElement(`p`,null,`💿 Hey developer 👋`),S.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,S.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,S.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),S.createElement(S.Fragment,null,S.createElement(`h2`,null,`Unexpected Application Error!`),S.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?S.createElement(`pre`,{style:i},n):null,o)}var vn=S.createElement(_n,null),yn=class extends S.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=sn(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:S.createElement(en.Provider,{value:this.props.routeContext},S.createElement(tn.Provider,{value:e,children:this.props.component}));return this.context?S.createElement(xn,{error:e},t):t}};yn.contextType=qt;var bn=new WeakMap;function xn({children:e,error:t}){let{basename:n}=S.useContext(Qt);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=on(t.digest);if(e){let r=bn.get(t);if(r)throw r;let i=Wt(e.location,n);if(Ut&&!bn.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw bn.set(t,n),n}return S.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function Sn({routeContext:e,match:t,children:n}){let r=S.useContext(Gt);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),S.createElement(en.Provider,{value:e},n)}function Cn(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);Ze(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e<i.length;e++){let t=i[e];if((t.route.HydrateFallback||t.route.hydrateFallbackElement)&&(s=e),t.route.id){lLine truncated
`)),(kc===void 0?Oc:Ac)(l,a,o,s,c)}}jc();function Mc(e,t=-(2**53-1),n=2**53-1){return Math.max(t,Math.min(e,n))}var Nc=Mc;function Pc(e,t=0,n=1){return Nc(e,t,n)}function Fc(e){e=e.slice(1);let t=RegExp(`.{1,${e.length>=6?2:1}}`,`g`),n=e.match(t);return n&&n[0].length===1&&(n=n.map(e=>e+e)),n?`rgb${n.length===4?`a`:``}(${n.map((e,t)=>t<3?parseInt(e,16):Math.round(parseInt(e,16)/255*1e3)/1e3).join(`, `)})`:``}function Ic(e){if(e.type)return e;if(e.charAt(0)===`#`)return Ic(Fc(e));let t=e.indexOf(`(`),n=e.substring(0,t);if(![`rgb`,`rgba`,`hsl`,`hsla`,`color`].includes(n))throw Error(si(9,e));let r=e.substring(t+1,e.length-1),i;if(n===`color`){if(r=r.split(` `),i=r.shift(),r.length===4&&r[3].charAt(0)===`/`&&(r[3]=r[3].slice(1)),![`srgb`,`display-p3`,`a98-rgb`,`prophoto-rgb`,`rec-2020`].includes(i))throw Error(si(10,i))}else r=r.split(`,`);return r=r.map(e=>parseFloat(e)),{type:n,values:r,colorSpace:i}}const Lc=e=>{let t=Ic(e);return t.values.slice(0,3).map((e,n)=>t.type.includes(`hsl`)&&n!==0?`${e}%`:e).join(` `)},Rc=(e,t)=>{try{return Lc(e)}catch{return e}};function zc(e){let{type:t,colorSpace:n}=e,{values:r}=e;return t.includes(`rgb`)?r=r.map((e,t)=>t<3?parseInt(e,10):e):t.includes(`hsl`)&&(r[1]=`${r[1]}%`,r[2]=`${r[2]}%`),r=t.includes(`color`)?`${n} ${r.join(` `)}`:`${r.join(`, `)}`,`${t}(${r})`}function Bc(e){e=Ic(e);let{values:t}=e,n=t[0],r=t[1]/100,i=t[2]/100,a=r*Math.min(i,1-i),o=(e,t=(e+n/30)%12)=>i-a*Math.max(Math.min(t-3,9-t,1),-1),s=`rgb`,c=[Math.round(o(0)*255),Math.round(o(8)*255),Math.round(o(4)*255)];return e.type===`hsla`&&(s+=`a`,c.push(t[3])),zc({type:s,values:c})}function Vc(e){e=Ic(e);let t=e.type===`hsl`||e.type===`hsla`?Ic(Bc(e)).values:e.values;return t=t.map(t=>(e.type!==`color`&&(t/=255),t<=.03928?t/12.92:((t+.055)/1.055)**2.4)),Number((.2126*t[0]+.7152*t[1]+.0722*t[2]).toFixed(3))}function Hc(e,t){let n=Vc(e),r=Vc(t);return(Math.max(n,r)+.05)/(Math.min(n,r)+.05)}function Uc(e,t){return e=Ic(e),t=Pc(t),(e.type===`rgb`||e.type===`hsl`)&&(e.type+=`a`),e.type===`color`?e.values[3]=`/${t}`:e.values[3]=t,zc(e)}function Wc(e,t,n){try{return Uc(e,t)}catch{return e}}function Gc(e,t){if(e=Ic(e),t=Pc(t),e.type.includes(`hsl`))e.values[2]*=1-t;else if(e.type.includes(`rgb`)||e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]*=1-t;return zc(e)}function V(e,t,n){try{return Gc(e,t)}catch{return e}}function Kc(e,t){if(e=Ic(e),t=Pc(t),e.type.includes(`hsl`))e.values[2]+=(100-e.values[2])*t;else if(e.type.includes(`rgb`))for(let n=0;n<3;n+=1)e.values[n]+=(255-e.values[n])*t;else if(e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]+=(1-e.values[n])*t;return zc(e)}function H(e,t,n){try{return Kc(e,t)}catch{return e}}function qc(e,t=.15){return Vc(e)>.5?Gc(e,t):Kc(e,t)}function Jc(e,t,n){try{return qc(e,t)}catch{return e}}var Yc=S.createContext();const Xc=()=>S.useContext(Yc)??!1;var Zc=S.createContext(void 0);function Qc(e){let{theme:t,name:n,props:r}=e;if(!t||!t.components||!t.components[n])return r;let i=t.components[n];return i.defaultProps?wc(i.defaultProps,r,t.components.mergeClassNameAndStyle):!i.styleOverrides&&!i.variants?wc(i,r,t.components.mergeClassNameAndStyle):r}function $c({props:e,name:t}){return Qc({props:e,name:t,theme:{components:S.useContext(Zc)}})}var el=0;function tl(e){let[t,n]=S.useState(e),r=e||t;return S.useEffect(()=>{t??(el+=1,n(`mui-${el}`))},[t]),r}var nl={...S}.useId;function rl(e){if(nl!==void 0){let t=nl();return e??t}return tl(e)}var il={theme:void 0};function al(e){let t,n;return function(r){let i=t;return(i===void 0||r.theme!==n)&&(il.theme=r.theme,i=dc(e(il)),t=i,n=r.theme),i}}function ol(e=``){function t(...n){if(!n.length)return``;let r=n[0];return typeof r==`string`&&!r.match(/(#|\(|\)|(-?(\d*\.)?\d+)(px|em|%|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc))|^(-?(\d*\.)?\d+)$|(\d+ \d+ \d+)/)?`, var(--${e?`${e}-`:``}${r}${t(...n.slice(1))})`:`, ${r}`}return(n,...r)=>`var(--${e?`${e}-`:``}${n}${t(...r)})`}const sl=(e,t,n,r=[])=>{let i=e;t.forEach((e,a)=>{a===t.length-1?Array.isArray(i)?i[Number(e)]=n:i&&typeof i==`object`&&(i[e]=n):i&&typeof i==`object`&&(i[e]||(i[e]=r.includes(e)?[]:{}),i=i[e])})},cl=(e,t,n)=>{function r(e,i=[],a=[]){Object.entries(e).forEach(([e,o])=>{(!n||n&&!n([...i,e]))&&o!=null&&(typeof o==`object`&&Object.keys(o).length>0?r(o,[...i,e],Array.isArray(o)?[...a,e]:a):t([...i,e],o,a))})}r(e)};var ll=(e,t)=>typeof t==`number`?[`lineHeight`,`fontWeight`,`opacity`,`zIndex`].some(t=>e.includes(t))||e[e.length-1].toLowerCase().includes(`opacity`)?t:`${t}px`:t;function ul(e,t){let{prefix:n,shouldSkipGeneratingVar:r}=t||{},i={},a={},o={};return cl(e,(e,t,s)=>{if((typeof t==`string`||typeof t==`number`)&&(!r||!r(e,t))){let r=`--${n?`${n}-`:``}${e.join(`-`)}`,c=ll(e,t);Object.assign(i,{[r]:c}),sl(a,e,`var(${r})`,s),sl(o,e,`var(${r}, ${c})`,s)}},e=>e[0]===`vars`),{css:i,vars:a,varsWithDefaults:o}}function dl(e,t={}){let{getSelector:n=_,disableCssColorScheme:r,colorSchemeSelector:i,enableContrastVars:a}=t,{colorSchemes:o={},components:s,defaultColorS Line truncated
const theme = ${JSON.stringify(t,null,2)};
theme.breakpoints = createBreakpoints(theme.breakpoints || {});
theme.transitions = createTransitions(theme.transitions || {});
export default theme;`}function wu(e){return typeof e==`number`?`${(e*100).toFixed(0)}%`:`calc((${e}) * 100%)`}var Tu=e=>{if(!Number.isNaN(+e))return+e;let t=e.match(/\d*\.?\d+/g);if(!t)return 0;let n=0;for(let e=0;e<t.length;e+=1)n+=+t[e];return n};function Eu(e){Object.assign(e,{alpha(t,n){let r=this||e;return r.colorSpace?`oklch(from ${t} l c h / ${typeof n==`string`?`calc(${n})`:n})`:r.vars?`rgba(${t.replace(/var\(--([^,\s)]+)(?:,[^)]+)?\)+/g,`var(--$1Channel)`)} / ${typeof n==`string`?`calc(${n})`:n})`:Uc(t,Tu(n))},lighten(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #fff ${wu(n)})`:Kc(t,n)},darken(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #000 ${wu(n)})`:Gc(t,n)}})}function Du(e={},...t){let{breakpoints:n,mixins:r={},spacing:i,palette:a={},transitions:o={},typography:s={},shape:c,colorSpace:l,...u}=e;if(e.vars&&e.generateThemeVars===void 0)throw Error(si(20));let d=iu({...a,colorSpace:l}),f=Js(e),p=wo(f,{mixins:ou(f.breakpoints,r),palette:d,shadows:hu.slice(),typography:uu(d,s),transitions:bu(o),zIndex:{...xu}});return p=wo(p,u),p=t.reduce((e,t)=>wo(e,t),p),p.unstable_sxConfig={...Bs,...u?.unstable_sxConfig},p.unstable_sx=function(e){return Gs({sx:e,theme:this})},p.toRuntimeSource=Cu,Eu(p),p}var Ou=Du;function ku(e){let t;return t=e<1?5.11916*e**2:4.5*Math.log(e+1)+2,Math.round(t*10)/1e3}var Au=[...Array(25)].map((e,t)=>{if(t===0)return`none`;let n=ku(t);return`linear-gradient(rgba(255 255 255 / ${n}), rgba(255 255 255 / ${n}))`});function ju(e){return{inputPlaceholder:e===`dark`?.5:.42,inputUnderline:e===`dark`?.7:.42,switchTrackDisabled:e===`dark`?.2:.12,switchTrack:e===`dark`?.3:.38}}function Mu(e){return e===`dark`?Au:[]}function Nu(e){let{palette:t={mode:`light`},opacity:n,overlays:r,colorSpace:i,...a}=e,o=iu({...t,colorSpace:i});return{palette:o,opacity:{...ju(o.mode),...n},overlays:r||Mu(o.mode),...a}}function Pu(e){return!!e[0].match(/(cssVarPrefix|colorSchemeSelector|modularCssLayers|rootSelector|typography|mixins|breakpoints|direction|transitions)/)||!!e[0].match(/sxConfig$/)||e[0]===`palette`&&!!e[1]?.match(/(mode|contrastThreshold|tonalOffset)/)}var Fu=e=>[...[...Array(25)].map((t,n)=>`--${e?`${e}-`:``}overlays-${n}`),`--${e?`${e}-`:``}palette-AppBar-darkBg`,`--${e?`${e}-`:``}palette-AppBar-darkColor`],Iu=e=>(t,n)=>{let r=e.rootSelector||`:root`,i=e.colorSchemeSelector,a=i;if(i===`class`&&(a=`.%s`),i===`data`&&(a=`[data-%s]`),i?.startsWith(`data-`)&&!i.includes(`%s`)&&(a=`[${i}="%s"]`),e.defaultColorScheme===t){if(t===`dark`){let i={};return Fu(e.cssVarPrefix).forEach(e=>{i[e]=n[e],delete n[e]}),a===`media`?{[r]:n,"@media (prefers-color-scheme: dark)":{[r]:i}}:a?{[a.replace(`%s`,t)]:i,[`${r}, ${a.replace(`%s`,t)}`]:n}:{[r]:{...n,...i}}}if(a&&a!==`media`)return`${r}, ${a.replace(`%s`,String(t))}`}else if(t){if(a===`media`)return{[`@media (prefers-color-scheme: ${String(t)})`]:{[r]:n}};if(a)return a.replace(`%s`,String(t))}return r};function Lu(e,t){t.forEach(t=>{e[t]||(e[t]={})})}function q(e,t,n){!e[t]&&n&&(e[t]=n)}function Ru(e){return typeof e!=`string`||!e.startsWith(`hsl`)?e:Bc(e)}function zu(e,t){`${t}Channel`in e||(e[`${t}Channel`]=Rc(Ru(e[t]),`MUI: Can't create \`palette.${t}Channel\` because \`palette.${t}\` is not one of these formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().
To suppress this warning, you need to explicitly provide the \`palette.${t}Channel\` as a string (in rgb format, for example "12 12 12") or undefined if you want to remove the channel token.`))}function Bu(e){return typeof e==`number`?`${e}px`:typeof e==`string`||typeof e==`function`||Array.isArray(e)?e:`8px`}var Vu=e=>{try{return e()}catch{}};const Hu=(e=`mui`)=>ol(e);function Uu(e,t,n,r,i){if(!n)return;n=n===!0?{}:n;let a=i===`dark`?`dark`:`light`;if(!r){t[i]=Nu({...n,palette:{mode:a,...n?.palette},colorSpace:e});return}let{palette:o,...s}=Ou({...r,palette:{mode:a,...n?.palette},colorSpace:e});return t[i]={...n,palette:o,opacity:{...ju(a),...n?.opacity},overlays:n?.overlays||Mu(a)},s}function Wu(e={},...t){let{colorSchemes:n={light:!0},defaultColorScheme:r,disableCssColorScheme:i=!1,cssVarPrefix:a=`mui`,nativeColor:o=!1,shouldSkipGeneratingVar:s=Pu,colorSchemeSelector:c=n.light&&n.dark?`media`:void 0,rootSelector:l=`:root`,...u}=e,d=Object.keys(n)[0],f=r||(n.light&&d!==`light`?`light`:d),p=Hu(a),{[f]:m,light:h,dark:g,..._}=n,v={..._},y=m;if((f===`dark`&&!(`dark`in n)||f===`light`&&!(`light`in n))&&(y=!0),!y)throw Error(si(21,f));let b;o&&(b=`oklch`);let x=Uu(b,v,y,u,f);h&&!v.light&&Uu(b,v,h,void 0,`light`),g&&!v.dark&&Uu(b,v,g,void 0,`dark`);let S={defaultColorScheme:f,...x,cssVarPrefix:a,colorSchemeSelector:c,rootSelector:l,getCssVar:p,colorSchemes:v,font:{...au(x.typography),...x.font},spacing:Bu(u.spacing)};Object.keys(S.colorSchemes).forEach(e=>{let t=S.colorSchemes[e].palette,n=e=>{let n=e.split(`-`),r=n[1],i=n[2];return p(e,t[r][i])};t.mode===`light`&&(q(t.common,`background`,`#fff`),q(t.common,`onBackground`,`#000`)),t.mode===`dark`&&(q(t.common,`background`,`#000`),q(t.common,`onBackground`,`#fff`));function r(e,t,n){if(b){let r;return e===Wc&&(r=`transparent ${((1-n)*100).toFixed(0)}%`),e===V&&(r=`#000 ${(n*100).toFixed(0)}%`),e===H&&(r=`#fff ${(n*100).toFixed(0)}%`),`color-mix(in ${b}, ${t}, ${r})`}return e(t,n)}if(Lu(t,[`Alert`,`AppBar`,`Avatar`,`Button`,`Chip`,`FilledInput`,`LinearProgress`,`Skeleton`,`Slider`,`SnackbarContent`,`SpeedDialAction`,`StepConnector`,`StepContent`,`Switch`,`TableCell`,`Tooltip`]),t.mode===`light`){q(t.Alert,`errorColor`,r(V,t.error.light,.6)),q(t.Alert,`infoColor`,r(V,t.info.light,.6)),q(t.Alert,`successColor`,r(V,t.success.light,.6)),q(t.Alert,`warningColor`,r(V,t.warning.light,.6)),q(t.Alert,`errorFilledBg`,n(`palette-error-main`)),q(t.Alert,`infoFilledBg`,n(`palette-info-main`)),q(t.Alert,`successFilledBg`,n(`palette-success-main`)),q(t.Alert,`warningFilledBg`,n(`palette-warning-main`)),q(t.Alert,`errorFilledColor`,Vu(()=>t.getContrastText(t.error.main))),q(t.Alert,`infoFilledColor`,Vu(()=>t.getContrastText(t.info.main))),q(t.Alert,`successFilledColor`,Vu(()=>t.getContrastText(t.success.main))),q(t.Alert,`warningFilledColor`,Vu(()=>t.getContrastText(t.warning.main))),q(t.Alert,`errorStandardBg`,r(H,t.error.light,.9)),q(t.Alert,`infoStandardBg`,r(H,t.info.light,.9)),q(t.Alert,`successStandardBg`,r(H,t.success.light,.9)),q(t.Alert,`warningStandardBg`,r(H,t.warning.light,.9)),q(t.Alert,`errorIconColor`,n(`palette-error-main`)),q(t.Alert,`infoIconColor`,n(`palette-info-main`)),q(t.Alert,`successIconColor`,n(`palette-success-main`)),q(t.Alert,`warningIconColor`,n(`palette-warning-main`)),q(t.AppBar,`defaultBg`,n(`palette-grey-100`)),q(t.Avatar,`defaultBg`,n(`palette-grey-400`)),q(t.Button,`inheritContainedBg`,n(`palette-grey-300`)),q(t.Button,`inheritContainedHoverBg`,n(`palette-grey-A100`)),q(t.Chip,`defaultBorder`,n(`palette-grey-400`)),q(t.Chip,`defaultAvatarColor`,n(`palette-grey-700`)),q(t.Chip,`defaultIconColor`,n(`palette-grey-700`)),q(t.FilledInput,`bg`,`rgba(0, 0, 0, 0.06)`),q(t.FilledInput,`hoverBg`,`rgba(0, 0, 0, 0.09)`),q(t.FilledInput,`disabledBg`,`rgba(0, 0, 0, 0.12)`),q(t.LinearProgress,`primaryBg`,r(H,t.primary.main,.62)),q(t.LinearProgress,`secondaryBg`,r(H,t.secondary.main,.62)),q(t.LinearProgress,`errorBg`,r(H,t.error.main,.62)),q(t.LinearProgress,`infoBg`,r(H,t.info.main,.62)),q(t.LinearProgress,`successBg`,r(H,t.success.main,.62)),q(t.LinearProgress,`warningBg`,r(H,t.warning.main,.62)),q(t.Skeleton,`bg`,b?r(Wc,t.text.primary,.11):`rgba(${n(`palette-text-primaryChannel`)} / 0.11)`),q(t.Slider,`primaryTrack`,r(H,t.primary.main,.62)),q(t.Slider,`secondaryTrack`,r(H,t.secondary.main,.62)),q(t.Slider,`errorTrack`,r(H,t.error.main,.62)),q(t.Slider,`infoTrack`,r(H,t.info.main,.62)),q(t.Slider,`successTrack`,r(H,t.success.main,.62)),q(t.Slider,`warningTrack`,r(H,t.warning.main,.62));let e=b?r(V,t.background.default,.6825):Jc(t.background.default,.8);q(t.SnackbarContent,`bg`,e),q(t.SnackbarContent,`color`,Vu(()=>b?Jl.text.primary:t.getContrastText(e))),q(t.SpeedDialAction,`fabHoverBg`,Jc(t.background.paper,.15)),q(t.StepConnector,`border`,n(`palette-grey-400`)),q(t.StepContent,`border`,n(`palette-grey-400`)),q(t.Switch,`defaultColor`,n(`palette-common-white`)),q(t.Switch,`defaultDisabledColor`,n(`palette-grey-100`)),q(t.Switch,`primaryDisabledColor`,r(H,tLine truncated
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
`,Df=ro`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`,Of=ro`
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
`;const kf=Y(`span`,{name:`MuiTouchRipple`,slot:`Root`})({overflow:`hidden`,pointerEvents:`none`,position:`absolute`,zIndex:0,top:0,right:0,bottom:0,left:0,borderRadius:`inherit`}),Af=Y(Cf,{name:`MuiTouchRipple`,slot:`Ripple`})`
opacity: 0;
position: absolute;
&.${wf.rippleVisible} {
opacity: 0.3;
transform: scale(1);
animation-name: ${Ef};
animation-duration: ${Tf}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
&.${wf.ripplePulsate} {
animation-duration: ${({theme:e})=>e.transitions.duration.shorter}ms;
}
& .${wf.child} {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
& .${wf.childLeaving} {
opacity: 0;
animation-name: ${Df};
animation-duration: ${Tf}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
& .${wf.childPulsate} {
position: absolute;
/* @noflip */
left: 0px;
top: 0;
animation-name: ${Of};
animation-duration: 2500ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
animation-iteration-count: infinite;
animation-delay: 200ms;
}
`;var jf=S.forwardRef(function(e,t){let{center:n=!1,classes:r={},className:i,...a}=ed({props:e,name:`MuiTouchRipple`}),[o,s]=S.useState([]),c=S.useRef(0),l=S.useRef(null);S.useEffect(()=>{l.current&&=(l.current(),null)},[o]);let u=S.useRef(!1),d=Zd(),f=S.useRef(null),p=S.useRef(null),m=S.useCallback(e=>{let{pulsate:t,rippleX:n,rippleY:i,rippleSize:a,cb:o}=e;s(e=>[...e,(0,B.jsx)(Af,{classes:{ripple:z(r.ripple,wf.ripple),rippleVisible:z(r.rippleVisible,wf.rippleVisible),ripplePulsate:z(r.ripplePulsate,wf.ripplePulsate),child:z(r.child,wf.child),childLeaving:z(r.childLeaving,wf.childLeaving),childPulsate:z(r.childPulsate,wf.childPulsate)},timeout:Tf,pulsate:t,rippleX:n,rippleY:i,rippleSize:a},c.current)]),c.current+=1,l.current=o},[r]),h=S.useCallback((e={},t={},r=()=>{})=>{let{pulsate:i=!1,center:a=n||t.pulsate,fakeElement:o=!1}=t;if(e?.type===`mousedown`&&u.current){u.current=!1;return}e?.type===`touchstart`&&(u.current=!0);let s=o?null:p.current,c=s?s.getBoundingClientRect():{width:0,height:0,left:0,top:0},l,h,g;if(a||e===void 0||e.clientX===0&&e.clientY===0||!e.clientX&&!e.touches)l=Math.round(c.width/2),h=Math.round(c.height/2);else{let{clientX:t,clientY:n}=e.touches&&e.touches.length>0?e.touches[0]:e;l=Math.round(t-c.left),h=Math.round(n-c.top)}if(a)g=Math.sqrt((2*c.width**2+c.height**2)/3),g%2==0&&(g+=1);else{let e=Math.max(Math.abs((s?s.clientWidth:0)-l),l)*2+2,t=Math.max(Math.abs((s?s.clientHeight:0)-h),h)*2+2;g=Math.sqrt(e**2+t**2)}e?.touches?f.current===null&&(f.current=()=>{m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},d.start(80,()=>{f.current&&=(f.current(),null)})):m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},[n,m,d]),g=S.useCallback(()=>{h({},{pulsate:!0})},[h]),_=S.useCallback((e,t)=>{if(d.clear(),e?.type===`touchend`&&f.current){f.current(),f.current=null,d.start(0,()=>{_(e,t)});return}f.current=null,s(e=>e.length>0?e.slice(1):e),l.current=t},[d]);return S.useImperativeHandle(t,()=>({pulsate:g,start:h,stop:_}),[g,h,_]),(0,B.jsx)(kf,{className:z(wf.root,r.root,i),ref:p,...a,children:(0,B.jsx)(Gd,{component:null,exit:!0,children:o})})});function Mf(e){return lc(`MuiButtonBase`,e)}var Nf=uc(`MuiButtonBase`,[`root`,`disabled`,`focusVisible`]),Pf=e=>{let{disabled:t,focusVisible:n,focusVisibleClassName:r,classes:i}=e,a=ml({root:[`root`,t&&`disabled`,n&&`focusVisible`]},Mf,i);return n&&r&&(a.root+=` ${r}`),a};const Ff=Y(`button`,{name:`MuiButtonBase`,slot:`Root`})({display:`inline-flex`,alignItems:`center`,justifyContent:`center`,position:`relative`,boxSizing:`border-box`,WebkitTapHighlightColor:`transparent`,backgroundColor:`transparent`,outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:`pointer`,userSelect:`none`,verticalAlign:`middle`,MozAppearance:`none`,WebkitAppearance:`none`,textDecoration:`none`,color:`inherit`,"&::-moz-focus-inner":{borderStyle:`none`},[`&.${Nf.disabled}`]:{pointerEvents:`none`,cursor:`default`},"@media print":{colorAdjust:`exact`}});var If=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiButtonBase`}),{action:r,centerRipple:i=!1,children:a,className:o,component:s=`button`,disabled:c=!1,disableRipple:l=!1,disableTouchRipple:u=!1,focusRipple:d=!1,focusVisibleClassName:f,LinkComponent:p=`a`,onBlur:m,onClick:h,onContextMenu:g,onDragLeave:_,onFocus:v,onFocusVisible:y,onKeyDown:b,onKeyUp:x,onMouseDown:C,onMouseLeave:w,onMouseUp:T,onTouchEnd:E,onTouchMove:ee,onTouchStart:D,tabIndex:O=0,TouchRippleProps:k,touchRippleRef:A,type:te,...ne}=n,re=S.useRef(null),j=bf(),M=bd(j.ref,A),[N,ie]=S.useState(!1);c&&N&&ie(!1),S.useImperativeHandle(r,()=>({focusVisible:()=>{ie(!0),re.current.focus()}}),[]);let ae=j.shouldMount&&!l&&!c;S.useEffect(()=>{N&&d&&!l&&j.pulsate()},[l,d,N,j]);let oe=Lf(j,`start`,C,u),se=Lf(j,`stop`,g,u),ce=Lf(j,`stop`,_,u),le=Lf(j,`stop`,T,u),ue=Lf(j,`stop`,e=>{N&&e.preventDefault(),w&&w(e)},u),de=Lf(j,`start`,D,u),fe=Lf(j,`stop`,E,u),pe=Lf(j,`stop`,ee,u),me=Lf(j,`stop`,e=>{vf(e.target)||ie(!1),m&&m(e)},!1),he=vd(e=>{re.current||=e.currentTarget,vf(e.target)&&(ie(!0),y&&y(e)),v&&v(e)}),ge=()=>{let e=re.current;return s&&s!==`button`&&!(e.tagName===`A`&&e.href)},_e=vd(e=>{d&&!e.repeat&&N&&e.key===` `&&j.stop(e,()=>{j.start(e)}),e.target===e.currentTarget&&ge()&&e.key===` `&&e.preventDefault(),b&&b(e),e.target===e.currentTarget&&ge()&&e.key===`Enter`&&!c&&(e.preventDefault(),h&&h(e))}),ve=vd(e=>{d&&e.key===` `&&N&&!e.defaultPrevented&&j.stop(e,()=>{j.pulsate(e)}),x&&x(e),h&&e.target===e.currentTarget&&ge()&&e.key===` `&&!e.defaultPrevented&&h(e)}),ye=s;ye===`button`&&(ne.href||ne.to)&&(ye=p);let be={};ye===`button`?(be.type=te===void 0?`button`:te,be.disabled=c):(!ne.href&&!ne.to&&(be.role=`button`),c&&(be[`aria-disabled`]=c));let xe=bd(t,re),Se={...n,centerRipple:i,component:s,disabled:c,disableRipple:l,disableTouchRipple:u,focusRipple:d,tabIndex:O,focusVisible:N},Ce=Pf(Se);return(0,B.jsxs)(Ff,{as:ye,className:z(Ce.root,o),ownerState:Se,onBlur:me,onClick:h,onContextMenu:se,onFocus:he,onKeyDown:_e,onKeyUp:ve,onMouseDown:oe,onMouseLeave:ue,o Line truncated
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`,qf=ro`
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: -126px;
}
`,Jf=typeof Kf==`string`?null:no`
animation: ${Kf} 1.4s linear infinite;
`,Yf=typeof qf==`string`?null:no`
animation: ${qf} 1.4s ease-in-out infinite;
`,Xf=e=>{let{classes:t,variant:n,color:r,disableShrink:i}=e;return ml({root:[`root`,n,`color${X(r)}`],svg:[`svg`],track:[`track`],circle:[`circle`,`circle${X(n)}`,i&&`circleDisableShrink`]},Wf,t)},Zf=Y(`span`,{name:`MuiCircularProgress`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,t[n.variant],t[`color${X(n.color)}`]]}})($u(({theme:e})=>({display:`inline-block`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`transform`)}},{props:{variant:`indeterminate`},style:Jf||{animation:`${Kf} 1.4s linear infinite`}},...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}}))]}))),Qf=Y(`svg`,{name:`MuiCircularProgress`,slot:`Svg`})({display:`block`}),$f=Y(`circle`,{name:`MuiCircularProgress`,slot:`Circle`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.circle,t[`circle${X(n.variant)}`],n.disableShrink&&t.circleDisableShrink]}})($u(({theme:e})=>({stroke:`currentColor`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`stroke-dashoffset`)}},{props:{variant:`indeterminate`},style:{strokeDasharray:`80px, 200px`,strokeDashoffset:0}},{props:({ownerState:e})=>e.variant===`indeterminate`&&!e.disableShrink,style:Yf||{animation:`${qf} 1.4s ease-in-out infinite`}}]}))),ep=Y(`circle`,{name:`MuiCircularProgress`,slot:`Track`})($u(({theme:e})=>({stroke:`currentColor`,opacity:(e.vars||e).palette.action.activatedOpacity}))),tp=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiCircularProgress`}),{className:r,color:i=`primary`,disableShrink:a=!1,enableTrackSlot:o=!1,size:s=40,style:c,thickness:l=3.6,value:u=0,variant:d=`indeterminate`,...f}=n,p={...n,color:i,disableShrink:a,size:s,thickness:l,value:u,variant:d,enableTrackSlot:o},m=Xf(p),h={},g={},_={};if(d===`determinate`){let e=2*Math.PI*((Gf-l)/2);h.strokeDasharray=e.toFixed(3),_[`aria-valuenow`]=Math.round(u),h.strokeDashoffset=`${((100-u)/100*e).toFixed(3)}px`,g.transform=`rotate(-90deg)`}return(0,B.jsx)(Zf,{className:z(m.root,r),style:{width:s,height:s,...g,...c},ownerState:p,ref:t,role:`progressbar`,..._,...f,children:(0,B.jsxs)(Qf,{className:m.svg,ownerState:p,viewBox:`${Gf/2} ${Gf/2} ${Gf} ${Gf}`,children:[o?(0,B.jsx)(ep,{className:m.track,ownerState:p,cx:Gf,cy:Gf,r:(Gf-l)/2,fill:`none`,strokeWidth:l,"aria-hidden":`true`}):null,(0,B.jsx)($f,{className:m.circle,style:h,ownerState:p,cx:Gf,cy:Gf,r:(Gf-l)/2,fill:`none`,strokeWidth:l})]})})});function np(e){return lc(`MuiIconButton`,e)}var rp=uc(`MuiIconButton`,[`root`,`disabled`,`colorInherit`,`colorPrimary`,`colorSecondary`,`colorError`,`colorInfo`,`colorSuccess`,`colorWarning`,`edgeStart`,`edgeEnd`,`sizeSmall`,`sizeMedium`,`sizeLarge`,`loading`,`loadingIndicator`,`loadingWrapper`]),ip=e=>{let{classes:t,disabled:n,color:r,edge:i,size:a,loading:o}=e;return ml({root:[`root`,o&&`loading`,n&&`disabled`,r!==`default`&&`color${X(r)}`,i&&`edge${X(i)}`,`size${X(a)}`],loadingIndicator:[`loadingIndicator`],loadingWrapper:[`loadingWrapper`]},np,t)},ap=Y(Rf,{name:`MuiIconButton`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.loading&&t.loading,n.color!==`default`&&t[`color${X(n.color)}`],n.edge&&t[`edge${X(n.edge)}`],t[`size${X(n.size)}`]]}})($u(({theme:e})=>({textAlign:`center`,flex:`0 0 auto`,fontSize:e.typography.pxToRem(24),padding:8,borderRadius:`50%`,color:(e.vars||e).palette.action.active,transition:e.transitions.create(`background-color`,{duration:e.transitions.duration.shortest}),variants:[{props:e=>!e.disableRipple,style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"&:hover":{backgroundColor:`var(--IconButton-hoverBg)`,"@media (hover: none)":{backgroundColor:`transparent`}}}},{props:{edge:`start`},style:{marginLeft:-12}},{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:{edge:`end`},style:{marginRight:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}}]})),$u(({theme:e})=>({variants:[{props:{color:`inherit`},style:{color:`inherit`}},...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}})),...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity)}})),{props:{size:`small`},style:{padding:5,fontSize:e.typography.pxToRem(18)}},{props:{size:`large`},style:{padding:12,fontSize:e.typography.pxToRem(28)}}],[`&.${rp.disabled}`]:{backgroundColor:`transparent`,color:(e.vars||e).palette.action.disabled},[`&.${rp.loading}`]:{color:`transparent`}}))),op=Y(`span`,{name:`MuiIconButton`,slot:`LoadingIndicator`})(({theme:e})=>({display:`none`,position:`absolute`,visibility:`visible`,top:`50%`,left:`50%`,transform:`translate(-50%, -50%)`,color:(e.vars||e).palette.action.disabled,variants:[{props:{loading:!0},style:{display:`flex`}}]})),sp=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiIconButtonLine truncated
-121
View File
@@ -1,121 +0,0 @@
const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/vitec_downloadcardcollection-OugWP7GR.js","assets/jszip.min-Df29-9GY.js","assets/vitec_datasheets-BiCTxfiq.js"])))=>i.map(i=>d[i]);
var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports),s=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},c=(n,r,a)=>(a=n==null?{}:e(i(n)),s(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n)),l=(e=>typeof require<`u`?require:typeof Proxy<`u`?new Proxy(e,{get:(e,t)=>(typeof require<`u`?require:e)[t]}):e)(function(e){if(typeof require<`u`)return require.apply(this,arguments);throw Error('Calling `require` for "'+e+"\" in an environment that doesn't expose the `require` function.")});(function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),e.crossOrigin===`use-credentials`?t.credentials=`include`:e.crossOrigin===`anonymous`?t.credentials=`omit`:t.credentials=`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var u=o((e=>{var t=Symbol.for(`react.transitional.element`),n=Symbol.for(`react.portal`),r=Symbol.for(`react.fragment`),i=Symbol.for(`react.strict_mode`),a=Symbol.for(`react.profiler`),o=Symbol.for(`react.consumer`),s=Symbol.for(`react.context`),c=Symbol.for(`react.forward_ref`),l=Symbol.for(`react.suspense`),u=Symbol.for(`react.memo`),d=Symbol.for(`react.lazy`),f=Symbol.for(`react.activity`),p=Symbol.iterator;function m(e){return typeof e!=`object`||!e?null:(e=p&&e[p]||e[`@@iterator`],typeof e==`function`?e:null)}var h={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},g=Object.assign,_={};function v(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}v.prototype.isReactComponent={},v.prototype.setState=function(e,t){if(typeof e!=`object`&&typeof e!=`function`&&e!=null)throw Error(`takes an object of state variables to update or a function which returns an object of state variables.`);this.updater.enqueueSetState(this,e,t,`setState`)},v.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,`forceUpdate`)};function y(){}y.prototype=v.prototype;function b(e,t,n){this.props=e,this.context=t,this.refs=_,this.updater=n||h}var x=b.prototype=new y;x.constructor=b,g(x,v.prototype),x.isPureReactComponent=!0;var S=Array.isArray;function C(){}var w={H:null,A:null,T:null,S:null},T=Object.prototype.hasOwnProperty;function E(e,n,r){var i=r.ref;return{$$typeof:t,type:e,key:n,ref:i===void 0?null:i,props:r}}function ee(e,t){return E(e.type,t,e.props)}function D(e){return typeof e==`object`&&!!e&&e.$$typeof===t}function O(e){var t={"=":`=0`,":":`=2`};return`$`+e.replace(/[=:]/g,function(e){return t[e]})}var k=/\/+/g;function A(e,t){return typeof e==`object`&&e&&e.key!=null?O(``+e.key):t.toString(36)}function te(e){switch(e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason;default:switch(typeof e.status==`string`?e.then(C,C):(e.status=`pending`,e.then(function(t){e.status===`pending`&&(e.status=`fulfilled`,e.value=t)},function(t){e.status===`pending`&&(e.status=`rejected`,e.reason=t)})),e.status){case`fulfilled`:return e.value;case`rejected`:throw e.reason}}throw e}function ne(e,r,i,a,o){var s=typeof e;(s===`undefined`||s===`boolean`)&&(e=null);var c=!1;if(e===null)c=!0;else switch(s){case`bigint`:case`string`:case`number`:c=!0;break;case`object`:switch(e.$$typeof){case t:case n:c=!0;break;case d:return c=e._init,ne(c(e._payload),r,i,a,o)}}if(c)return o=o(e),c=a===``?`.`+A(e,0):a,S(o)?(i=``,c!=null&&(i=c.replace(k,`$&/`)+`/`),ne(o,r,i,``,function(e){return e})):o!=null&&(D(o)&&(o=ee(o,i+(o.key==null||e&&e.key===o.key?``:(``+o.key).replace(k,`$&/`)+`/`)+c)),r.push(o)),1;c=0;var l=a===``?`.`:a+`:`;if(S(e))for(var u=0;u<e.length;u++)a=e[u],s=l+A(a,u),c+=ne(a,r,i,s,o);else if(u=m(e),typeof u==`function`)for(e=u.call(e),u=0;!(a=e.next()).done;)a=a.value,s=l+A(a,u++),c+=ne(a,r,i,s,o);else if(s===`object`){if(typeof e.then==`function`)return ne(te(e),r,i,a,o);throw r=String(e),Error(`Objects are not valid as a React child (found: `+(r===`[object Object]`?`object with keys {`+Object.keys(e).join(`, `)+`}`:r)+`). If you meant to render a collection of children, use an array instead.`)}return c}function re(e,t,n){if(e==null)return e;var r=[],i=0;return ne(e,r,``,``,function(e){return t.call(n,e,i++)}),r}function j(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(t){(e._status===0||e._status===-1)&&(eLine truncated
at`)?` (<anonymous>)`:-1<e.stack.indexOf(`@`)?`@unknown:0:0`:``}return`
`+ve+e+ye}var xe=!1;function Se(e,t){if(!e||xe)return``;xe=!0;var n=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{var r={DetermineComponentFrameRoot:function(){try{if(t){var n=function(){throw Error()};if(Object.defineProperty(n.prototype,`props`,{set:function(){throw Error()}}),typeof Reflect==`object`&&Reflect.construct){try{Reflect.construct(n,[])}catch(e){var r=e}Reflect.construct(e,[],n)}else{try{n.call()}catch(e){r=e}e.call(n.prototype)}}else{try{throw Error()}catch(e){r=e}(n=e())&&typeof n.catch==`function`&&n.catch(function(){})}}catch(e){if(e&&r&&typeof e.stack==`string`)return[e.stack,r.stack]}return[null,null]}};r.DetermineComponentFrameRoot.displayName=`DetermineComponentFrameRoot`;var i=Object.getOwnPropertyDescriptor(r.DetermineComponentFrameRoot,`name`);i&&i.configurable&&Object.defineProperty(r.DetermineComponentFrameRoot,`name`,{value:`DetermineComponentFrameRoot`});var a=r.DetermineComponentFrameRoot(),o=a[0],s=a[1];if(o&&s){var c=o.split(`
`),l=s.split(`
`);for(i=r=0;r<c.length&&!c[r].includes(`DetermineComponentFrameRoot`);)r++;for(;i<l.length&&!l[i].includes(`DetermineComponentFrameRoot`);)i++;if(r===c.length||i===l.length)for(r=c.length-1,i=l.length-1;1<=r&&0<=i&&c[r]!==l[i];)i--;for(;1<=r&&0<=i;r--,i--)if(c[r]!==l[i]){if(r!==1||i!==1)do if(r--,i--,0>i||c[r]!==l[i]){var u=`
`+c[r].replace(` at new `,` at `);return e.displayName&&u.includes(`<anonymous>`)&&(u=u.replace(`<anonymous>`,e.displayName)),u}while(1<=r&&0<=i);break}}}finally{xe=!1,Error.prepareStackTrace=n}return(n=e?e.displayName||e.name:``)?be(n):``}function Ce(e,t){switch(e.tag){case 26:case 27:case 5:return be(e.type);case 16:return be(`Lazy`);case 13:return e.child!==t&&t!==null?be(`Suspense Fallback`):be(`Suspense`);case 19:return be(`SuspenseList`);case 0:case 15:return Se(e.type,!1);case 11:return Se(e.type.render,!1);case 1:return Se(e.type,!0);case 31:return be(`Activity`);default:return``}}function we(e){try{var t=``,n=null;do t+=Ce(e,n),n=e,e=e.return;while(e);return t}catch(e){return`
Error generating stack: `+e.message+`
`+e.stack}}var Te=Object.prototype.hasOwnProperty,Ee=t.unstable_scheduleCallback,De=t.unstable_cancelCallback,Oe=t.unstable_shouldYield,ke=t.unstable_requestPaint,Ae=t.unstable_now,je=t.unstable_getCurrentPriorityLevel,Me=t.unstable_ImmediatePriority,Ne=t.unstable_UserBlockingPriority,Pe=t.unstable_NormalPriority,Fe=t.unstable_LowPriority,Ie=t.unstable_IdlePriority,Le=t.log,Re=t.unstable_setDisableYieldValue,ze=null,Be=null;function Ve(e){if(typeof Le==`function`&&Re(e),Be&&typeof Be.setStrictMode==`function`)try{Be.setStrictMode(ze,e)}catch{}}var He=Math.clz32?Math.clz32:Ge,Ue=Math.log,We=Math.LN2;function Ge(e){return e>>>=0,e===0?32:31-(Ue(e)/We|0)|0}var Ke=256,qe=262144,Je=4194304;function Ye(e){var t=e&42;if(t!==0)return t;switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:return 64;case 128:return 128;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:return e&261888;case 262144:case 524288:case 1048576:case 2097152:return e&3932160;case 4194304:case 8388608:case 16777216:case 33554432:return e&62914560;case 67108864:return 67108864;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 0;default:return e}}function Xe(e,t,n){var r=e.pendingLanes;if(r===0)return 0;var i=0,a=e.suspendedLanes,o=e.pingedLanes;e=e.warmLanes;var s=r&134217727;return s===0?(s=r&~a,s===0?o===0?n||(n=r&~e,n!==0&&(i=Ye(n))):i=Ye(o):i=Ye(s)):(r=s&~a,r===0?(o&=s,o===0?n||(n=s&~e,n!==0&&(i=Ye(n))):i=Ye(o)):i=Ye(r)),i===0?0:t!==0&&t!==i&&(t&a)===0&&(a=i&-i,n=t&-t,a>=n||a===32&&n&4194048)?t:i}function Ze(e,t){return(e.pendingLanes&~(e.suspendedLanes&~e.pingedLanes)&t)===0}function Qe(e,t){switch(e){case 1:case 2:case 4:case 8:case 64:return t+250;case 16:case 32:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return t+5e3;case 4194304:case 8388608:case 16777216:case 33554432:return-1;case 67108864:case 134217728:case 268435456:case 536870912:case 1073741824:return-1;default:return-1}}function $e(){var e=Je;return Je<<=1,!(Je&62914560)&&(Je=4194304),e}function et(e){for(var t=[],n=0;31>n;n++)t.push(e);return t}function tt(e,t){e.pendingLanes|=t,t!==268435456&&(e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0)}function nt(e,t,n,r,i,a){var o=e.pendingLanes;e.pendingLanes=n,e.suspendedLanes=0,e.pingedLanes=0,e.warmLanes=0,e.expiredLanes&=n,e.entangledLanes&=n,e.errorRecoveryDisabledLanes&=n,e.shellSuspendCounter=0;var s=e.entanglements,c=e.expirationTimes,l=e.hiddenUpdates;for(n=o&~n;0<n;){var u=31-He(n),d=1<<u;s[u]=0,c[u]=-1;var f=l[u];if(f!==null)for(l[u]=null,u=0;u<f.length;u++){var p=f[u];p!==null&&(p.lane&=-536870913)}n&=~d}r!==0&&rt(e,r,0),a!==0&&i===0&&e.tag!==0&&(e.suspendedLanes|=a&~(o&~t))}function rt(e,t,n){e.pendingLanes|=t,e.suspendedLanes&=~t;var r=31-He(t);e.entangledLanes|=t,e.entanglements[r]=e.entanglements[r]|1073741824|n&261930}function it(e,t){var n=e.entangledLanes|=t;for(e=e.entanglements;n;){var r=31-He(n),i=1<<r;i&t|e[r]&t&&(e[r]|=t),n&=~i}}function at(e,t){var n=t&-t;return n=n&42?1:ot(n),(n&(e.suspendedLanes|t))===0?n:0}function ot(e){switch(e){case 2:e=1;break;case 8:e=4;break;case 32:e=16;break;case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:case 4194304:case 8388608:case 16777216:case 33554432:e=128;break;case 268435456:e=134217728;break;default:e=0}return e}function st(e){return e&=-e,2<e?8<e?e&134217727?32:268435456:8:2}function ct(){var e=N.p;return e===0?(e=window.event,e===void 0?32:hp(e.type)):e}function lt(e,t){var n=N.p;try{return N.p=e,t()}finally{N.p=n}}var ut=Math.random().toString(36).slice(2),dt=`__reactFiber$`+ut,ft=`__reactProps$`+ut,pt=`__reactContainer$`+ut,mt=`__reactEvents$`+ut,ht=`__reactListeners$`+ut,gt=`__reactHandles$`+ut,_t=`__reactResources$`+ut,vt=`__reactMarker$`+ut;function yt(e){delete e[dt],delete e[ft],delete e[mt],delete e[ht],delete e[gt]}function bt(e){var t=e[dt];if(t)return t;for(var n=e.parentNode;n;){if(t=n[pt]||n[dt]){if(n=t.alternate,t.child!==null||n!==null&&n.child!==null)for(e=ff(e);e!==null;){if(n=e[dt])return n;e=ff(e)}return t}e=n,n=e.parentNode}return null}function xt(e){if(e=e[dt]||e[pt]){var t=e.tag;if(t===5||t===6||t===13||t===31||t===26||t===27||t===3)return e}return null}function St(e){var t=e.tag;if(t===5||t===26||t===27||t===6)return e.stateNode;throw Error(i(33))}function Ct(e){var t=e[_t];return t||=e[_t]={hoistableStyles:new Map,hoistableScripts:new Map},t}function wt(e){e[vt]=!0}var Tt=new Set,Et={};function Dt(e,t){Ot(e,t),Ot(e+`Capture`,t)}function Ot(e,t){for(Et[e]=t,e=0;e<t.length;e++)Tt.add(t[e])}var kt=RegExp(`^[:A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001Line truncated
`).replace(jd,``)}function Nd(e,t){return t=Md(t),Md(e)===t}function Q(e,t,n,r,a,o){switch(n){case`children`:typeof r==`string`?t===`body`||t===`textarea`&&r===``||Xt(e,r):(typeof r==`number`||typeof r==`bigint`)&&t!==`body`&&Xt(e,``+r);break;case`className`:Pt(e,`class`,r);break;case`tabIndex`:Pt(e,`tabindex`,r);break;case`dir`:case`role`:case`viewBox`:case`width`:case`height`:Pt(e,n,r);break;case`style`:$t(e,r,o);break;case`data`:if(t!==`object`){Pt(e,`data`,r);break}case`src`:case`href`:if(r===``&&(t!==`a`||n!==`href`)){e.removeAttribute(n);break}if(r==null||typeof r==`function`||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=rn(``+r),e.setAttribute(n,r);break;case`action`:case`formAction`:if(typeof r==`function`){e.setAttribute(n,`javascript:throw new Error('A React form was unexpectedly submitted. If you called form.submit() manually, consider using form.requestSubmit() instead. If you\\'re trying to use event.stopPropagation() in a submit event handler, consider also calling event.preventDefault().')`);break}else typeof o==`function`&&(n===`formAction`?(t!==`input`&&Q(e,t,`name`,a.name,a,null),Q(e,t,`formEncType`,a.formEncType,a,null),Q(e,t,`formMethod`,a.formMethod,a,null),Q(e,t,`formTarget`,a.formTarget,a,null)):(Q(e,t,`encType`,a.encType,a,null),Q(e,t,`method`,a.method,a,null),Q(e,t,`target`,a.target,a,null)));if(r==null||typeof r==`symbol`||typeof r==`boolean`){e.removeAttribute(n);break}r=rn(``+r),e.setAttribute(n,r);break;case`onClick`:r!=null&&(e.onclick=an);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`multiple`:e.multiple=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`muted`:e.muted=r&&typeof r!=`function`&&typeof r!=`symbol`;break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`defaultValue`:case`defaultChecked`:case`innerHTML`:case`ref`:break;case`autoFocus`:break;case`xlinkHref`:if(r==null||typeof r==`function`||typeof r==`boolean`||typeof r==`symbol`){e.removeAttribute(`xlink:href`);break}n=rn(``+r),e.setAttributeNS(`http://www.w3.org/1999/xlink`,`xlink:href`,n);break;case`contentEditable`:case`spellCheck`:case`draggable`:case`value`:case`autoReverse`:case`externalResourcesRequired`:case`focusable`:case`preserveAlpha`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``+r):e.removeAttribute(n);break;case`inert`:case`allowFullScreen`:case`async`:case`autoPlay`:case`controls`:case`default`:case`defer`:case`disabled`:case`disablePictureInPicture`:case`disableRemotePlayback`:case`formNoValidate`:case`hidden`:case`loop`:case`noModule`:case`noValidate`:case`open`:case`playsInline`:case`readOnly`:case`required`:case`reversed`:case`scoped`:case`seamless`:case`itemScope`:r&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,``):e.removeAttribute(n);break;case`capture`:case`download`:!0===r?e.setAttribute(n,``):!1!==r&&r!=null&&typeof r!=`function`&&typeof r!=`symbol`?e.setAttribute(n,r):e.removeAttribute(n);break;case`cols`:case`rows`:case`size`:case`span`:r!=null&&typeof r!=`function`&&typeof r!=`symbol`&&!isNaN(r)&&1<=r?e.setAttribute(n,r):e.removeAttribute(n);break;case`rowSpan`:case`start`:r==null||typeof r==`function`||typeof r==`symbol`||isNaN(r)?e.removeAttribute(n):e.setAttribute(n,r);break;case`popover`:Z(`beforetoggle`,e),Z(`toggle`,e),Nt(e,`popover`,r);break;case`xlinkActuate`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:actuate`,r);break;case`xlinkArcrole`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:arcrole`,r);break;case`xlinkRole`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:role`,r);break;case`xlinkShow`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:show`,r);break;case`xlinkTitle`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:title`,r);break;case`xlinkType`:Ft(e,`http://www.w3.org/1999/xlink`,`xlink:type`,r);break;case`xmlBase`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:base`,r);break;case`xmlLang`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:lang`,r);break;case`xmlSpace`:Ft(e,`http://www.w3.org/XML/1998/namespace`,`xml:space`,r);break;case`is`:Nt(e,`is`,r);break;case`innerText`:case`textContent`:break;default:(!(2<n.length)||n[0]!==`o`&&n[0]!==`O`||n[1]!==`n`&&n[1]!==`N`)&&(n=tn.get(n)||n,Nt(e,n,r))}}function Pd(e,t,n,r,a,o){switch(n){case`style`:$t(e,r,o);break;case`dangerouslySetInnerHTML`:if(r!=null){if(typeof r!=`object`||!(`__html`in r))throw Error(i(61));if(n=r.__html,n!=null){if(a.children!=null)throw Error(i(60));e.innerHTML=n}}break;case`children`:typeof r==`string`?Xt(e,r):(typeof r==`number`||typeof r==`bigint`)&&Xt(e,``+r);break;case`onScroll`:r!=null&&Z(`scroll`,e);break;case`onScrollEnd`:r!=null&&Z(`scrollend`,e);break;case`onClick`:r!=null&&(e.onclick=an);break;case`suppressContentEditableWarning`:case`suppressHydrationWarning`:case`innerHTML`:case`re Line truncated
Please change the parent <Route path="${e}"> to <Route path="${e===`/`?`*`:`${e}/*`}">.`)}let u=un(),d;if(t){let e=typeof t==`string`?rt(t):t;Ze(c===`/`||e.pathname?.startsWith(c),`When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, the location pathname must begin with the portion of the URL pathname that was matched by all parent routes. The current pathname base is "${c}" but pathname "${e.pathname}" was given in the \`location\` prop.`),d=e}else d=u;let f=d.pathname||`/`,p=f;if(c!==`/`){let e=c.replace(/^\//,``).split(`/`);p=`/`+f.replace(/^\//,``).split(`/`).slice(e.length).join(`/`)}let m=n&&n.state.matches.length?n.state.matches.map(e=>Object.assign(e,{route:n.manifest[e.route.id]||e.route})):ot(e,{pathname:p});Qe(l||m!=null,`No routes matched location "${d.pathname}${d.search}${d.hash}" `),Qe(m==null||m[m.length-1].route.element!==void 0||m[m.length-1].route.Component!==void 0||m[m.length-1].route.lazy!==void 0,`Matched leaf route at location "${d.pathname}${d.search}${d.hash}" does not have an element or Component. This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`);let h=Cn(m&&m.map(e=>Object.assign({},e,{params:Object.assign({},o,e.params),pathname:Ft([c,r.encodeLocation?r.encodeLocation(e.pathname.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathname]),pathnameBase:e.pathnameBase===`/`?c:Ft([c,r.encodeLocation?r.encodeLocation(e.pathnameBase.replace(/%/g,`%25`).replace(/\?/g,`%3F`).replace(/#/g,`%23`)).pathname:e.pathnameBase])})),i,n);return t&&h?S.createElement($t.Provider,{value:{location:{pathname:`/`,search:``,hash:``,state:null,key:`default`,mask:void 0,...d},navigationType:`POP`}},h):h}function _n(){let e=Mn(),t=Vt(e)?`${e.status} ${e.statusText}`:e instanceof Error?e.message:JSON.stringify(e),n=e instanceof Error?e.stack:null,r=`rgba(200,200,200, 0.5)`,i={padding:`0.5rem`,backgroundColor:r},a={padding:`2px 4px`,backgroundColor:r},o=null;return console.error(`Error handled by React Router default ErrorBoundary:`,e),o=S.createElement(S.Fragment,null,S.createElement(`p`,null,`💿 Hey developer 👋`),S.createElement(`p`,null,`You can provide a way better UX than this when your app throws errors by providing your own `,S.createElement(`code`,{style:a},`ErrorBoundary`),` or`,` `,S.createElement(`code`,{style:a},`errorElement`),` prop on your route.`)),S.createElement(S.Fragment,null,S.createElement(`h2`,null,`Unexpected Application Error!`),S.createElement(`h3`,{style:{fontStyle:`italic`}},t),n?S.createElement(`pre`,{style:i},n):null,o)}var vn=S.createElement(_n,null),yn=class extends S.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||t.revalidation!==`idle`&&e.revalidation===`idle`?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:e.error===void 0?t.error:e.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){this.props.onError?this.props.onError(e,t):console.error(`React Router caught the following error during render`,e)}render(){let e=this.state.error;if(this.context&&typeof e==`object`&&e&&`digest`in e&&typeof e.digest==`string`){let t=sn(e.digest);t&&(e=t)}let t=e===void 0?this.props.children:S.createElement(en.Provider,{value:this.props.routeContext},S.createElement(tn.Provider,{value:e,children:this.props.component}));return this.context?S.createElement(xn,{error:e},t):t}};yn.contextType=qt;var bn=new WeakMap;function xn({children:e,error:t}){let{basename:n}=S.useContext(Qt);if(typeof t==`object`&&t&&`digest`in t&&typeof t.digest==`string`){let e=on(t.digest);if(e){let r=bn.get(t);if(r)throw r;let i=Wt(e.location,n);if(Ut&&!bn.get(t))if(i.isExternal||e.reloadDocument)window.location.href=i.absoluteURL||i.to;else{let n=Promise.resolve().then(()=>window.__reactRouterDataRouter.navigate(i.to,{replace:e.replace}));throw bn.set(t,n),n}return S.createElement(`meta`,{httpEquiv:`refresh`,content:`0;url=${i.absoluteURL||i.to}`})}}return e}function Sn({routeContext:e,match:t,children:n}){let r=S.useContext(Gt);return r&&r.static&&r.staticContext&&(t.route.errorElement||t.route.ErrorBoundary)&&(r.staticContext._deepestRenderedBoundaryId=t.route.id),S.createElement(en.Provider,{value:e},n)}function Cn(e,t=[],n){let r=n?.state;if(e==null){if(!r)return null;if(r.errors)e=r.matches;else if(t.length===0&&!r.initialized&&r.matches.length>0)e=r.matches;else return null}let i=e,a=r?.errors;if(a!=null){let e=i.findIndex(e=>e.route.id&&a?.[e.route.id]!==void 0);Ze(e>=0,`Could not find a matching route for errors on route IDs: ${Object.keys(a).join(`,`)}`),i=i.slice(0,Math.min(i.length,e+1))}let o=!1,s=-1;if(n&&r){o=r.renderFallback;for(let e=0;e<i.length;e++){let t=i[e];if((t.route.HydrateFallback||t.route.hydrateFallbackElement)&&(s=e),t.route.id){lLine truncated
`)),(kc===void 0?Oc:Ac)(l,a,o,s,c)}}jc();function Mc(e,t=-(2**53-1),n=2**53-1){return Math.max(t,Math.min(e,n))}var Nc=Mc;function Pc(e,t=0,n=1){return Nc(e,t,n)}function Fc(e){e=e.slice(1);let t=RegExp(`.{1,${e.length>=6?2:1}}`,`g`),n=e.match(t);return n&&n[0].length===1&&(n=n.map(e=>e+e)),n?`rgb${n.length===4?`a`:``}(${n.map((e,t)=>t<3?parseInt(e,16):Math.round(parseInt(e,16)/255*1e3)/1e3).join(`, `)})`:``}function Ic(e){if(e.type)return e;if(e.charAt(0)===`#`)return Ic(Fc(e));let t=e.indexOf(`(`),n=e.substring(0,t);if(![`rgb`,`rgba`,`hsl`,`hsla`,`color`].includes(n))throw Error(si(9,e));let r=e.substring(t+1,e.length-1),i;if(n===`color`){if(r=r.split(` `),i=r.shift(),r.length===4&&r[3].charAt(0)===`/`&&(r[3]=r[3].slice(1)),![`srgb`,`display-p3`,`a98-rgb`,`prophoto-rgb`,`rec-2020`].includes(i))throw Error(si(10,i))}else r=r.split(`,`);return r=r.map(e=>parseFloat(e)),{type:n,values:r,colorSpace:i}}const Lc=e=>{let t=Ic(e);return t.values.slice(0,3).map((e,n)=>t.type.includes(`hsl`)&&n!==0?`${e}%`:e).join(` `)},Rc=(e,t)=>{try{return Lc(e)}catch{return e}};function zc(e){let{type:t,colorSpace:n}=e,{values:r}=e;return t.includes(`rgb`)?r=r.map((e,t)=>t<3?parseInt(e,10):e):t.includes(`hsl`)&&(r[1]=`${r[1]}%`,r[2]=`${r[2]}%`),r=t.includes(`color`)?`${n} ${r.join(` `)}`:`${r.join(`, `)}`,`${t}(${r})`}function Bc(e){e=Ic(e);let{values:t}=e,n=t[0],r=t[1]/100,i=t[2]/100,a=r*Math.min(i,1-i),o=(e,t=(e+n/30)%12)=>i-a*Math.max(Math.min(t-3,9-t,1),-1),s=`rgb`,c=[Math.round(o(0)*255),Math.round(o(8)*255),Math.round(o(4)*255)];return e.type===`hsla`&&(s+=`a`,c.push(t[3])),zc({type:s,values:c})}function Vc(e){e=Ic(e);let t=e.type===`hsl`||e.type===`hsla`?Ic(Bc(e)).values:e.values;return t=t.map(t=>(e.type!==`color`&&(t/=255),t<=.03928?t/12.92:((t+.055)/1.055)**2.4)),Number((.2126*t[0]+.7152*t[1]+.0722*t[2]).toFixed(3))}function Hc(e,t){let n=Vc(e),r=Vc(t);return(Math.max(n,r)+.05)/(Math.min(n,r)+.05)}function Uc(e,t){return e=Ic(e),t=Pc(t),(e.type===`rgb`||e.type===`hsl`)&&(e.type+=`a`),e.type===`color`?e.values[3]=`/${t}`:e.values[3]=t,zc(e)}function Wc(e,t,n){try{return Uc(e,t)}catch{return e}}function Gc(e,t){if(e=Ic(e),t=Pc(t),e.type.includes(`hsl`))e.values[2]*=1-t;else if(e.type.includes(`rgb`)||e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]*=1-t;return zc(e)}function V(e,t,n){try{return Gc(e,t)}catch{return e}}function Kc(e,t){if(e=Ic(e),t=Pc(t),e.type.includes(`hsl`))e.values[2]+=(100-e.values[2])*t;else if(e.type.includes(`rgb`))for(let n=0;n<3;n+=1)e.values[n]+=(255-e.values[n])*t;else if(e.type.includes(`color`))for(let n=0;n<3;n+=1)e.values[n]+=(1-e.values[n])*t;return zc(e)}function H(e,t,n){try{return Kc(e,t)}catch{return e}}function qc(e,t=.15){return Vc(e)>.5?Gc(e,t):Kc(e,t)}function Jc(e,t,n){try{return qc(e,t)}catch{return e}}var Yc=S.createContext();const Xc=()=>S.useContext(Yc)??!1;var Zc=S.createContext(void 0);function Qc(e){let{theme:t,name:n,props:r}=e;if(!t||!t.components||!t.components[n])return r;let i=t.components[n];return i.defaultProps?wc(i.defaultProps,r,t.components.mergeClassNameAndStyle):!i.styleOverrides&&!i.variants?wc(i,r,t.components.mergeClassNameAndStyle):r}function $c({props:e,name:t}){return Qc({props:e,name:t,theme:{components:S.useContext(Zc)}})}var el=0;function tl(e){let[t,n]=S.useState(e),r=e||t;return S.useEffect(()=>{t??(el+=1,n(`mui-${el}`))},[t]),r}var nl={...S}.useId;function rl(e){if(nl!==void 0){let t=nl();return e??t}return tl(e)}var il={theme:void 0};function al(e){let t,n;return function(r){let i=t;return(i===void 0||r.theme!==n)&&(il.theme=r.theme,i=dc(e(il)),t=i,n=r.theme),i}}function ol(e=``){function t(...n){if(!n.length)return``;let r=n[0];return typeof r==`string`&&!r.match(/(#|\(|\)|(-?(\d*\.)?\d+)(px|em|%|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc))|^(-?(\d*\.)?\d+)$|(\d+ \d+ \d+)/)?`, var(--${e?`${e}-`:``}${r}${t(...n.slice(1))})`:`, ${r}`}return(n,...r)=>`var(--${e?`${e}-`:``}${n}${t(...r)})`}const sl=(e,t,n,r=[])=>{let i=e;t.forEach((e,a)=>{a===t.length-1?Array.isArray(i)?i[Number(e)]=n:i&&typeof i==`object`&&(i[e]=n):i&&typeof i==`object`&&(i[e]||(i[e]=r.includes(e)?[]:{}),i=i[e])})},cl=(e,t,n)=>{function r(e,i=[],a=[]){Object.entries(e).forEach(([e,o])=>{(!n||n&&!n([...i,e]))&&o!=null&&(typeof o==`object`&&Object.keys(o).length>0?r(o,[...i,e],Array.isArray(o)?[...a,e]:a):t([...i,e],o,a))})}r(e)};var ll=(e,t)=>typeof t==`number`?[`lineHeight`,`fontWeight`,`opacity`,`zIndex`].some(t=>e.includes(t))||e[e.length-1].toLowerCase().includes(`opacity`)?t:`${t}px`:t;function ul(e,t){let{prefix:n,shouldSkipGeneratingVar:r}=t||{},i={},a={},o={};return cl(e,(e,t,s)=>{if((typeof t==`string`||typeof t==`number`)&&(!r||!r(e,t))){let r=`--${n?`${n}-`:``}${e.join(`-`)}`,c=ll(e,t);Object.assign(i,{[r]:c}),sl(a,e,`var(${r})`,s),sl(o,e,`var(${r}, ${c})`,s)}},e=>e[0]===`vars`),{css:i,vars:a,varsWithDefaults:o}}function dl(e,t={}){let{getSelector:n=_,disableCssColorScheme:r,colorSchemeSelector:i,enableContrastVars:a}=t,{colorSchemes:o={},components:s,defaultColorS Line truncated
const theme = ${JSON.stringify(t,null,2)};
theme.breakpoints = createBreakpoints(theme.breakpoints || {});
theme.transitions = createTransitions(theme.transitions || {});
export default theme;`}function wu(e){return typeof e==`number`?`${(e*100).toFixed(0)}%`:`calc((${e}) * 100%)`}var Tu=e=>{if(!Number.isNaN(+e))return+e;let t=e.match(/\d*\.?\d+/g);if(!t)return 0;let n=0;for(let e=0;e<t.length;e+=1)n+=+t[e];return n};function Eu(e){Object.assign(e,{alpha(t,n){let r=this||e;return r.colorSpace?`oklch(from ${t} l c h / ${typeof n==`string`?`calc(${n})`:n})`:r.vars?`rgba(${t.replace(/var\(--([^,\s)]+)(?:,[^)]+)?\)+/g,`var(--$1Channel)`)} / ${typeof n==`string`?`calc(${n})`:n})`:Uc(t,Tu(n))},lighten(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #fff ${wu(n)})`:Kc(t,n)},darken(t,n){let r=this||e;return r.colorSpace?`color-mix(in ${r.colorSpace}, ${t}, #000 ${wu(n)})`:Gc(t,n)}})}function Du(e={},...t){let{breakpoints:n,mixins:r={},spacing:i,palette:a={},transitions:o={},typography:s={},shape:c,colorSpace:l,...u}=e;if(e.vars&&e.generateThemeVars===void 0)throw Error(si(20));let d=iu({...a,colorSpace:l}),f=Js(e),p=wo(f,{mixins:ou(f.breakpoints,r),palette:d,shadows:hu.slice(),typography:uu(d,s),transitions:bu(o),zIndex:{...xu}});return p=wo(p,u),p=t.reduce((e,t)=>wo(e,t),p),p.unstable_sxConfig={...Bs,...u?.unstable_sxConfig},p.unstable_sx=function(e){return Gs({sx:e,theme:this})},p.toRuntimeSource=Cu,Eu(p),p}var Ou=Du;function ku(e){let t;return t=e<1?5.11916*e**2:4.5*Math.log(e+1)+2,Math.round(t*10)/1e3}var Au=[...Array(25)].map((e,t)=>{if(t===0)return`none`;let n=ku(t);return`linear-gradient(rgba(255 255 255 / ${n}), rgba(255 255 255 / ${n}))`});function ju(e){return{inputPlaceholder:e===`dark`?.5:.42,inputUnderline:e===`dark`?.7:.42,switchTrackDisabled:e===`dark`?.2:.12,switchTrack:e===`dark`?.3:.38}}function Mu(e){return e===`dark`?Au:[]}function Nu(e){let{palette:t={mode:`light`},opacity:n,overlays:r,colorSpace:i,...a}=e,o=iu({...t,colorSpace:i});return{palette:o,opacity:{...ju(o.mode),...n},overlays:r||Mu(o.mode),...a}}function Pu(e){return!!e[0].match(/(cssVarPrefix|colorSchemeSelector|modularCssLayers|rootSelector|typography|mixins|breakpoints|direction|transitions)/)||!!e[0].match(/sxConfig$/)||e[0]===`palette`&&!!e[1]?.match(/(mode|contrastThreshold|tonalOffset)/)}var Fu=e=>[...[...Array(25)].map((t,n)=>`--${e?`${e}-`:``}overlays-${n}`),`--${e?`${e}-`:``}palette-AppBar-darkBg`,`--${e?`${e}-`:``}palette-AppBar-darkColor`],Iu=e=>(t,n)=>{let r=e.rootSelector||`:root`,i=e.colorSchemeSelector,a=i;if(i===`class`&&(a=`.%s`),i===`data`&&(a=`[data-%s]`),i?.startsWith(`data-`)&&!i.includes(`%s`)&&(a=`[${i}="%s"]`),e.defaultColorScheme===t){if(t===`dark`){let i={};return Fu(e.cssVarPrefix).forEach(e=>{i[e]=n[e],delete n[e]}),a===`media`?{[r]:n,"@media (prefers-color-scheme: dark)":{[r]:i}}:a?{[a.replace(`%s`,t)]:i,[`${r}, ${a.replace(`%s`,t)}`]:n}:{[r]:{...n,...i}}}if(a&&a!==`media`)return`${r}, ${a.replace(`%s`,String(t))}`}else if(t){if(a===`media`)return{[`@media (prefers-color-scheme: ${String(t)})`]:{[r]:n}};if(a)return a.replace(`%s`,String(t))}return r};function Lu(e,t){t.forEach(t=>{e[t]||(e[t]={})})}function q(e,t,n){!e[t]&&n&&(e[t]=n)}function Ru(e){return typeof e!=`string`||!e.startsWith(`hsl`)?e:Bc(e)}function zu(e,t){`${t}Channel`in e||(e[`${t}Channel`]=Rc(Ru(e[t]),`MUI: Can't create \`palette.${t}Channel\` because \`palette.${t}\` is not one of these formats: #nnn, #nnnnnn, rgb(), rgba(), hsl(), hsla(), color().
To suppress this warning, you need to explicitly provide the \`palette.${t}Channel\` as a string (in rgb format, for example "12 12 12") or undefined if you want to remove the channel token.`))}function Bu(e){return typeof e==`number`?`${e}px`:typeof e==`string`||typeof e==`function`||Array.isArray(e)?e:`8px`}var Vu=e=>{try{return e()}catch{}};const Hu=(e=`mui`)=>ol(e);function Uu(e,t,n,r,i){if(!n)return;n=n===!0?{}:n;let a=i===`dark`?`dark`:`light`;if(!r){t[i]=Nu({...n,palette:{mode:a,...n?.palette},colorSpace:e});return}let{palette:o,...s}=Ou({...r,palette:{mode:a,...n?.palette},colorSpace:e});return t[i]={...n,palette:o,opacity:{...ju(a),...n?.opacity},overlays:n?.overlays||Mu(a)},s}function Wu(e={},...t){let{colorSchemes:n={light:!0},defaultColorScheme:r,disableCssColorScheme:i=!1,cssVarPrefix:a=`mui`,nativeColor:o=!1,shouldSkipGeneratingVar:s=Pu,colorSchemeSelector:c=n.light&&n.dark?`media`:void 0,rootSelector:l=`:root`,...u}=e,d=Object.keys(n)[0],f=r||(n.light&&d!==`light`?`light`:d),p=Hu(a),{[f]:m,light:h,dark:g,..._}=n,v={..._},y=m;if((f===`dark`&&!(`dark`in n)||f===`light`&&!(`light`in n))&&(y=!0),!y)throw Error(si(21,f));let b;o&&(b=`oklch`);let x=Uu(b,v,y,u,f);h&&!v.light&&Uu(b,v,h,void 0,`light`),g&&!v.dark&&Uu(b,v,g,void 0,`dark`);let S={defaultColorScheme:f,...x,cssVarPrefix:a,colorSchemeSelector:c,rootSelector:l,getCssVar:p,colorSchemes:v,font:{...au(x.typography),...x.font},spacing:Bu(u.spacing)};Object.keys(S.colorSchemes).forEach(e=>{let t=S.colorSchemes[e].palette,n=e=>{let n=e.split(`-`),r=n[1],i=n[2];return p(e,t[r][i])};t.mode===`light`&&(q(t.common,`background`,`#fff`),q(t.common,`onBackground`,`#000`)),t.mode===`dark`&&(q(t.common,`background`,`#000`),q(t.common,`onBackground`,`#fff`));function r(e,t,n){if(b){let r;return e===Wc&&(r=`transparent ${((1-n)*100).toFixed(0)}%`),e===V&&(r=`#000 ${(n*100).toFixed(0)}%`),e===H&&(r=`#fff ${(n*100).toFixed(0)}%`),`color-mix(in ${b}, ${t}, ${r})`}return e(t,n)}if(Lu(t,[`Alert`,`AppBar`,`Avatar`,`Button`,`Chip`,`FilledInput`,`LinearProgress`,`Skeleton`,`Slider`,`SnackbarContent`,`SpeedDialAction`,`StepConnector`,`StepContent`,`Switch`,`TableCell`,`Tooltip`]),t.mode===`light`){q(t.Alert,`errorColor`,r(V,t.error.light,.6)),q(t.Alert,`infoColor`,r(V,t.info.light,.6)),q(t.Alert,`successColor`,r(V,t.success.light,.6)),q(t.Alert,`warningColor`,r(V,t.warning.light,.6)),q(t.Alert,`errorFilledBg`,n(`palette-error-main`)),q(t.Alert,`infoFilledBg`,n(`palette-info-main`)),q(t.Alert,`successFilledBg`,n(`palette-success-main`)),q(t.Alert,`warningFilledBg`,n(`palette-warning-main`)),q(t.Alert,`errorFilledColor`,Vu(()=>t.getContrastText(t.error.main))),q(t.Alert,`infoFilledColor`,Vu(()=>t.getContrastText(t.info.main))),q(t.Alert,`successFilledColor`,Vu(()=>t.getContrastText(t.success.main))),q(t.Alert,`warningFilledColor`,Vu(()=>t.getContrastText(t.warning.main))),q(t.Alert,`errorStandardBg`,r(H,t.error.light,.9)),q(t.Alert,`infoStandardBg`,r(H,t.info.light,.9)),q(t.Alert,`successStandardBg`,r(H,t.success.light,.9)),q(t.Alert,`warningStandardBg`,r(H,t.warning.light,.9)),q(t.Alert,`errorIconColor`,n(`palette-error-main`)),q(t.Alert,`infoIconColor`,n(`palette-info-main`)),q(t.Alert,`successIconColor`,n(`palette-success-main`)),q(t.Alert,`warningIconColor`,n(`palette-warning-main`)),q(t.AppBar,`defaultBg`,n(`palette-grey-100`)),q(t.Avatar,`defaultBg`,n(`palette-grey-400`)),q(t.Button,`inheritContainedBg`,n(`palette-grey-300`)),q(t.Button,`inheritContainedHoverBg`,n(`palette-grey-A100`)),q(t.Chip,`defaultBorder`,n(`palette-grey-400`)),q(t.Chip,`defaultAvatarColor`,n(`palette-grey-700`)),q(t.Chip,`defaultIconColor`,n(`palette-grey-700`)),q(t.FilledInput,`bg`,`rgba(0, 0, 0, 0.06)`),q(t.FilledInput,`hoverBg`,`rgba(0, 0, 0, 0.09)`),q(t.FilledInput,`disabledBg`,`rgba(0, 0, 0, 0.12)`),q(t.LinearProgress,`primaryBg`,r(H,t.primary.main,.62)),q(t.LinearProgress,`secondaryBg`,r(H,t.secondary.main,.62)),q(t.LinearProgress,`errorBg`,r(H,t.error.main,.62)),q(t.LinearProgress,`infoBg`,r(H,t.info.main,.62)),q(t.LinearProgress,`successBg`,r(H,t.success.main,.62)),q(t.LinearProgress,`warningBg`,r(H,t.warning.main,.62)),q(t.Skeleton,`bg`,b?r(Wc,t.text.primary,.11):`rgba(${n(`palette-text-primaryChannel`)} / 0.11)`),q(t.Slider,`primaryTrack`,r(H,t.primary.main,.62)),q(t.Slider,`secondaryTrack`,r(H,t.secondary.main,.62)),q(t.Slider,`errorTrack`,r(H,t.error.main,.62)),q(t.Slider,`infoTrack`,r(H,t.info.main,.62)),q(t.Slider,`successTrack`,r(H,t.success.main,.62)),q(t.Slider,`warningTrack`,r(H,t.warning.main,.62));let e=b?r(V,t.background.default,.6825):Jc(t.background.default,.8);q(t.SnackbarContent,`bg`,e),q(t.SnackbarContent,`color`,Vu(()=>b?Jl.text.primary:t.getContrastText(e))),q(t.SpeedDialAction,`fabHoverBg`,Jc(t.background.paper,.15)),q(t.StepConnector,`border`,n(`palette-grey-400`)),q(t.StepContent,`border`,n(`palette-grey-400`)),q(t.Switch,`defaultColor`,n(`palette-common-white`)),q(t.Switch,`defaultDisabledColor`,n(`palette-grey-100`)),q(t.Switch,`primaryDisabledColor`,r(H,tLine truncated
0% {
transform: scale(0);
opacity: 0.1;
}
100% {
transform: scale(1);
opacity: 0.3;
}
`,Df=ro`
0% {
opacity: 1;
}
100% {
opacity: 0;
}
`,Of=ro`
0% {
transform: scale(1);
}
50% {
transform: scale(0.92);
}
100% {
transform: scale(1);
}
`;const kf=Y(`span`,{name:`MuiTouchRipple`,slot:`Root`})({overflow:`hidden`,pointerEvents:`none`,position:`absolute`,zIndex:0,top:0,right:0,bottom:0,left:0,borderRadius:`inherit`}),Af=Y(Cf,{name:`MuiTouchRipple`,slot:`Ripple`})`
opacity: 0;
position: absolute;
&.${wf.rippleVisible} {
opacity: 0.3;
transform: scale(1);
animation-name: ${Ef};
animation-duration: ${Tf}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
&.${wf.ripplePulsate} {
animation-duration: ${({theme:e})=>e.transitions.duration.shorter}ms;
}
& .${wf.child} {
opacity: 1;
display: block;
width: 100%;
height: 100%;
border-radius: 50%;
background-color: currentColor;
}
& .${wf.childLeaving} {
opacity: 0;
animation-name: ${Df};
animation-duration: ${Tf}ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
}
& .${wf.childPulsate} {
position: absolute;
/* @noflip */
left: 0px;
top: 0;
animation-name: ${Of};
animation-duration: 2500ms;
animation-timing-function: ${({theme:e})=>e.transitions.easing.easeInOut};
animation-iteration-count: infinite;
animation-delay: 200ms;
}
`;var jf=S.forwardRef(function(e,t){let{center:n=!1,classes:r={},className:i,...a}=ed({props:e,name:`MuiTouchRipple`}),[o,s]=S.useState([]),c=S.useRef(0),l=S.useRef(null);S.useEffect(()=>{l.current&&=(l.current(),null)},[o]);let u=S.useRef(!1),d=Zd(),f=S.useRef(null),p=S.useRef(null),m=S.useCallback(e=>{let{pulsate:t,rippleX:n,rippleY:i,rippleSize:a,cb:o}=e;s(e=>[...e,(0,B.jsx)(Af,{classes:{ripple:z(r.ripple,wf.ripple),rippleVisible:z(r.rippleVisible,wf.rippleVisible),ripplePulsate:z(r.ripplePulsate,wf.ripplePulsate),child:z(r.child,wf.child),childLeaving:z(r.childLeaving,wf.childLeaving),childPulsate:z(r.childPulsate,wf.childPulsate)},timeout:Tf,pulsate:t,rippleX:n,rippleY:i,rippleSize:a},c.current)]),c.current+=1,l.current=o},[r]),h=S.useCallback((e={},t={},r=()=>{})=>{let{pulsate:i=!1,center:a=n||t.pulsate,fakeElement:o=!1}=t;if(e?.type===`mousedown`&&u.current){u.current=!1;return}e?.type===`touchstart`&&(u.current=!0);let s=o?null:p.current,c=s?s.getBoundingClientRect():{width:0,height:0,left:0,top:0},l,h,g;if(a||e===void 0||e.clientX===0&&e.clientY===0||!e.clientX&&!e.touches)l=Math.round(c.width/2),h=Math.round(c.height/2);else{let{clientX:t,clientY:n}=e.touches&&e.touches.length>0?e.touches[0]:e;l=Math.round(t-c.left),h=Math.round(n-c.top)}if(a)g=Math.sqrt((2*c.width**2+c.height**2)/3),g%2==0&&(g+=1);else{let e=Math.max(Math.abs((s?s.clientWidth:0)-l),l)*2+2,t=Math.max(Math.abs((s?s.clientHeight:0)-h),h)*2+2;g=Math.sqrt(e**2+t**2)}e?.touches?f.current===null&&(f.current=()=>{m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},d.start(80,()=>{f.current&&=(f.current(),null)})):m({pulsate:i,rippleX:l,rippleY:h,rippleSize:g,cb:r})},[n,m,d]),g=S.useCallback(()=>{h({},{pulsate:!0})},[h]),_=S.useCallback((e,t)=>{if(d.clear(),e?.type===`touchend`&&f.current){f.current(),f.current=null,d.start(0,()=>{_(e,t)});return}f.current=null,s(e=>e.length>0?e.slice(1):e),l.current=t},[d]);return S.useImperativeHandle(t,()=>({pulsate:g,start:h,stop:_}),[g,h,_]),(0,B.jsx)(kf,{className:z(wf.root,r.root,i),ref:p,...a,children:(0,B.jsx)(Gd,{component:null,exit:!0,children:o})})});function Mf(e){return lc(`MuiButtonBase`,e)}var Nf=uc(`MuiButtonBase`,[`root`,`disabled`,`focusVisible`]),Pf=e=>{let{disabled:t,focusVisible:n,focusVisibleClassName:r,classes:i}=e,a=ml({root:[`root`,t&&`disabled`,n&&`focusVisible`]},Mf,i);return n&&r&&(a.root+=` ${r}`),a};const Ff=Y(`button`,{name:`MuiButtonBase`,slot:`Root`})({display:`inline-flex`,alignItems:`center`,justifyContent:`center`,position:`relative`,boxSizing:`border-box`,WebkitTapHighlightColor:`transparent`,backgroundColor:`transparent`,outline:0,border:0,margin:0,borderRadius:0,padding:0,cursor:`pointer`,userSelect:`none`,verticalAlign:`middle`,MozAppearance:`none`,WebkitAppearance:`none`,textDecoration:`none`,color:`inherit`,"&::-moz-focus-inner":{borderStyle:`none`},[`&.${Nf.disabled}`]:{pointerEvents:`none`,cursor:`default`},"@media print":{colorAdjust:`exact`}});var If=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiButtonBase`}),{action:r,centerRipple:i=!1,children:a,className:o,component:s=`button`,disabled:c=!1,disableRipple:l=!1,disableTouchRipple:u=!1,focusRipple:d=!1,focusVisibleClassName:f,LinkComponent:p=`a`,onBlur:m,onClick:h,onContextMenu:g,onDragLeave:_,onFocus:v,onFocusVisible:y,onKeyDown:b,onKeyUp:x,onMouseDown:C,onMouseLeave:w,onMouseUp:T,onTouchEnd:E,onTouchMove:ee,onTouchStart:D,tabIndex:O=0,TouchRippleProps:k,touchRippleRef:A,type:te,...ne}=n,re=S.useRef(null),j=bf(),M=bd(j.ref,A),[N,ie]=S.useState(!1);c&&N&&ie(!1),S.useImperativeHandle(r,()=>({focusVisible:()=>{ie(!0),re.current.focus()}}),[]);let ae=j.shouldMount&&!l&&!c;S.useEffect(()=>{N&&d&&!l&&j.pulsate()},[l,d,N,j]);let oe=Lf(j,`start`,C,u),se=Lf(j,`stop`,g,u),ce=Lf(j,`stop`,_,u),le=Lf(j,`stop`,T,u),ue=Lf(j,`stop`,e=>{N&&e.preventDefault(),w&&w(e)},u),de=Lf(j,`start`,D,u),fe=Lf(j,`stop`,E,u),pe=Lf(j,`stop`,ee,u),me=Lf(j,`stop`,e=>{vf(e.target)||ie(!1),m&&m(e)},!1),he=vd(e=>{re.current||=e.currentTarget,vf(e.target)&&(ie(!0),y&&y(e)),v&&v(e)}),ge=()=>{let e=re.current;return s&&s!==`button`&&!(e.tagName===`A`&&e.href)},_e=vd(e=>{d&&!e.repeat&&N&&e.key===` `&&j.stop(e,()=>{j.start(e)}),e.target===e.currentTarget&&ge()&&e.key===` `&&e.preventDefault(),b&&b(e),e.target===e.currentTarget&&ge()&&e.key===`Enter`&&!c&&(e.preventDefault(),h&&h(e))}),ve=vd(e=>{d&&e.key===` `&&N&&!e.defaultPrevented&&j.stop(e,()=>{j.pulsate(e)}),x&&x(e),h&&e.target===e.currentTarget&&ge()&&e.key===` `&&!e.defaultPrevented&&h(e)}),ye=s;ye===`button`&&(ne.href||ne.to)&&(ye=p);let be={};ye===`button`?(be.type=te===void 0?`button`:te,be.disabled=c):(!ne.href&&!ne.to&&(be.role=`button`),c&&(be[`aria-disabled`]=c));let xe=bd(t,re),Se={...n,centerRipple:i,component:s,disabled:c,disableRipple:l,disableTouchRipple:u,focusRipple:d,tabIndex:O,focusVisible:N},Ce=Pf(Se);return(0,B.jsxs)(Ff,{as:ye,className:z(Ce.root,o),ownerState:Se,onBlur:me,onClick:h,onContextMenu:se,onFocus:he,onKeyDown:_e,onKeyUp:ve,onMouseDown:oe,onMouseLeave:ue,o Line truncated
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
`,qf=ro`
0% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 100px, 200px;
stroke-dashoffset: -15px;
}
100% {
stroke-dasharray: 1px, 200px;
stroke-dashoffset: -126px;
}
`,Jf=typeof Kf==`string`?null:no`
animation: ${Kf} 1.4s linear infinite;
`,Yf=typeof qf==`string`?null:no`
animation: ${qf} 1.4s ease-in-out infinite;
`,Xf=e=>{let{classes:t,variant:n,color:r,disableShrink:i}=e;return ml({root:[`root`,n,`color${X(r)}`],svg:[`svg`],track:[`track`],circle:[`circle`,`circle${X(n)}`,i&&`circleDisableShrink`]},Wf,t)},Zf=Y(`span`,{name:`MuiCircularProgress`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,t[n.variant],t[`color${X(n.color)}`]]}})($u(({theme:e})=>({display:`inline-block`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`transform`)}},{props:{variant:`indeterminate`},style:Jf||{animation:`${Kf} 1.4s linear infinite`}},...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}}))]}))),Qf=Y(`svg`,{name:`MuiCircularProgress`,slot:`Svg`})({display:`block`}),$f=Y(`circle`,{name:`MuiCircularProgress`,slot:`Circle`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.circle,t[`circle${X(n.variant)}`],n.disableShrink&&t.circleDisableShrink]}})($u(({theme:e})=>({stroke:`currentColor`,variants:[{props:{variant:`determinate`},style:{transition:e.transitions.create(`stroke-dashoffset`)}},{props:{variant:`indeterminate`},style:{strokeDasharray:`80px, 200px`,strokeDashoffset:0}},{props:({ownerState:e})=>e.variant===`indeterminate`&&!e.disableShrink,style:Yf||{animation:`${qf} 1.4s ease-in-out infinite`}}]}))),ep=Y(`circle`,{name:`MuiCircularProgress`,slot:`Track`})($u(({theme:e})=>({stroke:`currentColor`,opacity:(e.vars||e).palette.action.activatedOpacity}))),tp=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiCircularProgress`}),{className:r,color:i=`primary`,disableShrink:a=!1,enableTrackSlot:o=!1,size:s=40,style:c,thickness:l=3.6,value:u=0,variant:d=`indeterminate`,...f}=n,p={...n,color:i,disableShrink:a,size:s,thickness:l,value:u,variant:d,enableTrackSlot:o},m=Xf(p),h={},g={},_={};if(d===`determinate`){let e=2*Math.PI*((Gf-l)/2);h.strokeDasharray=e.toFixed(3),_[`aria-valuenow`]=Math.round(u),h.strokeDashoffset=`${((100-u)/100*e).toFixed(3)}px`,g.transform=`rotate(-90deg)`}return(0,B.jsx)(Zf,{className:z(m.root,r),style:{width:s,height:s,...g,...c},ownerState:p,ref:t,role:`progressbar`,..._,...f,children:(0,B.jsxs)(Qf,{className:m.svg,ownerState:p,viewBox:`${Gf/2} ${Gf/2} ${Gf} ${Gf}`,children:[o?(0,B.jsx)(ep,{className:m.track,ownerState:p,cx:Gf,cy:Gf,r:(Gf-l)/2,fill:`none`,strokeWidth:l,"aria-hidden":`true`}):null,(0,B.jsx)($f,{className:m.circle,style:h,ownerState:p,cx:Gf,cy:Gf,r:(Gf-l)/2,fill:`none`,strokeWidth:l})]})})});function np(e){return lc(`MuiIconButton`,e)}var rp=uc(`MuiIconButton`,[`root`,`disabled`,`colorInherit`,`colorPrimary`,`colorSecondary`,`colorError`,`colorInfo`,`colorSuccess`,`colorWarning`,`edgeStart`,`edgeEnd`,`sizeSmall`,`sizeMedium`,`sizeLarge`,`loading`,`loadingIndicator`,`loadingWrapper`]),ip=e=>{let{classes:t,disabled:n,color:r,edge:i,size:a,loading:o}=e;return ml({root:[`root`,o&&`loading`,n&&`disabled`,r!==`default`&&`color${X(r)}`,i&&`edge${X(i)}`,`size${X(a)}`],loadingIndicator:[`loadingIndicator`],loadingWrapper:[`loadingWrapper`]},np,t)},ap=Y(Rf,{name:`MuiIconButton`,slot:`Root`,overridesResolver:(e,t)=>{let{ownerState:n}=e;return[t.root,n.loading&&t.loading,n.color!==`default`&&t[`color${X(n.color)}`],n.edge&&t[`edge${X(n.edge)}`],t[`size${X(n.size)}`]]}})($u(({theme:e})=>({textAlign:`center`,flex:`0 0 auto`,fontSize:e.typography.pxToRem(24),padding:8,borderRadius:`50%`,color:(e.vars||e).palette.action.active,transition:e.transitions.create(`background-color`,{duration:e.transitions.duration.shortest}),variants:[{props:e=>!e.disableRipple,style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette.action.active,(e.vars||e).palette.action.hoverOpacity),"&:hover":{backgroundColor:`var(--IconButton-hoverBg)`,"@media (hover: none)":{backgroundColor:`transparent`}}}},{props:{edge:`start`},style:{marginLeft:-12}},{props:{edge:`start`,size:`small`},style:{marginLeft:-3}},{props:{edge:`end`},style:{marginRight:-12}},{props:{edge:`end`,size:`small`},style:{marginRight:-3}}]})),$u(({theme:e})=>({variants:[{props:{color:`inherit`},style:{color:`inherit`}},...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{color:(e.vars||e).palette[t].main}})),...Object.entries(e.palette).filter(Vf()).map(([t])=>({props:{color:t},style:{"--IconButton-hoverBg":e.alpha((e.vars||e).palette[t].main,(e.vars||e).palette.action.hoverOpacity)}})),{props:{size:`small`},style:{padding:5,fontSize:e.typography.pxToRem(18)}},{props:{size:`large`},style:{padding:12,fontSize:e.typography.pxToRem(28)}}],[`&.${rp.disabled}`]:{backgroundColor:`transparent`,color:(e.vars||e).palette.action.disabled},[`&.${rp.loading}`]:{color:`transparent`}}))),op=Y(`span`,{name:`MuiIconButton`,slot:`LoadingIndicator`})(({theme:e})=>({display:`none`,position:`absolute`,visibility:`visible`,top:`50%`,left:`50%`,transform:`translate(-50%, -50%)`,color:(e.vars||e).palette.action.disabled,variants:[{props:{loading:!0},style:{display:`flex`}}]})),sp=S.forwardRef(function(e,t){let n=ed({props:e,name:`MuiIconButtonLine truncated
Loaded 100 of 197 files, more files were not shown because too many files have changed in this diff. Show more