diff --git a/components/ILIAS/GlobalScreen/tests/Notification/BaseNotificationSetUpTBD.php b/components/ILIAS/GlobalScreen/tests/Notification/BaseNotificationSetUpTBD.php index 60dd4840441c..66952c147051 100644 --- a/components/ILIAS/GlobalScreen/tests/Notification/BaseNotificationSetUpTBD.php +++ b/components/ILIAS/GlobalScreen/tests/Notification/BaseNotificationSetUpTBD.php @@ -26,7 +26,6 @@ use ILIAS\GlobalScreen\Services; use ILIAS\GlobalScreen\Provider\ProviderFactory; use ILIAS\UI\Component as C; - use PHPUnit\Framework\TestCase; use ILIAS\UI\Implementation\Component as I; use ILIAS\UI\Implementation\Component\Counter\Factory; @@ -79,7 +78,15 @@ protected function setUp(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $language_mock = $this->createMock(\ILIAS\Language\Language::class); + $language_mock->method('txt')->willReturnArgument(0); + + $factory = new class ($language_mock) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function item(): ILIAS\UI\Component\Item\Factory { return new I\Item\Factory(); @@ -88,7 +95,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } @@ -101,7 +108,7 @@ public function mainControls(): C\MainControls\Factory new Factory(), new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ) ) diff --git a/components/ILIAS/Init/classes/Dependencies/InitUIFramework.php b/components/ILIAS/Init/classes/Dependencies/InitUIFramework.php index b7e4b73a8b44..9ecb49e8462e 100755 --- a/components/ILIAS/Init/classes/Dependencies/InitUIFramework.php +++ b/components/ILIAS/Init/classes/Dependencies/InitUIFramework.php @@ -206,7 +206,9 @@ public function getRefreshIntervalInMs(): int ); }; $c["ui.factory.symbol.glyph"] = function ($c) { - return new ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory(); + return new ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory( + $c["lng"], + ); }; $c["ui.factory.symbol.icon"] = function ($c) { return new ILIAS\UI\Implementation\Component\Symbol\Icon\Factory(); diff --git a/components/ILIAS/LearningSequence/tests/IliasMocks.php b/components/ILIAS/LearningSequence/tests/IliasMocks.php index d0be460f7208..03242f333129 100755 --- a/components/ILIAS/LearningSequence/tests/IliasMocks.php +++ b/components/ILIAS/LearningSequence/tests/IliasMocks.php @@ -1,7 +1,5 @@ $m->getName(), + fn($m) => $m->getName(), $ui_reflection->getMethods() ); @@ -57,10 +57,12 @@ protected function mockUIFactory(): UIFactory ); $ui_factory->method('link') ->willReturn(new CImpl\Link\Factory()); + $language_mock = $this->createMock(\ILIAS\Language\Language::class); + $language_mock->method('txt')->willReturnArgument(0); $ui_factory->method('symbol') ->willReturn(new CImpl\Symbol\Factory( new CImpl\Symbol\Icon\Factory(), - new CImpl\Symbol\Glyph\Factory(), + new CImpl\Symbol\Glyph\Factory($language_mock), new CImpl\Symbol\Avatar\Factory() )); diff --git a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php index 5271489510c3..a8503d24fa90 100755 --- a/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php +++ b/components/ILIAS/Test/tests/Scoring/Settings/ScoreSettingsTest.php @@ -227,12 +227,20 @@ public function testScoreSettingsSectionScoring(): void public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + $language_mock = $this->createMock(\ILIAS\Language\Language::class); + $language_mock->method('txt')->willReturnArgument(0); + + return new class ($language_mock) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function symbol(): C\Symbol\Factory { return new S\Factory( new S\Icon\Factory(), - new S\Glyph\Factory(), + new S\Glyph\Factory($this->language), new S\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/resources/ui-examples/images/Image/mountains_widescreen-thumbnail.jpg b/components/ILIAS/UI/resources/ui-examples/images/Image/mountains_widescreen-thumbnail.jpg new file mode 100644 index 000000000000..c62994e2ddab Binary files /dev/null and b/components/ILIAS/UI/resources/ui-examples/images/Image/mountains_widescreen-thumbnail.jpg differ diff --git a/components/ILIAS/UI/resources/ui-examples/images/Image/sanfrancisco_widescreen-thumbnail.jpg b/components/ILIAS/UI/resources/ui-examples/images/Image/sanfrancisco_widescreen-thumbnail.jpg new file mode 100644 index 000000000000..06bb7428bbc2 Binary files /dev/null and b/components/ILIAS/UI/resources/ui-examples/images/Image/sanfrancisco_widescreen-thumbnail.jpg differ diff --git a/components/ILIAS/UI/resources/ui-examples/images/Image/ski_widescreen-thumbnail.jpg b/components/ILIAS/UI/resources/ui-examples/images/Image/ski_widescreen-thumbnail.jpg new file mode 100644 index 000000000000..8f31cce2aa87 Binary files /dev/null and b/components/ILIAS/UI/resources/ui-examples/images/Image/ski_widescreen-thumbnail.jpg differ diff --git a/components/ILIAS/UI/src/Component/Entity/Entity.php b/components/ILIAS/UI/src/Component/Entity/Entity.php index 12e7a7dc58a4..96688e24baef 100755 --- a/components/ILIAS/UI/src/Component/Entity/Entity.php +++ b/components/ILIAS/UI/src/Component/Entity/Entity.php @@ -21,14 +21,14 @@ namespace ILIAS\UI\Component\Entity; use ILIAS\UI\Component\Component; -use ILIAS\UI\Component\Image\Image; -use ILIAS\UI\Component\Symbol\Symbol; use ILIAS\UI\Component\Symbol\Glyph\Glyph; +use ILIAS\UI\Component\Button\Standard as StandardButton; use ILIAS\UI\Component\Button\Shy; use ILIAS\UI\Component\Button\Tag; use ILIAS\UI\Component\Legacy\Legacy; use ILIAS\UI\Component\Listing\Property as PropertyListing; use ILIAS\UI\Component\Link\Standard as StandardLink; +use ILIAS\UI\Component\Listing\Workflow; /** * This describes an Entity @@ -70,15 +70,14 @@ public function withMainDetails( * Another way of distinguishing Reactions might be the availability/significance * for everybody in contrast to the current user (e.g. rating vs. my favorite) */ - public function withPrioritizedReactions(Glyph | Tag ...$prio_reactions): self; - + public function withPrioritizedReactions(Glyph | Tag | StandardButton | Shy ...$prio_reactions): self; //Further Areas /** * Reactions that are less prominent than Prioritized Reactions go here. */ - public function withReactions(Glyph | Tag ...$reactions): self; + public function withReactions(Glyph | Tag | Shy | StandardButton ...$reactions): self; /** * Properties that could potentially limit a users access to the object @@ -98,8 +97,13 @@ public function withDetails( ): self; /** - * Actions are the things you can actually _do_ with the entity, - * e.g. in context of repository items: view, copy, delete, etc. + * ManagingActions are the things an owner or admin can actually do _with_ the entity, + * e.g. in context of repository items: view, copy, delete, set online etc. + */ + public function withManagingActions(Shy ...$managing_actions): static; + + /** + * @deprecated for semantic reasons, "actions" is not precise enough. */ public function withActions(Shy ...$actions): self; @@ -111,4 +115,11 @@ public function withActions(Shy ...$actions): self; public function withPersonalStatus( PropertyListing | Legacy ...$personal_status ): self; + + /** + * This Workflow is used to create buttons on the entity. + * Only Workflow Steps which are AVAILABLE and either NOT_STARTED or IN_PROGRESS + * will be rendered as buttons. + */ + public function withWorkflow(Workflow\Linear $workflow): static; } diff --git a/components/ILIAS/UI/src/Component/Listing/Entity/Factory.php b/components/ILIAS/UI/src/Component/Listing/Entity/Factory.php index 86f9db97115c..ce6f9930ebfa 100755 --- a/components/ILIAS/UI/src/Component/Listing/Entity/Factory.php +++ b/components/ILIAS/UI/src/Component/Listing/Entity/Factory.php @@ -29,11 +29,36 @@ interface Factory * --- * description: * purpose: > - * The Entity Listing yields uniform Entities according to a consumer - * defined concept and lists them one after the other. + * The Entity Listing yields uniform Entities according to a consumer + * defined concept and lists them one after the other. + * composition: > + * Entities are stacked one after the other. On very large screens the layout will have multiple columns to use + * the space optimally. The design of the entity is one that favors a more horizontal representation. * * --- + * @param \ILIAS\UI\Component\Listing\Entity\RecordToEntity $entity_mapping * @return \ILIAS\UI\Component\Listing\Entity\Standard */ public function standard(RecordToEntity $entity_mapping): Standard; + + /** + * --- + * description: + * purpose: > + * The Entity Listing yields uniform Entities according to a consumer + * defined concept and lists them in a grid. + * composition: + * Shows a grid of many entities in a card-style design. Images, Symbols and other secondary identifiers are + * stacked to favor a vertical representation. + * rules: + * usage: + * 1: > + * If you want all entity secondary identifier images to take on the same height, you must provide images + * with the same height. + * + * --- + * @param \ILIAS\UI\Component\Listing\Entity\RecordToEntity $entity_mapping + * @return \ILIAS\UI\Component\Listing\Entity\Grid + */ + public function grid(RecordToEntity $entity_mapping): Grid; } diff --git a/components/ILIAS/UI/src/Component/Listing/Entity/Grid.php b/components/ILIAS/UI/src/Component/Listing/Entity/Grid.php new file mode 100644 index 000000000000..b9e060d93529 --- /dev/null +++ b/components/ILIAS/UI/src/Component/Listing/Entity/Grid.php @@ -0,0 +1,25 @@ + + * Inline Lists are used to display a set of elements next to each other when the available + * space allows for it. The elements belong to a group of similar items and have about equal + * relevance. + * composition: > + * Inline Lists string up the items horizontally breaking into the next line if necessary. + * They are separated by a comma. + * rivals: + * Unordered List, Ordered Listing: > + * If there is enough space for a vertical list, Unordered and Ordered Listing should + * be preferred. Line by line items are better suited when the user is expected to be + * exploring or engaging with the list for longer than a casual glance. + * Property Listing: > + * To display key-value pairs in a row, use the Property Listing. + * + * context: + * - Inline Listings can be used as values in a Property Listing. + * + * rules: + * usage: + * - You MUST use the Inline Listing only when another component around it gives it a + * context or headline clarifying what is being listed. + * - You MUST only add items belonging to the same group or type. + * - The Inline Listing MAY be the value of a property listing item. + * - You MUST NOT use this component as a layout tool to force unrelated components next + * to each other. + * - You MAY change the comma delimiter in your component using CSS. + * ---- + * @param array $items + * @return \ILIAS\UI\Component\Listing\Inline + */ + public function inline(array $items): Inline; + /** * --- * description: @@ -160,16 +199,38 @@ public function entity(): Entity\Factory; * Entries are listed as label/value pair in one line. * Since the focus is strongly on the value, which might be * self-explaining, visibility of the label is optional. - * The value is a string, or one or several Symbols, Links or Legacy Components. + * The label is a string. A Symbol may be shown in its place. + * The value is a string, Links or Legacy Components. + * A Symbol may be shown as the value. + * Very long value strings will turn into a truncated paragraph + * with a clickable Show more/less toggle. * rivals: * Characteristic Value: > - * In Charakteristic Values, label/value pairs are displayed in a + * In Characteristic Values, label/value pairs are displayed in a * tabular way; labels cannot be omitted for display. * Descriptive: > * The Descriptive's (visual) emphasis is on the key, not the value. + * * context: - * - Property Listing is used in Entities + * - Property Listing is used in Entities * + * rules: + * usage: + * - You MUST NOT use html code as a value string as it may get truncated in + * unexpected ways. + * - With more than 6 properties, you SHOULD use multiple Property Listing's + * to segment properties into multiple visual groups/lines. Each new + * property component starts a new line. + * - You SHOULD use properties with short values (e.g. not full paragraphs). + * You SHOULD split off long properties into their own Property component so + * it will always start a new line. + * - When using a Symbol as a label and/or value, the chosen icon + * MUST be self-explanatory and easily understood by users. + * - When using a Symbol as a label, it SHOULD not have an action. + * accessibility: + * - When using a Symbol, you still MUST enter a label with a text that can + * be understood when read through a screen reader independently of any + * visuals. This label is passed onto the Symbol as the aria-label. * ---- * @return \ILIAS\UI\Component\Listing\Property */ diff --git a/components/ILIAS/UI/src/Component/Listing/Inline.php b/components/ILIAS/UI/src/Component/Listing/Inline.php new file mode 100644 index 000000000000..a9e05e21d5cc --- /dev/null +++ b/components/ILIAS/UI/src/Component/Listing/Inline.php @@ -0,0 +1,24 @@ + + * @var array */ protected array $main_details = []; /** @@ -68,12 +68,14 @@ abstract class Entity implements I\Entity /** * @var Shy[] */ - protected array $actions = []; + protected array $managing_actions = []; /** * @var array */ protected array $personal_status = []; + protected ?Workflow\Linear $workflow = null; + public function __construct( protected Symbol | Image | Shy | StandardLink | string $primary_identifier, protected Symbol | Image | Shy | StandardLink | string $secondary_identifier @@ -159,12 +161,12 @@ public function getMainDetails(): array /** * @inheritdoc */ - public function withPrioritizedReactions(Glyph | Tag ...$prio_reactions): self + public function withPrioritizedReactions(Glyph | Tag | StandardButton | Shy ...$prio_reactions): self { $this->checkArgListElements( "Entity Prioritized Reactions", $prio_reactions, - [Glyph::class, Tag::class] + [Glyph::class, Tag::class, StandardButton::class, Shy::class] ); $clone = clone $this; $clone->prio_reactions = $prio_reactions; @@ -181,12 +183,12 @@ public function getPrioritizedReactions(): array /** * @inheritdoc */ - public function withReactions(Glyph | Tag ...$reactions): self + public function withReactions(Glyph | Tag | Shy | StandardButton ...$reactions): self { $this->checkArgListElements( "Entity Reactions", $reactions, - [Glyph::class, Tag::class] + [Glyph::class, Tag::class, Shy::class, StandardButton::class] ); $clone = clone $this; @@ -237,21 +239,27 @@ public function getDetails(): array return $this->details; } + public function withManagingActions(Shy ...$managing_actions): static + { + $clone = clone $this; + $clone->managing_actions = $managing_actions; + return $clone; + } + /** * @inheritdoc */ public function withActions(Shy ...$actions): self { - $clone = clone $this; - $clone->actions = $actions; - return $clone; + return $this->withManagingActions(...$actions); } + /** * @return Shy[] */ - public function getActions(): array + public function getManagingActions(): array { - return $this->actions; + return $this->managing_actions; } /** @@ -271,4 +279,16 @@ public function getPersonalStatus(): array { return $this->personal_status; } + + public function withWorkflow(Workflow\Linear $workflow): static + { + $clone = clone $this; + $clone->workflow = $workflow; + return $clone; + } + + public function getWorkflow(): ?Workflow\Linear + { + return $this->workflow; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Entity/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Entity/Renderer.php index 878cd345346a..3bb4ff664005 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Entity/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Entity/Renderer.php @@ -20,12 +20,9 @@ namespace ILIAS\UI\Implementation\Component\Entity; -//use ILIAS\UI\Component\JavaScriptBindable; use ILIAS\UI\Implementation\Render\AbstractComponentRenderer; use ILIAS\UI\Renderer as RendererInterface; use ILIAS\UI\Component; -use ILIAS\UI\Implementation\Render\ResourceRegistry; -use ILIAS\UI\Implementation\Render\Template; class Renderer extends AbstractComponentRenderer { @@ -34,7 +31,7 @@ class Renderer extends AbstractComponentRenderer */ public function render(Component\Component $component, RendererInterface $default_renderer): string { - if ($component instanceof Component\Entity\Entity) { + if ($component instanceof Entity) { return $this->renderEntity($component, $default_renderer); } $this->cannotHandleComponent($component); @@ -49,11 +46,11 @@ protected function renderEntity(Entity $component, RendererInterface $default_re $tpl->touchBlock('secondid_string'); } elseif ($secondary_identifier instanceof Component\Image\Image) { $tpl->touchBlock('secondid_image'); - } elseif ($secondary_identifier instanceof Component\Image\Symbol) { + } elseif ($secondary_identifier instanceof Component\Symbol\Symbol) { $tpl->touchBlock('secondid_symbol'); - } elseif ($secondary_identifier instanceof Component\Image\Link) { + } elseif ($secondary_identifier instanceof Component\Link\Link) { $tpl->touchBlock('secondid_link'); - } elseif ($secondary_identifier instanceof Component\Image\Shy) { + } elseif ($secondary_identifier instanceof Component\Button\Shy) { $tpl->touchBlock('secondid_shy'); } @@ -62,6 +59,7 @@ protected function renderEntity(Entity $component, RendererInterface $default_re $primary_identifier = $component->getPrimaryIdentifier(); $primary_identifier = is_string($primary_identifier) ? $primary_identifier : $this->maybeRender($default_renderer, $primary_identifier); $tpl->setVariable('PRIMARY_IDENTIFIER', $primary_identifier); + $tpl->setVariable('PRIMARY_IDENTIFIER_ID', $this->createId()); $tpl->setVariable('BLOCKING_CONDITIONS', $this->maybeRender($default_renderer, ...$component->getBlockingAvailabilityConditions())); $tpl->setVariable('FEATURES', $this->maybeRender($default_renderer, ...$component->getFeaturedProperties())); @@ -70,9 +68,13 @@ protected function renderEntity(Entity $component, RendererInterface $default_re $tpl->setVariable('AVAILABILITY', $this->maybeRender($default_renderer, ...$component->getAvailability())); $tpl->setVariable('DETAILS', $this->maybeRender($default_renderer, ...$component->getDetails())); - if ($actions = $component->getActions()) { + if (null !== $component->getWorkflow()) { + $button_components = $this->createUnfinishedWorkflowActions($component->getWorkflow()); + $tpl->setVariable('WORKFLOW_ACTIONS', $default_renderer->render($button_components)); + } + if ($actions = $component->getManagingActions()) { $actions_dropdown = $this->getUIFactory()->dropdown()->standard($actions); - $tpl->setVariable('ACTIONS', $default_renderer->render($actions_dropdown)); + $tpl->setVariable('MANAGING_ACTIONS', $default_renderer->render($actions_dropdown)); } if ($reactions = $component->getReactions()) { $tpl->setVariable('REACTIONS', $default_renderer->render($reactions)); @@ -93,4 +95,21 @@ protected function maybeRender(RendererInterface $default_renderer, Component\Co return $default_renderer->render($values); } + + /** @return Component\Button\Standard[] */ + protected function createUnfinishedWorkflowActions(Component\Listing\Workflow\Workflow $workflow): array + { + $actions = []; + foreach ($workflow->getSteps() as $step) { + if (null === $step->getAction() || + $step->getAvailability() !== Component\Listing\Workflow\Step::AVAILABLE || + ($step->getStatus() !== Component\Listing\Workflow\Step::NOT_STARTED && + $step->getStatus() !== Component\Listing\Workflow\Step::IN_PROGRESS) + ) { + continue; + } + $actions[] = $this->getUIFactory()->button()->standard($step->getLabel(), $step->getAction()); + } + return $actions; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Factory.php index 49008e8fbd4f..e8fe586bdceb 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Factory.php @@ -28,4 +28,9 @@ public function standard(I\RecordToEntity $mapping): Standard { return new Standard($mapping); } + + public function grid(I\RecordToEntity $mapping): Grid + { + return new Grid($mapping); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Grid.php b/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Grid.php new file mode 100644 index 000000000000..fcfb4b95da2c --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Listing/Entity/Grid.php @@ -0,0 +1,27 @@ +renderEntityListing($component, $default_renderer); + if ($component instanceof Standard) { + return $this->renderEntityListingStandard($component, $default_renderer); + } + if ($component instanceof Grid) { + return $this->renderEntityListingGrid($component, $default_renderer); } $this->cannotHandleComponent($component); } - protected function renderEntityListing(EntityListing $component, RendererInterface $default_renderer): string + protected function renderEntityListingStandard(EntityListing $component, RendererInterface $default_renderer): string { $tpl = $this->getTemplate('tpl.entitylisting.html', true, true); @@ -52,4 +55,17 @@ protected function renderEntityListing(EntityListing $component, RendererInterfa } return $tpl->get(); } + protected function renderEntityListingGrid(EntityListing $component, RendererInterface $default_renderer): string + { + $tpl = $this->getTemplate('tpl.entitylistinggrid.html', true, true); + + foreach ($component->getEntities( + $this->getUIFactory(), + ) as $entity) { + $tpl->setCurrentBlock('entry'); + $tpl->setVariable('ENTITY', $default_renderer->render($entity)); + $tpl->parseCurrentBlock(); + } + return $tpl->get(); + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Listing/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Listing/Factory.php index fc486f2a54bf..42e1b8d9ffa2 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Listing/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Listing/Factory.php @@ -52,6 +52,11 @@ public function descriptive(array $items): L\Descriptive return new Descriptive($items); } + public function inline(array $items): Inline + { + return new Inline($items); + } + /** * @inheritdoc */ diff --git a/components/ILIAS/UI/src/Implementation/Component/Listing/Inline.php b/components/ILIAS/UI/src/Implementation/Component/Listing/Inline.php new file mode 100644 index 000000000000..bf6b3ebc84fc --- /dev/null +++ b/components/ILIAS/UI/src/Implementation/Component/Listing/Inline.php @@ -0,0 +1,26 @@ +checkArgListElements("value", $value, self::ALLOWED_VALUE_TYPES); - } $clone = clone $this; $clone->items[] = [$label, $value, $show_label]; return $clone; diff --git a/components/ILIAS/UI/src/Implementation/Component/Listing/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Listing/Renderer.php index 1a2aced57f96..095a93b48c8b 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Listing/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Listing/Renderer.php @@ -21,8 +21,9 @@ namespace ILIAS\UI\Implementation\Component\Listing; use ILIAS\UI\Implementation\Render\AbstractComponentRenderer; +use ILIAS\UI\Implementation\Render\Template; use ILIAS\UI\Renderer as RendererInterface; -use ILIAS\UI\Component; +use ILIAS\UI\Component\Component; /** * Class Renderer @@ -30,28 +31,35 @@ */ class Renderer extends AbstractComponentRenderer { + /** @var int amount of characters that fits into one line on desktop. */ + protected const MAX_CHARS_IN_LINE = 260; + /** * @inheritdocs */ - public function render(Component\Component $component, RendererInterface $default_renderer): string + public function render(Component $component, RendererInterface $default_renderer): string { - if ($component instanceof Component\Listing\Descriptive) { - return $this->render_descriptive($component, $default_renderer); + if ($component instanceof Descriptive) { + return $this->renderDescriptive($component, $default_renderer); } - - if ($component instanceof Component\Listing\Property) { + if ($component instanceof Property) { return $this->renderProperty($component, $default_renderer); } - - if ($component instanceof Component\Listing\Listing) { - return $this->render_simple($component, $default_renderer); + if ($component instanceof Ordered) { + return $this->renderOrdered($component, $default_renderer); + } + if ($component instanceof Unordered) { + return $this->renderUnordered($component, $default_renderer); + } + if ($component instanceof Inline) { + return $this->renderInline($component, $default_renderer); } $this->cannotHandleComponent($component); } - protected function render_descriptive( - Component\Listing\Descriptive $component, + protected function renderDescriptive( + Descriptive $component, RendererInterface $default_renderer ): string { $tpl = $this->getTemplate("tpl.descriptive.html", true, true); @@ -73,49 +81,75 @@ protected function render_descriptive( return $tpl->get(); } - protected function render_simple(Component\Listing\Listing $component, RendererInterface $default_renderer): string + protected function renderOrdered(Ordered $component, RendererInterface $default_renderer): string { - $tpl_name = ""; + $tpl = $this->getTemplate("tpl.ordered.html", true, true); - if ($component instanceof Component\Listing\Ordered) { - $tpl_name = "tpl.ordered.html"; - } - if ($component instanceof Component\Listing\Unordered) { - $tpl_name = "tpl.unordered.html"; - } + $tpl = $this->fillItems($tpl, $component, $default_renderer); - $tpl = $this->getTemplate($tpl_name, true, true); + return $tpl->get(); + } - if (count($component->getItems()) > 0) { - foreach ($component->getItems() as $item) { - $tpl->setCurrentBlock("item"); - if (is_string($item)) { - $tpl->setVariable("ITEM", $item); - } else { - $tpl->setVariable("ITEM", $default_renderer->render($item)); - } - $tpl->parseCurrentBlock(); + protected function renderUnordered(Unordered $component, RendererInterface $default_renderer): string + { + $tpl = $this->getTemplate("tpl.unordered.html", true, true); + + $tpl = $this->fillItems($tpl, $component, $default_renderer); + + return $tpl->get(); + } + + protected function renderInline(Inline $component, RendererInterface $default_renderer): string + { + $tpl = $this->getTemplate("tpl.inline.html", true, true); + + $tpl = $this->fillItems($tpl, $component, $default_renderer); + + return $tpl->get(); + } + + protected function fillItems(Template $tpl, Listing $component, RendererInterface $default_renderer): Template + { + $items = $component->getItems(); + + foreach ($items as $item) { + $tpl->setCurrentBlock("item"); + if ($item instanceof Component) { + $tpl->setVariable("ITEM", $default_renderer->render($item)); + } else { + $tpl->setVariable("ITEM", $item); } + $tpl->parseCurrentBlock(); } - return $tpl->get(); + + return $tpl; } protected function renderProperty( - Component\Listing\Property $component, + Property $component, RendererInterface $default_renderer ): string { $tpl = $this->getTemplate("tpl.propertylisting.html", true, true); - foreach ($component->getItems() as $property) { - list($label, $value, $show_label) = $property; - if (! is_string($value)) { - $value = $default_renderer->render($value); - } - + foreach ($component->getItems() as [$label, $value, $show_label]) { $tpl->setCurrentBlock("property"); - $tpl->setVariable("VALUE", $value); if ($show_label) { - $tpl->setVariable("LABEL", $label); + if ($label instanceof Component) { + $tpl->setVariable('LABEL', $default_renderer->render($label)); + } else { + $tpl->setVariable('LABEL', $this->convertSpecialCharacters($label)); + } + } + if (is_string($value) && self::MAX_CHARS_IN_LINE <= mb_strlen($value)) { + $tpl->setVariable("ID_SHOW_MORE_TOGGLE", $this->createId()); + $tpl->setVariable("MORE", $this->txt("show_more")); + $tpl->setVariable("LESS", $this->txt("show_less")); + $tpl->setVariable("LONG_VALUE", $this->convertSpecialCharacters($value)); + $tpl->parseCurrentBlock(); + } elseif (is_string($value)) { + $tpl->setVariable("SHORT_VALUE", $this->convertSpecialCharacters($value)); + } elseif ($value instanceof Component) { + $tpl->setVariable("SHORT_VALUE", $default_renderer->render($value)); } $tpl->parseCurrentBlock(); } diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php index e898dc34fb27..45010458fcb7 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Factory.php @@ -24,293 +24,298 @@ class Factory implements G\Factory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function settings(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SETTINGS, "settings", $action); + return new Glyph(G\Glyph::SETTINGS, $this->language->txt("settings"), $action); } public function collapse(string $action = null): G\Glyph { - return new Glyph(G\Glyph::COLLAPSE, "collapse_content", $action); + return new Glyph(G\Glyph::COLLAPSE, $this->language->txt("collapse_content"), $action); } public function expand(string $action = null): G\Glyph { - return new Glyph(G\Glyph::EXPAND, "expand_content", $action); + return new Glyph(G\Glyph::EXPAND, $this->language->txt("expand_content"), $action); } public function add(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ADD, "add", $action); + return new Glyph(G\Glyph::ADD, $this->language->txt("add"), $action); } public function remove(string $action = null): G\Glyph { - return new Glyph(G\Glyph::REMOVE, "remove", $action); + return new Glyph(G\Glyph::REMOVE, $this->language->txt("remove"), $action); } public function up(string $action = null): G\Glyph { - return new Glyph(G\Glyph::UP, "up", $action); + return new Glyph(G\Glyph::UP, $this->language->txt("up"), $action); } public function down(string $action = null): G\Glyph { - return new Glyph(G\Glyph::DOWN, "down", $action); + return new Glyph(G\Glyph::DOWN, $this->language->txt("down"), $action); } public function back(string $action = null): G\Glyph { - return new Glyph(G\Glyph::BACK, "back", $action); + return new Glyph(G\Glyph::BACK, $this->language->txt("back"), $action); } public function next(string $action = null): G\Glyph { - return new Glyph(G\Glyph::NEXT, "next", $action); + return new Glyph(G\Glyph::NEXT, $this->language->txt("next"), $action); } public function sortAscending(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SORT_ASCENDING, "sort_ascending", $action); + return new Glyph(G\Glyph::SORT_ASCENDING, $this->language->txt("sort_ascending"), $action); } public function briefcase(string $action = null): G\Glyph { - return new Glyph(G\Glyph::BRIEFCASE, "briefcase", $action); + return new Glyph(G\Glyph::BRIEFCASE, $this->language->txt("briefcase"), $action); } public function sortDescending(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SORT_DESCENDING, "sort_descending", $action); + return new Glyph(G\Glyph::SORT_DESCENDING, $this->language->txt("sort_descending"), $action); } public function user(string $action = null): G\Glyph { - return new Glyph(G\Glyph::USER, "show_who_is_online", $action); + return new Glyph(G\Glyph::USER, $this->language->txt("show_who_is_online"), $action); } public function mail(string $action = null): G\Glyph { - return new Glyph(G\Glyph::MAIL, "mail", $action); + return new Glyph(G\Glyph::MAIL, $this->language->txt("mail"), $action); } public function notification(string $action = null): G\Glyph { - return new Glyph(G\Glyph::NOTIFICATION, "notifications", $action); + return new Glyph(G\Glyph::NOTIFICATION, $this->language->txt("notifications"), $action); } public function tag(string $action = null): G\Glyph { - return new Glyph(G\Glyph::TAG, "tags", $action); + return new Glyph(G\Glyph::TAG, $this->language->txt("tags"), $action); } public function note(string $action = null): G\Glyph { - return new Glyph(G\Glyph::NOTE, "notes", $action); + return new Glyph(G\Glyph::NOTE, $this->language->txt("notes"), $action); } public function comment(string $action = null): G\Glyph { - return new Glyph(G\Glyph::COMMENT, "comments", $action); + return new Glyph(G\Glyph::COMMENT, $this->language->txt("comments"), $action); } public function like(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LIKE, "like", $action); + return new Glyph(G\Glyph::LIKE, $this->language->txt("like"), $action); } public function love(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LOVE, "love", $action); + return new Glyph(G\Glyph::LOVE, $this->language->txt("love"), $action); } public function dislike(string $action = null): G\Glyph { - return new Glyph(G\Glyph::DISLIKE, "dislike", $action); + return new Glyph(G\Glyph::DISLIKE, $this->language->txt("dislike"), $action); } public function laugh(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LAUGH, "laugh", $action); + return new Glyph(G\Glyph::LAUGH, $this->language->txt("laugh"), $action); } public function astounded(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ASTOUNDED, "astounded", $action); + return new Glyph(G\Glyph::ASTOUNDED, $this->language->txt("astounded"), $action); } public function sad(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SAD, "sad", $action); + return new Glyph(G\Glyph::SAD, $this->language->txt("sad"), $action); } public function angry(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ANGRY, "angry", $action); + return new Glyph(G\Glyph::ANGRY, $this->language->txt("angry"), $action); } public function eyeopen(string $action = null): G\Glyph { - return new Glyph(G\Glyph::EYEOPEN, "eyeopened", $action); + return new Glyph(G\Glyph::EYEOPEN, $this->language->txt("eyeopened"), $action); } public function eyeclosed(string $action = null): G\Glyph { - return new Glyph(G\Glyph::EYECLOSED, "eyeclosed", $action); + return new Glyph(G\Glyph::EYECLOSED, $this->language->txt("eyeclosed"), $action); } public function attachment(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ATTACHMENT, "attachment", $action); + return new Glyph(G\Glyph::ATTACHMENT, $this->language->txt("attachment"), $action); } public function reset(string $action = null): G\Glyph { - return new Glyph(G\Glyph::RESET, "reset", $action); + return new Glyph(G\Glyph::RESET, $this->language->txt("reset"), $action); } public function apply(string $action = null): G\Glyph { - return new Glyph(G\Glyph::APPLY, "apply", $action); + return new Glyph(G\Glyph::APPLY, $this->language->txt("apply"), $action); } public function search(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SEARCH, "search", $action); + return new Glyph(G\Glyph::SEARCH, $this->language->txt("search"), $action); } public function help(string $action = null): G\Glyph { - return new Glyph(G\Glyph::HELP, "help", $action); + return new Glyph(G\Glyph::HELP, $this->language->txt("help"), $action); } public function calendar($action = null): G\Glyph { - return new Glyph(G\Glyph::CALENDAR, "calendar", $action); + return new Glyph(G\Glyph::CALENDAR, $this->language->txt("calendar"), $action); } public function time($action = null): G\Glyph { - return new Glyph(G\Glyph::TIME, "time", $action); + return new Glyph(G\Glyph::TIME, $this->language->txt("time"), $action); } public function close($action = null): G\Glyph { - return new Glyph(G\Glyph::CLOSE, "close", $action); + return new Glyph(G\Glyph::CLOSE, $this->language->txt("close"), $action); } public function more($action = null): G\Glyph { - return new Glyph(G\Glyph::MORE, "show_more", $action); + return new Glyph(G\Glyph::MORE, $this->language->txt("show_more"), $action); } public function disclosure($action = null): G\Glyph { - return new Glyph(G\Glyph::DISCLOSURE, "disclose", $action); + return new Glyph(G\Glyph::DISCLOSURE, $this->language->txt("disclose"), $action); } public function language(?string $action = null): G\Glyph { - return new Glyph(G\Glyph::LANGUAGE, "switch_language", $action); + return new Glyph(G\Glyph::LANGUAGE, $this->language->txt("switch_language"), $action); } public function login(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LOGIN, "log_in", $action); + return new Glyph(G\Glyph::LOGIN, $this->language->txt("log_in"), $action); } public function logout(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LOGOUT, "log_out", $action); + return new Glyph(G\Glyph::LOGOUT, $this->language->txt("log_out"), $action); } public function bulletlist(string $action = null): G\Glyph { - return new Glyph(G\Glyph::BULLETLIST, "bulletlist_action", $action); + return new Glyph(G\Glyph::BULLETLIST, $this->language->txt("bulletlist_action"), $action); } public function numberedlist(string $action = null): G\Glyph { - return new Glyph(G\Glyph::NUMBEREDLIST, "numberedlist_action", $action); + return new Glyph(G\Glyph::NUMBEREDLIST, $this->language->txt("numberedlist_action"), $action); } public function listindent(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LISTINDENT, "listindent", $action); + return new Glyph(G\Glyph::LISTINDENT, $this->language->txt("listindent"), $action); } public function listoutdent(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LISTOUTDENT, "listoutdent", $action); + return new Glyph(G\Glyph::LISTOUTDENT, $this->language->txt("listoutdent"), $action); } public function filter(string $action = null): G\Glyph { - return new Glyph(G\Glyph::FILTER, "filter", $action); + return new Glyph(G\Glyph::FILTER, $this->language->txt("filter"), $action); } public function collapseHorizontal(string $action = null): G\Glyph { - return new Glyph(G\Glyph::COLLAPSE_HORIZONTAL, "collapse/back", $action); + return new Glyph(G\Glyph::COLLAPSE_HORIZONTAL, $this->language->txt("collapse/back"), $action); } public function header(string $action = null): G\Glyph { - return new Glyph(G\Glyph::HEADER, "header_action", $action); + return new Glyph(G\Glyph::HEADER, $this->language->txt("header_action"), $action); } public function italic(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ITALIC, "italic_action", $action); + return new Glyph(G\Glyph::ITALIC, $this->language->txt("italic_action"), $action); } public function bold(string $action = null): G\Glyph { - return new Glyph(G\Glyph::BOLD, "bold_action", $action); + return new Glyph(G\Glyph::BOLD, $this->language->txt("bold_action"), $action); } public function link(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LINK, "link_action", $action); + return new Glyph(G\Glyph::LINK, $this->language->txt("link_action"), $action); } public function launch(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LAUNCH, "launch", $action); + return new Glyph(G\Glyph::LAUNCH, $this->language->txt("launch"), $action); } public function enlarge(string $action = null): G\Glyph { - return new Glyph(G\Glyph::ENLARGE, "enlarge", $action); + return new Glyph(G\Glyph::ENLARGE, $this->language->txt("enlarge"), $action); } public function listView(string $action = null): G\Glyph { - return new Glyph(G\Glyph::LIST_VIEW, "list_view", $action); + return new Glyph(G\Glyph::LIST_VIEW, $this->language->txt("list_view"), $action); } public function preview(string $action = null): G\Glyph { - return new Glyph(G\Glyph::PREVIEW, "preview", $action); + return new Glyph(G\Glyph::PREVIEW, $this->language->txt("preview"), $action); } public function sort(string $action = null): G\Glyph { - return new Glyph(G\Glyph::SORT, "sort", $action); + return new Glyph(G\Glyph::SORT, $this->language->txt("sort"), $action); } public function columnSelection(string $action = null): G\Glyph { - return new Glyph(G\Glyph::COLUMN_SELECTION, "column_selection", $action); + return new Glyph(G\Glyph::COLUMN_SELECTION, $this->language->txt("column_selection"), $action); } public function tileView(string $action = null): G\Glyph { - return new Glyph(G\Glyph::TILE_VIEW, "tile_view", $action); + return new Glyph(G\Glyph::TILE_VIEW, $this->language->txt("tile_view"), $action); } public function dragHandle(string $action = null): G\Glyph { - return new Glyph(G\Glyph::DRAG_HANDLE, "drag_handle", $action); + return new Glyph(G\Glyph::DRAG_HANDLE, $this->language->txt("drag_handle"), $action); } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Renderer.php index 278d9a3f5988..51d1318e4594 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Symbol/Glyph/Renderer.php @@ -80,7 +80,7 @@ public function render(Component\Component $component, RendererInterface $defaul protected function renderLabel(Component\Component $component, Template $tpl): Template { - $tpl->setVariable("LABEL", $this->txt($component->getLabel())); + $tpl->setVariable("LABEL", $component->getLabel()); return $tpl; } diff --git a/components/ILIAS/UI/src/examples/Entity/Standard/base.php b/components/ILIAS/UI/src/examples/Entity/Standard/base.php index 8f52fdb2a08a..dee4691b27bd 100755 --- a/components/ILIAS/UI/src/examples/Entity/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Entity/Standard/base.php @@ -6,21 +6,23 @@ /** * --- + * description: > + * Entities being used to show a made up event object. * expected output: > * Entities arrange information about e.g. an object into semantic groups; - * this example focusses on the possible contents of those groups and shows + * this example focuses on the possible contents of those groups and shows * a possible representation of a made up event. * From top to bottom, left to right: + * - An icon indents the following. * - There is a precondition; it links to ilias.de. * - An action-dropdown is available with two entries linking to ilias/github. - * - An icon indents the following. - * - Prominently featured is the event's date proptery. * - Only after that, the title of the event is displayed in bold. + * - Prominently featured is the event's date property. * - A progress meter ("in progress") is followed by detailed properties: * - Room information * - Description * - in one line: Available seats and availability of the event - * - in the next line: duration and the information of available redording + * - in the next line: duration and the information of available recording * - The bottom "row" shows two tags on the left * - and two glyphs on the right, the first one with status counter, the second one with * both status- and novelty counter. @@ -53,7 +55,7 @@ function base() $f->button()->shy("ILIAS", "https://www.ilias.de"), $f->button()->shy("GitHub", "https://www.github.com") ]; - $entity = $entity->withActions(...$actions); + $entity = $entity->withManagingActions(...$actions); /* * Logic for Pulling Availabilty Properties to Blocking Conditions diff --git a/components/ILIAS/UI/src/examples/Entity/Standard/semantic_groups.php b/components/ILIAS/UI/src/examples/Entity/Standard/semantic_groups.php index 6eac774a69b3..f5eedc9275d9 100755 --- a/components/ILIAS/UI/src/examples/Entity/Standard/semantic_groups.php +++ b/components/ILIAS/UI/src/examples/Entity/Standard/semantic_groups.php @@ -6,13 +6,17 @@ /** * --- + * description: > + * The different semantic locations on an entity. * expected output: > * This example shows/identifies the semantic groups of entites; * from top to bottom, left to right, the order of groups is this: - * - blocking conditions (left) and actions in a dropdown (right) * - secondary indentifier (it indents all the latter) and featured properties + * - blocking conditions (left) and actions in a dropdown (right) * - primary identifier + * - featured properties * - personal status + * - a workflow step button * - main details * - availability * - details @@ -27,15 +31,33 @@ function semantic_groups() $entity = $f->entity()->standard('Primary Identifier', 'Secondary Identifier') ->withBlockingAvailabilityConditions($f->legacy('Blocking Conditions')) - ->withFeaturedProperties($f->legacy('Featured_properties')) + ->withFeaturedProperties($f->legacy('Featured Properties')) ->withPersonalStatus($f->legacy('Personal Status')) ->withMainDetails($f->legacy('Main Details')) ->withAvailability($f->legacy('Availability')) ->withDetails($f->legacy('Details')) ->withReactions($f->button()->tag('reaction', '#')) - ->withPrioritizedReactions($f->symbol()->glyph()->like()) - ->withActions($f->button()->shy('action', '#')) + ->withPrioritizedReactions($f->button()->shy("Prioritized Reaction", "#")->withSymbol($f->symbol()->glyph()->like())) + ->withManagingActions($f->button()->shy('managing actions', '#')) ; + // to get buttons, they need to be created from a Workflow + $workflow_factory = $f->listing()->workflow(); + $dummy_step = $workflow_factory->step('', ''); + + // Creating Workflow Steps + $steps = [ + $workflow_factory->step("Workflow Step not longer available", "", "#") + ->withAvailability($dummy_step::NOT_ANYMORE)->withStatus($dummy_step::SUCCESSFULLY), + $workflow_factory->step("Start available Workflow Step", "", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Workflow Step not yet available", "", "#") + ->withAvailability($dummy_step::NOT_AVAILABLE)->withStatus($dummy_step::NOT_AVAILABLE), + ]; + + $video_workflow = $workflow_factory->linear("Workflow", $steps); + + $entity = $entity->withWorkflow($video_workflow); + return $renderer->render($entity); } diff --git a/components/ILIAS/UI/src/examples/Entity/Standard/video_object.php b/components/ILIAS/UI/src/examples/Entity/Standard/video_object.php new file mode 100644 index 000000000000..1c53f7dadc8d --- /dev/null +++ b/components/ILIAS/UI/src/examples/Entity/Standard/video_object.php @@ -0,0 +1,111 @@ + + * Full example showing how the entity could be used to describe a video object with all of its features. + * + * expected output: > + * This example shows a representation of a made up video object. + * - A thumbnail is shown as a secondary identifier that indents all following elements + * - the title as primary identifier + * - a dropdown with managing options + * - upload date and publisher as Featured Property + * - Workflow buttons + * - duration adn a description as main details + * Below is an example workflow that was given to the entity to generate the workflow buttons on this entity. + * Two of the four steps are marked as not completed and available. These two options are rendered as buttons inside + * the entity. + * --- + */ +function video_object() +{ + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + /* + * Basic Construction + */ + + $primary_id = "Mountains through the ages - the formation of giants"; + $secondary_id = $f->image()->responsive("assets/ui-examples/images/Image/mountains.jpg", "Some mountains in the dusk"); + + // creating the entity object now so it can be filled in the logic section + $entity = $f->entity()->standard( + $primary_id, + $secondary_id + ); + + /* + * Priority Areas + */ + + $glyph_calendar = $f->symbol()->glyph()->calendar()->withLabel("Published on"); + $glyph_user = $f->symbol()->glyph()->user()->withLabel("Created by"); + + $featured_properties = $f->listing()->property() + ->withProperty($glyph_calendar, '24.01.2025') + ->withProperty($glyph_user, 'BBC England, Co-Production: ARD/ZDF, Canal Plus') + ; + + $entity = $entity + ->withFeaturedProperties($featured_properties) + ; + + /* + * Dropdown Actions + */ + + $managing_actions = [ + $f->button()->shy("Copy", "https://www.ilias.de"), + $f->button()->shy("Delete", "https://www.github.com") + ]; + $entity = $entity->withManagingActions(...$managing_actions); + + /* + * Generating Action Buttons from Workflow + */ + + $workflow_factory = $f->listing()->workflow(); + $dummy_step = $workflow_factory->step('', ''); + + // Creating Workflow Steps + $steps = [ + $workflow_factory->step("Upload video file", "Upload an .mp4 file or start a recording.", "#") + ->withAvailability($dummy_step::NOT_ANYMORE)->withStatus($dummy_step::SUCCESSFULLY), + $workflow_factory->step("Cut video", "Trim or remove parts of the video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Add subtitles", "You must upload or generate subtitles for every video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Publish", "Set who can see this video.", "#") + ->withAvailability($dummy_step::NOT_AVAILABLE)->withStatus($dummy_step::NOT_AVAILABLE), + ]; + + $video_workflow = $workflow_factory->linear("Video Curation", $steps); + + $entity = $entity->withWorkflow($video_workflow); + + /* + * All Other Semantic Groups + */ + + $glyph_time = $f->symbol()->glyph()->time()->withLabel("Duration"); + + $main_details_01 = $f->listing()->property() + ->withProperty($glyph_time, '45:00') + ; + $main_details_02 = $f->listing()->property() + ->withProperty('Description', "A fascinating look on the forces of nature that are able to move unimaginable tons of rocks. Find out how seemingly immovable landscape has transformed drastically through the incredible forces set free by earthquakes, vulcanos and water. This award-winning documentary traces the movement of the world's greatest mountain ranges throughout millions of years.", false) + ; + + $entity = $entity + ->withMainDetails($main_details_01, $main_details_02) + ; + + return $renderer->render([$entity]); +} diff --git a/components/ILIAS/UI/src/examples/Link/Standard/with_open_in_new_viewport.php b/components/ILIAS/UI/src/examples/Link/Standard/with_open_in_new_viewport.php new file mode 100644 index 000000000000..6a3c1838d241 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Link/Standard/with_open_in_new_viewport.php @@ -0,0 +1,27 @@ + + * Example for rendering a standard link that opens to a new viewport. + * + * expected output: > + * ILIAS shows a link with the title "Goto ILIAS in new tab/window". + * Clicking the link opens the website ilias.ch in a new browser window or tab. + * --- + */ +function with_open_in_new_viewport(): string +{ + global $DIC; + $factory = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $link = $factory->link()->standard("Goto ILIAS", "http://ilias.ch"); + $link = $link->withOpenInNewViewport(true); + + return $renderer->render($link); +} diff --git a/components/ILIAS/UI/src/examples/Listing/Entity/Grid/base.php b/components/ILIAS/UI/src/examples/Listing/Entity/Grid/base.php new file mode 100644 index 000000000000..bfe3fe6118e0 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Listing/Entity/Grid/base.php @@ -0,0 +1,128 @@ + + * A card-style grid presenting many entity objects side by side. + * + * expected output: > + * ILIAS shows a grid of entities looking like cards. Each card has a thumbnail image, title and some video related + * properties and actions. The grid reacts flexibly to the available space. If there is a lot of space, the grid + * will have more columns. With little available space, the cards will stack. + * --- + */ +function base(): string +{ + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + $record_to_entity = new class () implements RecordToEntity { + public function map(UIFactory $ui_factory, mixed $record): Entity + { + $glyph_user = $ui_factory->symbol()->glyph()->user() + ->withLabel("Created by"); + + $glyph_calendar = $ui_factory->symbol()->glyph()->calendar() + ->withLabel("Upload date"); + + $glyph_duration = $ui_factory->symbol()->glyph()->time() + ->withLabel("Duration"); + + list($title, $thumbnail_url, $creator, $availability, $description, $duration, $add_workflow, $date) = $record; + $managing_actions = [ + $ui_factory->button()->shy("Edit", "#"), + $ui_factory->button()->shy("Move", "#"), + ]; + $entity = $ui_factory->entity()->standard( + $ui_factory->link()->standard($title, ""), + $ui_factory->image()->responsive($thumbnail_url, $title)->withAction("#") + ) + ->withFeaturedProperties( + $ui_factory->listing()->property() + ->withProperty($glyph_user, $creator) + ) + ->withManagingActions(...$managing_actions) + ->withMainDetails( + $ui_factory->listing()->property() + ->withProperty($glyph_duration, $description, false) + ->withProperty($glyph_duration, $duration) + ->withProperty($glyph_calendar, $date) + ) + ->withPrioritizedReactions($ui_factory->button()->shy("Like", "#")->withSymbol($ui_factory->symbol()->glyph()->like())) + ; + if ($availability) { + $entity = $entity->withBlockingAvailabilityConditions( + $ui_factory->listing()->property() + ->withProperty("Status", $ui_factory->legacy($availability), false) + ); + } + if ($add_workflow) { + $workflow_factory = $ui_factory->listing()->workflow(); + $dummy_step = $workflow_factory->step('', ''); + + $steps = [ + $workflow_factory->step("Upload video file", "Upload an .mp4 file or start a recording.", "#") + ->withAvailability($dummy_step::NOT_ANYMORE)->withStatus($dummy_step::SUCCESSFULLY), + $workflow_factory->step("Cut video", "Trim or remove parts of the video.", "#") + ->withAvailability($dummy_step::NOT_ANYMORE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Add subtitles", "You must upload or generate subtitles for every video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::SUCCESSFULLY), + $workflow_factory->step("Publish", "Set who can see this video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + ]; + + $video_workflow = $workflow_factory->linear("Video Curation", $steps); + + $entity = $entity->withWorkflow($video_workflow); + } + return $entity; + } + }; + + $glyph_eye_closed = $f->symbol()->glyph()->eyeclosed(); + $glyph_with_text = $renderer->render($glyph_eye_closed) . " offline"; + $card_data = [ + ['Snowboarding for beginners - How to avoid falling on your face', 'assets/ui-examples/images/Image/ski_widescreen-thumbnail.jpg', "Bobby's School of Snowboarding Austria", null, 'This is the perfect start for anyone wanting to get on a snowboard. We talk the best gear and the best locations for a beginner. And no worries - it is not expensive: Renting equipment will work just fine. Then we end with some first exercises to get the stability needed to tackle your first slope', '23 min', false, '01.01.2026'], + ['The History of Bridges', 'assets/ui-examples/images/Image/sanfrancisco_widescreen-thumbnail.jpg', 'BBC England', $glyph_with_text,'One of the most monumental achievements of human kind is the invention of bridges. Crossing streets, rivers and sometimes oceans became a huge pillar for our our modern infrastructure. This documentary looks at the different types of bridges and how they have been developed and engineered throughout different cultures and centuries','90 min', true, "01.11.2026"], + ['Mountains through the ages - the formation of giants', 'assets/ui-examples/images/Image/mountains_widescreen-thumbnail.jpg','ARD/ZDF, Canal Plus', null, "A fascinating look on the forces of nature that are able to move unimaginable tons of rocks. Find out how seemingly immovable landscape has transformed drastically through the incredible forces set free by earthquakes, vulcanos and water. This award-winning documentary traces the movement of the world's greatest mountain ranges throughout millions of years.", "45 min", false, "11.10.2026"], + ['Snowboarding for beginners - How to avoid falling on your face', 'assets/ui-examples/images/Image/ski_widescreen-thumbnail.jpg', "Bobby's School of Snowboarding Austria", null, 'This is the perfect start for anyone wanting to get on a snowboard. We talk the best gear and the best locations for a beginner. And no worries - it is not expensive: Renting equipment will work just fine. Then we end with some first exercises to get the stability needed to tackle your first slope', '23 min', false, "28.02.2026"], + ['The History of Bridges', 'assets/ui-examples/images/Image/sanfrancisco_widescreen-thumbnail.jpg', 'BBC England', $glyph_with_text,'One of the most monumental achievements of human kind is the invention of bridges. Crossing streets, rivers and sometimes oceans became a huge pillar for our our modern infrastructure. This documentary looks at the different types of bridges and how they have been developed and engineered throughout different cultures and centuries','90 min', true, "15.12.2025"], + ['Mountains through the ages - the formation of giants', 'assets/ui-examples/images/Image/mountains_widescreen-thumbnail.jpg','ARD/ZDF, Canal Plus', null, "A fascinating look on the forces of nature that are able to move unimaginable tons of rocks. Find out how seemingly immovable landscape has transformed drastically through the incredible forces set free by earthquakes, vulcanos and water. This award-winning documentary traces the movement of the world's greatest mountain ranges throughout millions of years.", "45 min", false, "09.06.2026"] + ]; + + $data = new class ($card_data) implements DataRetrieval { + protected array $data = []; + + public function __construct($card_data) + { + $this->data = $card_data; + } + + public function getEntities( + Mapping $mapping, + ?Range $range, + ?array $additional_parameters + ): \Generator { + foreach ($this->data as $vid) { + yield $mapping->map($vid); + } + } + }; + + $listing = $f->listing()->entity()->grid($record_to_entity) + ->withData($data); + + return $renderer->render($listing); +} diff --git a/components/ILIAS/UI/src/examples/Listing/Entity/Standard/base.php b/components/ILIAS/UI/src/examples/Listing/Entity/Standard/base.php index 76cb8a2cdd98..897dfc8be995 100755 --- a/components/ILIAS/UI/src/examples/Listing/Entity/Standard/base.php +++ b/components/ILIAS/UI/src/examples/Listing/Entity/Standard/base.php @@ -13,8 +13,11 @@ /** * --- + * description: > + * A component to list many entities. Has multiple columns on very large screens. * expected output: > - * ILIAS shows the rendered Component. + * ILIAS shows a list of entities. If there is a lot of space available, the list will switch to a layout with two + * columns. * --- */ function base() diff --git a/components/ILIAS/UI/src/examples/Listing/Inline/base.php b/components/ILIAS/UI/src/examples/Listing/Inline/base.php new file mode 100644 index 000000000000..fe9a7012b43c --- /dev/null +++ b/components/ILIAS/UI/src/examples/Listing/Inline/base.php @@ -0,0 +1,30 @@ + + * Example for rendering an inline list. + * + * expected output: > + * ILIAS shows the elements of a list horizontally in a row, separated by commas. + * --- + */ +function base(): string +{ + //Init Factory and Renderer + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + //Generate List + $inline = $f->listing()->inline( + ["Apple","Banana","Milk", "Toast", "Pumpkin Pie", "Bread"] + ); + + //Render + return $renderer->render($inline); +} diff --git a/components/ILIAS/UI/src/examples/Listing/Inline/property_listing.php b/components/ILIAS/UI/src/examples/Listing/Inline/property_listing.php new file mode 100644 index 000000000000..599f81fd0dc5 --- /dev/null +++ b/components/ILIAS/UI/src/examples/Listing/Inline/property_listing.php @@ -0,0 +1,46 @@ + + * Example for rendering an inline list inside a property list. + * + * expected output: > + * ILIAS shows two properties in a single row (if space allows for it). + * One is a "Languages" property followed with flag icons. + * The other property lists video resolutions as text. + * The values are separated by commas. + * --- + */ +function property_listing(): string +{ + //Init Factory and Renderer + global $DIC; + $f = $DIC->ui()->factory(); + $renderer = $DIC->ui()->renderer(); + + + $flag_de = $f->symbol()->icon()->custom( + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWRlIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPHBhdGggZmlsbD0iI2ZjMCIgZD0iTTAgMzIwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBmaWxsPSIjMDAwMDAxIiBkPSJNMCAwaDY0MHYxNjBIMHoiLz4KICA8cGF0aCBmaWxsPSJyZWQiIGQ9Ik0wIDE2MGg2NDB2MTYwSDB6Ii8+Cjwvc3ZnPgo=", + "German" + ); + + $flag_gb = $f->symbol()->icon()->custom( + "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGlkPSJmbGFnLWljb25zLWdiIiB2aWV3Qm94PSIwIDAgNjQwIDQ4MCI+CiAgPHBhdGggZmlsbD0iIzAxMjE2OSIgZD0iTTAgMGg2NDB2NDgwSDB6Ii8+CiAgPHBhdGggZmlsbD0iI0ZGRiIgZD0ibTc1IDAgMjQ0IDE4MUw1NjIgMGg3OHY2Mkw0MDAgMjQxbDI0MCAxNzh2NjFoLTgwTDMyMCAzMDEgODEgNDgwSDB2LTYwbDIzOS0xNzhMMCA2NFYweiIvPgogIDxwYXRoIGZpbGw9IiNDODEwMkUiIGQ9Im00MjQgMjgxIDIxNiAxNTl2NDBMMzY5IDI4MXptLTE4NCAyMCA2IDM1TDU0IDQ4MEgwek02NDAgMHYzTDM5MSAxOTFsMi00NEw1OTAgMHpNMCAwbDIzOSAxNzZoLTYwTDAgNDJ6Ii8+CiAgPHBhdGggZmlsbD0iI0ZGRiIgZD0iTTI0MSAwdjQ4MGgxNjBWMHpNMCAxNjB2MTYwaDY0MFYxNjB6Ii8+CiAgPHBhdGggZmlsbD0iI0M4MTAyRSIgZD0iTTAgMTkzdjk2aDY0MHYtOTZ6TTI3MyAwdjQ4MGg5NlYweiIvPgo8L3N2Zz4K", + "English" + ); + + $languages = $f->listing()->inline([$flag_de, $flag_gb]); + $resolutions = $f->listing()->inline(["480p", "720p", "1080p", "4k"]); + + $video_properties = $f->listing()->property() + ->withProperty("Languages", $languages) + ->withProperty("Resolutions", $resolutions); + + //Render + return $renderer->render($video_properties); +} diff --git a/components/ILIAS/UI/src/examples/Listing/Property/base.php b/components/ILIAS/UI/src/examples/Listing/Property/base.php index e16f57edcf52..c390f8bea0a8 100755 --- a/components/ILIAS/UI/src/examples/Listing/Property/base.php +++ b/components/ILIAS/UI/src/examples/Listing/Property/base.php @@ -6,8 +6,18 @@ /** * --- + * description: > + * Example of differently used properties. + * * expected output: > - * ILIAS shows the rendered Component. + * ILIAS shows the rendered Component. The following options are showcased at least once: + * - Key is a text string, value is a text string + * - Key is not shown, value is a Learning Progress status image followed by text + * - Key is a Glyph, value is a (date) text string + * - Key is a text string, value is a long text with a show more/less toggle + * - Key is a text string, value is a clickable link + * - Key is a text string, value is a Glyph + * * --- */ function base() @@ -16,26 +26,40 @@ function base() $f = $DIC->ui()->factory(); $renderer = $DIC->ui()->renderer(); + $some_legacy_code = $f->legacy( + $renderer->render( + $f->symbol()->icon()->custom('./assets/images/learning_progress/in_progress.svg', 'incomplete'), + ) . ' in progress' + ); + + $glyph_calendar = $f->symbol()->glyph()->calendar()->withLabel("date of upload"); + $props = $f->listing()->property() ->withProperty('Title', 'Some Title') ->withProperty('number', '7') ->withProperty( 'status', - $renderer->render( - $f->symbol()->icon()->custom('./assets/images/learning_progress/in_progress.svg', 'incomplete'), - ) . ' in progress', + $some_legacy_code, false - ); + ) + ->withProperty($glyph_calendar, "21.03.2026", false); + + $props2 = $f->listing()->property() + ->withProperty("Description", "Heads up, this is a very long description. It is always a challenge: You have more to say, but there is so little space. And we still want this text to be shown with all the other properties. For this case we have the automatic text collapsing feature. This way we get the best of both worlds: The text doesn't expand beyond one line, but you can see the rest if you need to. A good use case is on the entity. As the entity might be used to show course entities with lengthy descriptions. Those will take up less space initially. Isn't that sweet? And the crazy thing is: It does not need JavaScript. It's pure HTML and CSS only. Isn't that nice?", false); + + $yes_checkmark = $f->symbol()->glyph()->apply()->withLabel("yes, approved"); - $props2 = $props->withItems([ + $props3 = $props->withItems([ ['a', "1"], ['y', "25", false], - ['link', $f->link()->standard('Goto ILIAS', 'http://www.ilias.de')] + ['link', $f->link()->standard('Goto ILIAS', 'http://www.ilias.de')], + ['approved', $yes_checkmark], ]); return $renderer->render([ $props, + $props2, $f->divider()->horizontal(), - $props2 + $props3 ]); } diff --git a/components/ILIAS/UI/src/templates/default/Entity/tpl.entity.html b/components/ILIAS/UI/src/templates/default/Entity/tpl.entity.html index 42c1276d76d1..7dc8de0067b1 100755 --- a/components/ILIAS/UI/src/templates/default/Entity/tpl.entity.html +++ b/components/ILIAS/UI/src/templates/default/Entity/tpl.entity.html @@ -1,46 +1,76 @@ -
- -
{BLOCKING_CONDITIONS}
- +
+ + + - -
{ACTIONS}
- + - - -
{PRIMARY_IDENTIFIER}
- - + -
{PERSONAL_STATUS}
+
{PERSONAL_STATUS}
+ +
{WORKFLOW_ACTIONS}
+ + -
{MAIN_DETAILS}
+
{MAIN_DETAILS}
-
{AVAILABILITY}
+
{AVAILABILITY}
-
{DETAILS}
+
{DETAILS}
- -
{REACTIONS}
- + - - - -
+ +
+
+
+
+ +
{REACTIONS}
+ +
+
+
+ + + +
+
+
+ + diff --git a/components/ILIAS/UI/src/templates/default/Listing/tpl.entitylistinggrid.html b/components/ILIAS/UI/src/templates/default/Listing/tpl.entitylistinggrid.html new file mode 100644 index 000000000000..18f073d65d9b --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Listing/tpl.entitylistinggrid.html @@ -0,0 +1,5 @@ +
    + +
  • {ENTITY}
  • + +
diff --git a/components/ILIAS/UI/src/templates/default/Listing/tpl.inline.html b/components/ILIAS/UI/src/templates/default/Listing/tpl.inline.html new file mode 100644 index 000000000000..04e9b86140b3 --- /dev/null +++ b/components/ILIAS/UI/src/templates/default/Listing/tpl.inline.html @@ -0,0 +1,5 @@ +
    + +
  • {ITEM}
  • + +
diff --git a/components/ILIAS/UI/src/templates/default/Listing/tpl.propertylisting.html b/components/ILIAS/UI/src/templates/default/Listing/tpl.propertylisting.html index ac941ceb9b36..52b4c8e05011 100755 --- a/components/ILIAS/UI/src/templates/default/Listing/tpl.propertylisting.html +++ b/components/ILIAS/UI/src/templates/default/Listing/tpl.propertylisting.html @@ -2,9 +2,17 @@
- {LABEL} +
{LABEL}
- {VALUE} + + +
{LONG_VALUE}
+ + +
{SHORT_VALUE}
+
diff --git a/components/ILIAS/UI/tests/Component/Card/RepositoryObjectTest.php b/components/ILIAS/UI/tests/Component/Card/RepositoryObjectTest.php index 0e6efeb1f21b..fc7510307ce8 100755 --- a/components/ILIAS/UI/tests/Component/Card/RepositoryObjectTest.php +++ b/components/ILIAS/UI/tests/Component/Card/RepositoryObjectTest.php @@ -30,6 +30,8 @@ */ class RepositoryObjectTest extends ILIAS_UI_TestBase { + use LanguageStubs; + /** * @return NoUIFactory */ @@ -38,6 +40,7 @@ public function getFactory() $mocks = [ 'button' => $this->createMock(C\Button\Factory::class), 'divider' => $this->createMock(C\Divider\Factory::class), + 'language' => $this->createRelayArgumentLanguageStub(), ]; $factory = new class ($mocks) extends NoUIFactory { public function __construct( @@ -61,7 +64,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->mocks['language']), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Counter/CounterClientHtmlTest.php b/components/ILIAS/UI/tests/Component/Counter/CounterClientHtmlTest.php index 4d60efa503e0..c0cf079f25b2 100755 --- a/components/ILIAS/UI/tests/Component/Counter/CounterClientHtmlTest.php +++ b/components/ILIAS/UI/tests/Component/Counter/CounterClientHtmlTest.php @@ -27,9 +27,11 @@ */ class CounterClientHtmlTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getGlyphFactory(): \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory { - return new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory(); + return new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()); } public function getCounterFactory(): \ILIAS\UI\Implementation\Component\Counter\Factory diff --git a/components/ILIAS/UI/tests/Component/Entity/EntityTest.php b/components/ILIAS/UI/tests/Component/Entity/EntityTest.php index 88ba38c74dee..de80a117eb40 100755 --- a/components/ILIAS/UI/tests/Component/Entity/EntityTest.php +++ b/components/ILIAS/UI/tests/Component/Entity/EntityTest.php @@ -24,10 +24,10 @@ use ILIAS\UI\Implementation\Component\Link; use ILIAS\UI\Implementation\Component\Image; use ILIAS\UI\Implementation\Component\Dropdown; +use ILIAS\UI\Implementation\Component\Listing; use ILIAS\UI\Implementation\Component\Legacy\Legacy; use ILIAS\UI\Implementation\Component\SignalGenerator; use ILIAS\UI\Component as I; -use ILIAS\UI\Factory as UIFactory; class EntityTest extends ILIAS_UI_TestBase { @@ -93,11 +93,11 @@ public function testEntityActionProperties(): void $entity = $this->getEntityFactory()->standard('primary', 'secondary') ->withPrioritizedReactions($glyph, $tag) ->withReactions($glyph, $glyph, $glyph) - ->withActions($shy); + ->withManagingActions($shy); $this->assertEquals([$glyph, $tag], $entity->getPrioritizedReactions()); $this->assertEquals([$glyph,$glyph,$glyph], $entity->getReactions()); - $this->assertEquals([$shy], $entity->getActions()); + $this->assertEquals([$shy], $entity->getManagingActions()); } public function testEntityComponentProperties(): void @@ -108,13 +108,42 @@ public function testEntityComponentProperties(): void $entity = $this->getEntityFactory()->standard('primary', 'secondary') ->withPrioritizedReactions($glyph, $tag) ->withReactions($glyph) - ->withActions($shy); + ->withManagingActions($shy); $this->assertEquals([$glyph, $tag], $entity->getPrioritizedReactions()); $this->assertEquals([$glyph], $entity->getReactions()); - $this->assertEquals([$shy], $entity->getActions()); + $this->assertEquals([$shy], $entity->getManagingActions()); } + public function testEntityWorkflowButtons(): void + { + $workflow_factory = $this->getUIFactory()->listing()->workflow(); + $dummy_step = $workflow_factory->step('', ''); + + // Creating Workflow Steps + $steps = [ + $workflow_factory->step("Upload video file", "Upload an .mp4 file or start a recording.", "#") + ->withAvailability($dummy_step::NOT_ANYMORE)->withStatus($dummy_step::SUCCESSFULLY), + $workflow_factory->step("Cut video", "Trim or remove parts of the video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Add subtitles", "You must upload or generate subtitles for every video.", "#") + ->withAvailability($dummy_step::AVAILABLE)->withStatus($dummy_step::NOT_STARTED), + $workflow_factory->step("Publish", "Set who can see this video.", "#") + ->withAvailability($dummy_step::NOT_AVAILABLE)->withStatus($dummy_step::NOT_AVAILABLE), + ]; + + $video_workflow = $workflow_factory->linear("Video Curation", $steps); + + $entity = $this->getEntityFactory()->standard('primary', 'secondary') + ->withWorkflow($video_workflow); + + $rendered_entity = $this->getDefaultRenderer()->render($entity); + + $this->assertStringContainsString("Cut video", $rendered_entity); + $this->assertStringContainsString("Add subtitles", $rendered_entity); + $this->assertStringNotContainsString("Upload video file", $rendered_entity); + $this->assertStringNotContainsString("Publish", $rendered_entity); + } public function getUIFactory(): NoUIFactory { @@ -123,6 +152,14 @@ public function dropdown(): I\Dropdown\Factory { return new Dropdown\Factory(); } + public function button(): I\Button\Factory + { + return new Button\Factory(); + } + public function listing(): I\Listing\Factory + { + return new Listing\Factory(); + } }; } public function testEntityRendering(): void @@ -133,7 +170,7 @@ public function testEntityRendering(): void $entity = $this->getEntityFactory()->standard('primary', 'secondary') ->withPrioritizedReactions($glyph, $tag) ->withReactions($glyph, $glyph) - ->withActions($shy, $shy) + ->withManagingActions($shy, $shy) ->withBlockingAvailabilityConditions($this->legacy('bc')) ->withFeaturedProperties($this->legacy('fp')) ->withMainDetails($this->legacy('md')) @@ -144,34 +181,48 @@ public function testEntityRendering(): void $r = $this->getDefaultRenderer(); $html = $this->brutallyTrimHTML($r->render($entity)); $expected = $this->brutallyTrimHTML(' -
-
bc
-
- -
-
secondary
-
primary
- -
ps
-
md
-
a
-
d
-
- - -
- -
- '); +
+ +
secondary
+ +
ps
+
md
+
a
+
d
+
+
+
+
+
+
+
+
+ +
+
+
+
+'); $this->assertEquals($expected, $html); } } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php index 4320b19fe68c..b393333beef2 100644 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/FilterInputTest.php @@ -63,6 +63,8 @@ public function legacy($content): I\Legacy\Legacy class FilterInputTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected function buildFactory(): I\Input\Container\Filter\Factory { return new I\Input\Container\Filter\Factory( @@ -88,7 +90,7 @@ protected function buildSymbolFactory(): I\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php b/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php index 29a18605480c..2a9ab2bd47c7 100755 --- a/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Container/Filter/StandardFilterTest.php @@ -79,6 +79,8 @@ public function listing(): I\Listing\Factory class StandardFilterTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected function buildFactory(): I\Input\Container\Filter\Factory { return new I\Input\Container\Filter\Factory( @@ -109,7 +111,7 @@ protected function buildSymbolFactory(): I\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php index 8333c7027615..f8a8d6999780 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/DateTimeInputTest.php @@ -32,6 +32,7 @@ class DateTimeInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use LanguageStubs; protected DefNamesource $name_source; protected Data\Factory $data_factory; @@ -46,12 +47,16 @@ public function setUp(): void public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } public function symbol(): C\Symbol\Factory { return new S\Factory( new S\Icon\Factory(), - new S\Glyph\Factory(), + new S\Glyph\Factory($this->language), new S\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php index b126aad97e94..2e65fdbb37bc 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/DurationInputTest.php @@ -33,6 +33,7 @@ class DurationInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use LanguageStubs; protected DefNamesource $name_source; protected Data\Factory $data_factory; @@ -73,12 +74,17 @@ protected function buildFactory(): I\Input\Field\Factory public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected ILIAS\Language\Language $language, + ) { + } + public function symbol(): C\Symbol\Factory { return new S\Factory( new S\Icon\Factory(), - new S\Glyph\Factory(), + new S\Glyph\Factory($this->language), new S\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php b/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php index ca31d8d3e960..2b82067e82c8 100755 --- a/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php +++ b/components/ILIAS/UI/tests/Component/Input/Field/FileInputTest.php @@ -57,6 +57,7 @@ public function symbol(): SymbolFactory class FileInputTest extends ILIAS_UI_TestBase { use CommonFieldRendering; + use LanguageStubs; protected DefNamesource $name_source; @@ -486,7 +487,7 @@ protected function buildSymbolFactory(): I\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php index c4737a7dd979..9438218ce80c 100644 --- a/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php +++ b/components/ILIAS/UI/tests/Component/Input/ViewControl/ViewControlTestBase.php @@ -29,6 +29,8 @@ abstract class ViewControlTestBase extends ILIAS_UI_TestBase { + use LanguageStubs; + protected function getNamesource() { return new class () implements NameSource { @@ -80,10 +82,11 @@ protected function buildVCFactory(): Control\Factory public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { protected SignalGenerator $sig_gen; - public function __construct() - { + public function __construct( + protected ILIAS\Language\Language $language, + ) { $this->sig_gen = new SignalGenerator(); } public function button(): I\Button\Factory @@ -96,7 +99,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Item/ItemNotificationClientHtmlTest.php b/components/ILIAS/UI/tests/Component/Item/ItemNotificationClientHtmlTest.php index 04353570f241..188623783f51 100755 --- a/components/ILIAS/UI/tests/Component/Item/ItemNotificationClientHtmlTest.php +++ b/components/ILIAS/UI/tests/Component/Item/ItemNotificationClientHtmlTest.php @@ -28,6 +28,8 @@ */ class ItemNotificationClientHtmlTest extends ILIAS_UI_TestBase { + use LanguageStubs; + /** * @var I\SignalGenerator */ @@ -42,9 +44,14 @@ public function setUp(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\SignalGenerator $sig_gen; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function counter(): C\Counter\Factory { return new I\Counter\Factory(); @@ -57,7 +64,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Item/ItemNotificationTest.php b/components/ILIAS/UI/tests/Component/Item/ItemNotificationTest.php index 3e1718ed2662..ebf147219771 100755 --- a/components/ILIAS/UI/tests/Component/Item/ItemNotificationTest.php +++ b/components/ILIAS/UI/tests/Component/Item/ItemNotificationTest.php @@ -30,6 +30,8 @@ */ class ItemNotificationTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected I\Component\SignalGenerator $sig_gen; public function setUp(): void @@ -44,9 +46,14 @@ public function getIcon(): C\Symbol\Icon\Standard public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\Component\SignalGenerator $sig_gen; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function item(): C\Item\Factory { return new I\Component\Item\Factory(); @@ -66,7 +73,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php b/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php index ad6507f1be94..dbc32bd56f02 100755 --- a/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php +++ b/components/ILIAS/UI/tests/Component/Launcher/LauncherInlineTest.php @@ -28,6 +28,8 @@ class LauncherInlineTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected ILIAS\Data\Factory $df; protected ILIAS\Language\Language $language; @@ -64,10 +66,15 @@ protected function getIconFactory(): I\Symbol\Icon\Factory public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\SignalGenerator $sig_gen; public I\Input\Field\Factory $input_factory; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return new I\Button\Factory( @@ -78,7 +85,7 @@ public function symbol(): C\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Listing/Entity/GridEntityListingTest.php b/components/ILIAS/UI/tests/Component/Listing/Entity/GridEntityListingTest.php new file mode 100644 index 000000000000..86bcaf100984 --- /dev/null +++ b/components/ILIAS/UI/tests/Component/Listing/Entity/GridEntityListingTest.php @@ -0,0 +1,165 @@ +entity()->standard('primary', 'secondary'); + } + }; + } + public function getUIFactory(): NoUIFactory + { + return new class () extends NoUIFactory { + public function listing(): I\Listing\Factory + { + return new Listing\Factory(); + } + public function entity(): I\Entity\Factory + { + return new Entity\Factory(); + } + }; + } + + public function testGridEntityListingFactory(): void + { + $this->assertInstanceOf( + I\Listing\Entity\EntityListing::class, + $this->getUIFactory()->listing()->entity()->grid($this->getEntityMapping()) + ); + } + + public function testGridEntityListingRendering(): void + { + $data = new class () implements I\Listing\Entity\DataRetrieval { + protected $data = [1,2,3]; + + public function getEntities( + I\Listing\Entity\Mapping $mapping, + ?Range $range, + ?array $additional_parameters + ): \Generator { + foreach ($this->data as $entry) { + yield $mapping->map($entry); + } + } + }; + + $listing = $this->getUIFactory()->listing()->entity() + ->grid($this->getEntityMapping()) + ->withData($data); + + $render = $this->getDefaultRenderer()->render($listing); + $expected = << +
  • +
    + +
    secondary
    +
    +
  • +
  • +
    + +
    secondary
    +
    +
  • +
  • +
    + +
    secondary
    +
    +
  • + + HTML; + + $this->assertEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($render) + ); + } + + public function testGridEntityListingYieldingEntities(): void + { + $data = new class () implements I\Listing\Entity\DataRetrieval { + protected $data = [1,2,3]; + + public function getEntities( + I\Listing\Entity\Mapping $mapping, + ?Range $range, + ?array $additional_parameters + ): \Generator { + foreach ($this->data as $entry) { + yield $mapping->map($entry); + } + } + }; + + $listing = $this->getUIFactory()->listing()->entity() + ->grid($this->getEntityMapping()) + ->withData($data); + + $entities = iterator_to_array($listing->getEntities($this->getUIFactory())); + + $this->assertCount(3, $entities); + + $this->assertInstanceOf(I\Entity\Entity::class, array_pop($entities)); + } +} diff --git a/components/ILIAS/UI/tests/Component/Listing/ListingTest.php b/components/ILIAS/UI/tests/Component/Listing/ListingTest.php index ab540b9ed6b3..ef6b11add752 100755 --- a/components/ILIAS/UI/tests/Component/Listing/ListingTest.php +++ b/components/ILIAS/UI/tests/Component/Listing/ListingTest.php @@ -47,6 +47,10 @@ public function testImplementsFactoryInterface(): void "ILIAS\\UI\\Component\\Listing\\Unordered", $f->unordered(array("1")) ); + $this->assertInstanceOf( + "ILIAS\\UI\\Component\\Listing\\Inline", + $f->inline(array("1")) + ); $this->assertInstanceOf( "ILIAS\\UI\\Component\\Listing\\Descriptive", $f->descriptive(array("k1" => "c1")) @@ -83,7 +87,14 @@ public function testUnorderedGetItems(): void $items = array("1","2"); $this->assertEquals($l->getItems(), $items); } + public function testInlineGetItems(): void + { + $f = $this->getListingFactory(); + $l = $f->inline(array("1","2")); + $items = array("1","2"); + $this->assertEquals($l->getItems(), $items); + } public function testDescriptiveGetItems(): void { $f = $this->getListingFactory(); @@ -110,6 +121,14 @@ public function testUnorderedWithItems(): void $items = array("1","2"); $this->assertEquals($l->getItems(), $items); } + public function testInlineWithItems(): void + { + $f = $this->getListingFactory(); + $l = $f->inline(array())->withItems(array("1","2")); + + $items = array("1","2"); + $this->assertEquals($l->getItems(), $items); + } public function testDescriptiveWithItems(): void { @@ -173,6 +192,21 @@ public function testRenderUnorderedListing(): void $this->assertEquals($expected, $html); } + public function testRenderInlineListing(): void + { + $f = $this->getListingFactory(); + $r = $this->getDefaultRenderer(); + $l = $f->inline(array("1","2")); + + $html = $this->brutallyTrimHTML($r->render($l)); + + $expected = <<< 'HTML' +
    • 1
    • 2
    + HTML; + $expected = $this->brutallyTrimHTML($expected); + + $this->assertEquals($expected, $html); + } public function testRenderDescriptiveListing(): void { diff --git a/components/ILIAS/UI/tests/Component/Listing/Property/PropertyListingTest.php b/components/ILIAS/UI/tests/Component/Listing/Property/PropertyListingTest.php index 8e3f0a705336..0c463fcd76ae 100755 --- a/components/ILIAS/UI/tests/Component/Listing/Property/PropertyListingTest.php +++ b/components/ILIAS/UI/tests/Component/Listing/Property/PropertyListingTest.php @@ -19,15 +19,23 @@ declare(strict_types=1); use ILIAS\UI\Implementation\Component\Listing; +use ILIAS\UI\Implementation\Component\Symbol\Glyph; use ILIAS\UI\Component as I; class PropertyListingTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected function getListingFactory(): Listing\Factory { return new Listing\Factory(); } + protected function getGlyphFactory(): Glyph\Factory + { + return new Glyph\Factory($this->createRelayArgumentLanguageStub()); + } + public function testPropertyListingConstruction(): void { $pl = $this->getListingFactory()->property(); @@ -45,7 +53,9 @@ public function testPropertyListingWithProperty(): void ->withProperty(...$props[0]) ->withProperty(...$props[1]); - $this->assertEquals($props, $pl->getItems()); + $created_items = $pl->getItems(); + + $this->assertEquals($props, $created_items); } public function testPropertyListingWithItems(): void @@ -61,6 +71,19 @@ public function testPropertyListingWithItems(): void $this->assertEquals($props, $pl->getItems()); } + public function testPropertyListingWithSymbols(): void + { + $symbol = $this->getGlyphFactory()->user(); + $props = [ + [$symbol, 'value1', true], + ['label2', $symbol, false], + ]; + $pl = $this->getListingFactory()->property(); + + $pl = $pl->withItems($props); + $this->assertEquals($props, $pl->getItems()); + } + public function testPropertyListingRendering(): void { $props = [ @@ -70,21 +93,81 @@ public function testPropertyListingRendering(): void $pl = $this->getListingFactory()->property() ->withItems($props); - $expected = $this->brutallyTrimHTML(' -
    -
    - label1 - value1 -
    -
    - value2 -
    -
    - '); + $expected = $this->brutallyTrimHTML(<< +
    +
    label1
    +
    value1
    +
    +
    +
    value2
    +
    + + HTML); + + $this->assertEquals( + $expected, + $this->brutallyTrimHTML($this->getDefaultRenderer()->render($pl)) + ); + } + + public function testPropertyListingLongValue(): void + { + $props = [ + ['label1', 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.', true], + ['label2', 'Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.', false] + ]; + $pl = $this->getListingFactory()->property() + ->withItems($props); + + $expected = $this->brutallyTrimHTML(<< +
    +
    label1
    + +
    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
    +
    +
    + +
    Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi.
    +
    + + HTML); + + $this->assertEquals( + $this->brutallyTrimHTML($expected), + $this->brutallyTrimHTML($this->getDefaultRenderer()->render($pl)) + ); + } + + public function testPropertyListingSymbolsRendering(): void + { + $symbol = $this->getGlyphFactory()->user(); + $props = [ + [$symbol, 'value1'], + ['label2', $symbol], + ]; + + $pl = $this->getListingFactory()->property() + ->withItems($props); + + $expected = $this->brutallyTrimHTML(<< +
    +
    +
    value1
    +
    +
    +
    label2
    +
    +
    + + HTML); $this->assertEquals( $expected, $this->brutallyTrimHTML($this->getDefaultRenderer()->render($pl)) ); } + } diff --git a/components/ILIAS/UI/tests/Component/MainControls/MainBarTest.php b/components/ILIAS/UI/tests/Component/MainControls/MainBarTest.php index 42daf93fbb93..0bd12db1f46e 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/MainBarTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/MainBarTest.php @@ -33,6 +33,8 @@ */ class MainBarTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected I\Button\Factory $button_factory; protected I\Link\Factory $link_factory; protected I\Symbol\Icon\Factory $icon_factory; @@ -51,7 +53,7 @@ public function setUp(): void $counter_factory, new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()), new I\Symbol\Avatar\Factory() ) ); @@ -183,9 +185,14 @@ public function testSignalsPresent(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public C\Button\Factory $button_factory; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return $this->button_factory; @@ -193,7 +200,7 @@ public function button(): C\Button\Factory public function symbol(): C\Symbol\Factory { $f_icon = new I\Symbol\Icon\Factory(); - $f_glyph = new I\Symbol\Glyph\Factory(); + $f_glyph = new I\Symbol\Glyph\Factory($this->language); $f_avatar = new I\Symbol\Avatar\Factory(); return new I\Symbol\Factory($f_icon, $f_glyph, $f_avatar); @@ -204,7 +211,7 @@ public function mainControls(): C\MainControls\Factory $counter_factory = new I\Counter\Factory(); $symbol_factory = new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); $slate_factory = new I\MainControls\Slate\Factory($sig_gen, $counter_factory, $symbol_factory); diff --git a/components/ILIAS/UI/tests/Component/MainControls/MetaBarTest.php b/components/ILIAS/UI/tests/Component/MainControls/MetaBarTest.php index 69001c963750..44fe967787e7 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/MetaBarTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/MetaBarTest.php @@ -32,6 +32,8 @@ */ class MetaBarTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected I\Component\Button\Factory $button_factory; protected I\Component\Symbol\Icon\Factory $icon_factory; protected I\Component\Counter\Factory $counter_factory; @@ -50,7 +52,7 @@ public function setUp(): void $this->counter_factory, new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()), new I\Component\Symbol\Avatar\Factory() ) ); @@ -110,11 +112,16 @@ public function testSignalsPresent(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public C\Button\Factory $button_factory; public C\MainControls\Factory $mc_factory; public C\Counter\Factory $counter_factory; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return $this->button_factory; @@ -127,7 +134,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/MainControls/ModeInfoTest.php b/components/ILIAS/UI/tests/Component/MainControls/ModeInfoTest.php index 801cf31ef923..4d7c3c0d47d7 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/ModeInfoTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/ModeInfoTest.php @@ -33,6 +33,8 @@ */ class ModeInfoTest extends ILIAS_UI_TestBase { + use LanguageStubs; + private SignalGenerator $sig_gen; @@ -89,11 +91,12 @@ public function testData(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public SignalGenerator $sig_gen; - public function __construct() - { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { $this->sig_gen = new SignalGenerator(); } @@ -101,7 +104,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new Factory( new \ILIAS\UI\Implementation\Component\Symbol\Icon\Factory(), - new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory(), + new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory($this->language), new \ILIAS\UI\Implementation\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/MainControls/Slate/CombinedSlateTest.php b/components/ILIAS/UI/tests/Component/MainControls/Slate/CombinedSlateTest.php index 4e6db6a75dba..9f11759f58c4 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/Slate/CombinedSlateTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/Slate/CombinedSlateTest.php @@ -30,6 +30,8 @@ */ class CombinedSlateTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected I\SignalGenerator $sig_gen; protected I\Button\Factory $button_factory; protected I\Divider\Factory $divider_factory; @@ -45,17 +47,22 @@ public function setUp(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\SignalGenerator $sig_gen; public I\Button\Factory $button_factory; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return $this->button_factory; } public function glyph(): C\Symbol\Glyph\Factory { - return new I\Symbol\Glyph\Factory(); + return new I\Symbol\Glyph\Factory($this->language); } public function divider(): C\Divider\Factory diff --git a/components/ILIAS/UI/tests/Component/MainControls/Slate/DrilldownSlateTest.php b/components/ILIAS/UI/tests/Component/MainControls/Slate/DrilldownSlateTest.php index 6ae8622c583d..998c6cd15d20 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/Slate/DrilldownSlateTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/Slate/DrilldownSlateTest.php @@ -30,9 +30,16 @@ */ class DrilldownSlateTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + protected function getSigGen() { return new I\SignalGenerator(); @@ -49,7 +56,7 @@ public function symbol(): C\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/MainControls/Slate/NotificationSlateTest.php b/components/ILIAS/UI/tests/Component/MainControls/Slate/NotificationSlateTest.php index cca96681cbee..847c628b3b0a 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/Slate/NotificationSlateTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/Slate/NotificationSlateTest.php @@ -30,6 +30,8 @@ */ class NotificationSlateTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected I\SignalGenerator $sig_gen; public function setUp(): void @@ -44,9 +46,14 @@ public function getIcon(): C\Symbol\Icon\Standard public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\SignalGenerator $sig_gen; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return new I\Button\Factory(); @@ -55,7 +62,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/MainControls/SystemInfoTest.php b/components/ILIAS/UI/tests/Component/MainControls/SystemInfoTest.php index 77d3ae905c70..f8913b2f1146 100755 --- a/components/ILIAS/UI/tests/Component/MainControls/SystemInfoTest.php +++ b/components/ILIAS/UI/tests/Component/MainControls/SystemInfoTest.php @@ -34,6 +34,8 @@ */ class SystemInfoTest extends ILIAS_UI_TestBase { + use LanguageStubs; + private SignalGenerator $sig_gen; public function setUp(): void @@ -233,11 +235,12 @@ public function getOnLoadCodeAsync(): string public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public SignalGenerator $sig_gen; - public function __construct() - { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { $this->sig_gen = new SignalGenerator(); } @@ -245,7 +248,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new Factory( new \ILIAS\UI\Implementation\Component\Symbol\Icon\Factory(), - new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory(), + new \ILIAS\UI\Implementation\Component\Symbol\Glyph\Factory($this->language), new \ILIAS\UI\Implementation\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Menu/Drilldown/DrilldownTest.php b/components/ILIAS/UI/tests/Component/Menu/Drilldown/DrilldownTest.php index 0710c9635046..06f5a2ccec13 100755 --- a/components/ILIAS/UI/tests/Component/Menu/Drilldown/DrilldownTest.php +++ b/components/ILIAS/UI/tests/Component/Menu/Drilldown/DrilldownTest.php @@ -30,6 +30,8 @@ */ class DrilldownTest extends ILIAS_UI_TestBase { + use LanguageStubs; + protected C\Symbol\Icon\Standard $icon; protected C\Symbol\Glyph\Glyph $glyph; protected C\Button\Standard $button; @@ -38,7 +40,12 @@ class DrilldownTest extends ILIAS_UI_TestBase public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function menu(): C\Menu\Factory { return new Menu\Factory( @@ -60,7 +67,7 @@ public function symbol(): \ILIAS\UI\Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } @@ -70,7 +77,7 @@ public function symbol(): \ILIAS\UI\Component\Symbol\Factory public function setUp(): void { $icon_factory = new I\Symbol\Icon\Factory(); - $glyph_factory = new I\Symbol\Glyph\Factory(); + $glyph_factory = new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()); $button_factory = new I\Button\Factory(); $divider_factory = new I\Divider\Factory(); $this->icon = $icon_factory->standard('', ''); diff --git a/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryLegacyTest.php b/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryLegacyTest.php index 7b030f156244..c88062c78788 100755 --- a/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryLegacyTest.php +++ b/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryLegacyTest.php @@ -30,9 +30,16 @@ */ class PanelSecondaryLegacyTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function legacyPanel(string $title, C\Legacy\Legacy $content): I\Component\Panel\Secondary\Legacy { return new I\Component\Panel\Secondary\Legacy($title, $content); @@ -63,7 +70,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryListingTest.php b/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryListingTest.php index 843c61acd7c0..c238749bdf8a 100755 --- a/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryListingTest.php +++ b/components/ILIAS/UI/tests/Component/Panel/PanelSecondaryListingTest.php @@ -30,9 +30,16 @@ */ class PanelSecondaryListingTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function panelSecondary(): I\Component\Panel\Secondary\Factory { return new I\Component\Panel\Secondary\Factory(); @@ -57,7 +64,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Panel/PanelTest.php b/components/ILIAS/UI/tests/Component/Panel/PanelTest.php index 6629c1eac288..132c94be3c78 100755 --- a/components/ILIAS/UI/tests/Component/Panel/PanelTest.php +++ b/components/ILIAS/UI/tests/Component/Panel/PanelTest.php @@ -44,9 +44,16 @@ public function getCanonicalName(): string */ class PanelTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function panelSecondary(): I\Component\Panel\Secondary\Factory { return new I\Component\Panel\Secondary\Factory(); @@ -67,7 +74,7 @@ public function symbol(): C\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Symbol/Glyph/GlyphTest.php b/components/ILIAS/UI/tests/Component/Symbol/Glyph/GlyphTest.php index 561d2c0e5b32..25e004478d9c 100755 --- a/components/ILIAS/UI/tests/Component/Symbol/Glyph/GlyphTest.php +++ b/components/ILIAS/UI/tests/Component/Symbol/Glyph/GlyphTest.php @@ -34,9 +34,11 @@ */ class GlyphTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getGlyphFactory(): G\Factory { - return new I\Symbol\Glyph\Factory(); + return new I\Symbol\Glyph\Factory($this->createRelayArgumentLanguageStub()); } public function getCounterFactory(): C\Factory diff --git a/components/ILIAS/UI/tests/Component/Table/PresentationTest.php b/components/ILIAS/UI/tests/Component/Table/PresentationTest.php index 9294d317177b..68be3de89c95 100755 --- a/components/ILIAS/UI/tests/Component/Table/PresentationTest.php +++ b/components/ILIAS/UI/tests/Component/Table/PresentationTest.php @@ -30,6 +30,8 @@ */ class PresentationTest extends TableTestBase { + use LanguageStubs; + private function getFactory(): I\Component\Table\Factory { return new I\Component\Table\Factory( @@ -98,9 +100,14 @@ public function testRowConstruction(): void public function getUIFactory(): NoUIFactory { - $factory = new class () extends NoUIFactory { + $factory = new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { public I\Component\SignalGenerator $sig_gen; + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function button(): C\Button\Factory { return new I\Component\Button\Factory( @@ -111,7 +118,7 @@ public function symbol(): ILIAS\UI\Component\Symbol\Factory { return new I\Component\Symbol\Factory( new I\Component\Symbol\Icon\Factory(), - new I\Component\Symbol\Glyph\Factory(), + new I\Component\Symbol\Glyph\Factory($this->language), new I\Component\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php index 6643f628c6eb..6ee4e5dd3c77 100644 --- a/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php +++ b/components/ILIAS/UI/tests/Component/Table/TableRendererTestBase.php @@ -31,6 +31,8 @@ */ class TableRendererTestBase extends TableTestBase { + use LanguageStubs; + protected function getActionFactory() { return new I\Table\Action\Factory(); @@ -62,9 +64,13 @@ public function getDataFactory(): Data\Factory public function getUIFactory(): NoUIFactory { - $factory = new class ($this->getTableFactory()) extends NoUIFactory { + $factory = new class ( + $this->getTableFactory(), + $this->createRelayArgumentLanguageStub(), + ) extends NoUIFactory { public function __construct( - protected Component\Table\Factory $table_factory + protected Component\Table\Factory $table_factory, + protected \ILIAS\Language\Language $language, ) { } public function button(): Component\Button\Factory @@ -79,7 +85,7 @@ public function symbol(): Component\Symbol\Factory { return new I\Symbol\Factory( new I\Symbol\Icon\Factory(), - new I\Symbol\Glyph\Factory(), + new I\Symbol\Glyph\Factory($this->language), new I\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php b/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php index f60e6156136f..95ce6bd50426 100755 --- a/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php +++ b/components/ILIAS/UI/tests/Component/ViewControl/PaginationTest.php @@ -31,14 +31,21 @@ */ class PaginationTest extends ILIAS_UI_TestBase { + use LanguageStubs; + public function getUIFactory(): NoUIFactory { - return new class () extends NoUIFactory { + return new class ($this->createRelayArgumentLanguageStub()) extends NoUIFactory { + public function __construct( + protected \ILIAS\Language\Language $language, + ) { + } + public function symbol(): C\Symbol\Factory { return new IC\Symbol\Factory( new IC\Symbol\Icon\Factory(), - new IC\Symbol\Glyph\Factory(), + new IC\Symbol\Glyph\Factory($this->language), new IC\Symbol\Avatar\Factory() ); } diff --git a/components/ILIAS/UI/tests/LanguageStubs.php b/components/ILIAS/UI/tests/LanguageStubs.php new file mode 100644 index 000000000000..cf9fba608749 --- /dev/null +++ b/components/ILIAS/UI/tests/LanguageStubs.php @@ -0,0 +1,46 @@ + + */ +trait LanguageStubs +{ + protected function createFixedLanguageStub(string $translation): Language&MockObject + { + $stub = $this->createMock(Language::class); + $stub->method('txt')->willReturn($translation); + return $stub; + } + + protected function createRelayArgumentLanguageStub(): Language&MockObject + { + $stub = $this->createMock(Language::class); + $stub->method('txt')->willReturnArgument(0); + return $stub; + } +} diff --git a/lang/ilias_de.lang b/lang/ilias_de.lang index 028db218df6d..a49901046cd0 100644 --- a/lang/ilias_de.lang +++ b/lang/ilias_de.lang @@ -5702,6 +5702,7 @@ common#:#show_content#:#Inhalt anzeigen common#:#show_details#:#Details anzeigen common#:#show_filter#:#Filter anzeigen common#:#show_hidden_sections#:#Weitere Informationen anzeigen » +common#:#show_less#:#Weniger zeigen common#:#show_list#:#Auflistung anzeigen common#:#show_members#:#Mitglieder anzeigen common#:#show_more#:#Mehr zeigen diff --git a/lang/ilias_en.lang b/lang/ilias_en.lang index a6e823607601..7dece7551f05 100755 --- a/lang/ilias_en.lang +++ b/lang/ilias_en.lang @@ -5703,6 +5703,7 @@ common#:#show_content#:#Show Content common#:#show_details#:#Show Details common#:#show_filter#:#Show Filter common#:#show_hidden_sections#:#Show More Information » +common#:#show_less#:#Show less common#:#show_list#:#Show List common#:#show_members#:#Display Members common#:#show_more#:#Show More diff --git a/templates/default/030-tools/_index.scss b/templates/default/030-tools/_index.scss new file mode 100644 index 000000000000..b70d3f680d80 --- /dev/null +++ b/templates/default/030-tools/_index.scss @@ -0,0 +1,3 @@ +// Write the reason down, why these tools have to generate CSS utility classes +// Prefer using mixins and @extend instead whenever possible +@use "tool_text-more-less-toggle"; // many adjacent selectors; avoiding construct of 4+ mixins or 4+ parameters diff --git a/templates/default/030-tools/_tool_focus-outline.scss b/templates/default/030-tools/_tool_focus-outline.scss index 70fa7fea0e77..7a54799b6c56 100755 --- a/templates/default/030-tools/_tool_focus-outline.scss +++ b/templates/default/030-tools/_tool_focus-outline.scss @@ -6,14 +6,10 @@ $il-focus-outline-outer-width: 2px; // KEYBOARD FOCUS DEFAULT // This is the mixin you should be using if possible -@mixin il-focus($il-focus-outline-inner-width: $il-focus-outline-inner-width, $il-focus-outline-outer-width: $il-focus-outline-outer-width){ - $il-focus-outline-inner: $il-focus-outline-inner-width solid $il-focus-color; - $il-focus-outline-outer: $il-focus-outline-outer-width solid $il-focus-protection-color; - &:focus { - outline: none; - outline-offset: 0px; - } - &:focus-visible { +@mixin il-focus($il-focus-outline-inner-width: $il-focus-outline-inner-width, $il-focus-outline-outer-width: $il-focus-outline-outer-width, $apply-to-child: "") { + &:focus #{$apply-to-child}, &:focus-visible #{$apply-to-child} { + $il-focus-outline-inner: $il-focus-outline-inner-width solid $il-focus-color; + $il-focus-outline-outer: $il-focus-outline-outer-width solid $il-focus-protection-color; position: relative; // outermost protection color line outline: $il-focus-outline-outer; @@ -111,3 +107,5 @@ $il-focus-outline-outer-width: 2px; } } } + + diff --git a/templates/default/030-tools/_tool_text-more-less-toggle.scss b/templates/default/030-tools/_tool_text-more-less-toggle.scss new file mode 100644 index 000000000000..7ce3f6b470a7 --- /dev/null +++ b/templates/default/030-tools/_tool_text-more-less-toggle.scss @@ -0,0 +1,45 @@ +@use "../010-settings" as s; +@use "../030-tools/tool_multi-line-cap" as t-cap; +@use "../030-tools/tool_focus-outline" as t-focus; +@use "../030-tools/tool_screen-reader-only" as t-sr; +@use "../050-layout/basics" as l; +@use "../050-layout/layout_breakpoints" as l-brk; + +.t-text-more-less { + &__toggle { + &:checked + .t-text-more-less__label { + .t-text-more-less__label__more { + display: none; + } + } + &:not(:checked) + .t-text-more-less__label { + .t-text-more-less__label__less { + display: none; + } + } + } + &__label { + color: s.$il-link-color; + text-decoration: s.$il-link-decoration; + &:hover { + color: s.$il-link-hover-color; + text-decoration: s.$il-link-hover-decoration; + } + } + &:has(.t-text-more-less__toggle:not(:checked)) { + .t-text-more-less__text-body { + @include l-brk.on-screen-size(medium) { + @include t-cap.il-multi-line-cap-mixin(1); + } + @include l-brk.on-screen-size(small) { + @include t-cap.il-multi-line-cap-mixin(2); + } + } + } +} + +input[type="checkbox"].t-text-more-less__toggle { + @include t-sr.sr-only(); + @include t-focus.clear-focus-for-override(); + @include t-focus.il-focus($apply-to-child: "~ label"); +} diff --git a/templates/default/050-layout/_layout_element-bar.scss b/templates/default/050-layout/_layout_element-bar.scss index 0506cdd51826..d5286bb7797d 100755 --- a/templates/default/050-layout/_layout_element-bar.scss +++ b/templates/default/050-layout/_layout_element-bar.scss @@ -17,13 +17,20 @@ $l-bar__element__margin-bottom: $il-margin-xlarge-vertical; // this margin separ .l-bar__space-keeper, .l-bar__group { - // you MUST NOT add padding or margin and SHOULD NOT add any styling (e.g. background-color) in consumer code + // you MUST NOT add padding or margin and SHOULD NOT add any styling (e.g. background-color) in consumer code display: flex; flex-direction: row; flex-wrap: wrap; align-items: center; + // avoid nested elements breaking off + .l-bar__group, + .l-bar__element { + flex-wrap: nowrap; + } } +// elements inside have a margin bottom to keep lines apart +// compensate this line gap so elementbar does not have a margin-bottom below .l-bar__space-keeper:not(:empty) { margin-bottom: calc(-1 * var(--l-bar__element__margin-bottom)); // counteract element margin-bottom, so outer edge doesn't have a visible margin-bottom &.l-bar__space-keeper--space-between { @@ -31,23 +38,26 @@ $l-bar__element__margin-bottom: $il-margin-xlarge-vertical; // this margin separ } } +// bottom margin to separate lines .l-bar__space-keeper > .l-bar__element, .l-bar__group > .l-bar__element { margin-bottom: var(--l-bar__element__margin-bottom); - &:last-child { - margin-right: 0; - } } +// gap between two elements .l-bar__group > .l-bar__element, .l-bar__space-keeper > .l-bar__element { margin-right: var(--l-bar__gap--elements); + &:last-child { + margin-right: 0; + } } +// gap between two groups .l-bar__space-keeper > .l-bar__group, .l-bar__group > .l-bar__group { margin-right: var(--l-bar__gap--groups); &:last-child { margin-right: 0; } -} \ No newline at end of file +} diff --git a/templates/default/050-layout/_layout_grid-auto-columns.scss b/templates/default/050-layout/_layout_grid-auto-columns.scss new file mode 100644 index 000000000000..bac5e66564b2 --- /dev/null +++ b/templates/default/050-layout/_layout_grid-auto-columns.scss @@ -0,0 +1,15 @@ +@use "sass:math"; +@use "sass:list"; +@use "../050-layout/basics" as l; + +$allowed-units: ("px", "ch", "rem", "em"); + +@mixin make-grid-with-auto-columns($column-min-width, $gap: l.$il-margin-xxxlarge-vertical) { + $unit: math.unit($column-min-width); + @if (list.index($allowed-units, $unit) == null) { + @error "Enter a width with one of these units: #{$allowed-units}"; + } + display: grid; + grid-template-columns: repeat(auto-fill, minmax($column-min-width, 1fr)); + gap: $gap; +} diff --git a/templates/default/060-elements/_elements_media.scss b/templates/default/060-elements/_elements_media.scss index cd46057a5285..60c61df57bb4 100755 --- a/templates/default/060-elements/_elements_media.scss +++ b/templates/default/060-elements/_elements_media.scss @@ -1,4 +1,5 @@ @use "../010-settings/" as *; +@use "../030-tools/tool_focus-outline" as focus; img { vertical-align: middle; @@ -6,4 +7,10 @@ img { /* height: auto; messes e.g. survey progress bar */ max-width: 100%; } -} \ No newline at end of file +} + +a:has(img) { + display: inline-block; + @include focus.clear-focus-for-override(); + @include focus.il-focus(); +} diff --git a/templates/default/070-components/UI-framework/Entity/_ui-component_entity.scss b/templates/default/070-components/UI-framework/Entity/_ui-component_entity.scss index 780f3456c56f..f089acabb44f 100755 --- a/templates/default/070-components/UI-framework/Entity/_ui-component_entity.scss +++ b/templates/default/070-components/UI-framework/Entity/_ui-component_entity.scss @@ -1,94 +1,125 @@ @use "sass:math"; @use "../../../010-settings/" as s; @use "../../../050-layout/basics/" as l; +@use "../../../050-layout/layout_breakpoints" as l-brk; + +$entity-padding-vertical: l.$il-padding-xlarge-vertical; +$entity-spacer-padding-vertical: l.$il-padding-xlarge-vertical * 2; +$entity-padding-horizontal: l.$il-padding-xlarge-horizontal; // counterstyling in use for padding-less images on mobile +$img-large-max-width: 360px; +$max-rows: 9; + +@mixin entity-limited-space-design() { + &__container { + display: flex; + flex-direction: column; + > * { + order: 2; + } + } + &__secondary-identifier { + order: 1; + &.--image { + img { + // break out of padding to reach from edge to edge + width: calc(100% + $entity-padding-horizontal * 2); + max-width: unset; + margin-left: -($entity-padding-horizontal); + margin-top: -($entity-padding-vertical); + } + } + } + &__reactionbar { + flex-grow: 1; + } +} .c-entity { - &.__container { + + &__container { display: grid; - grid-template-areas: - "f-blocking f-blocking f-blocking f-blocking actions" - "second-id f-prop f-prop f-prop actions" - "second-id prim-id prim-id prim-id actions" - "second-id status status status status" - "second-id f-details f-details f-details f-details" - "second-id availab availab availab availab" - "second-id details details details details" - "second-id reaction reaction f-reaction f-reaction"; - grid-template-columns: min-content auto auto min-content min-content; + grid-template-columns: max-content auto; + grid-template-rows: repeat($max-rows, minmax(min-content, auto)); border: s.$il-main-border; background-color: s.$il-main-bg; - padding: math.div(l.$il-margin-xlarge-vertical, 2) math.div(l.$il-margin-xlarge-horizontal, 2); - > *:not(:empty) { - padding: math.div(l.$il-margin-xlarge-vertical, 2) math.div(l.$il-margin-xlarge-horizontal, 2); + padding: math.div($entity-padding-vertical, 2) math.div($entity-padding-horizontal, 2); + > * { + // half paddings edge to edge equal a full padding + padding: math.div($entity-padding-vertical, 2) math.div($entity-padding-horizontal, 2); + } + @include l-brk.on-screen-size(small) { + display: block; } } - &.__blocking-conditions { - grid-area: f-blocking; - font-size: s.$il-font-size-xlarge; - } - - &.__actions { - display: flex; - justify-content: end; - grid-area: actions; - .dropdown { - height: max-content; + &__featured-headerbar { + column-span: 2; + > .l-bar__space-keeper { + flex-wrap: nowrap; + .l-bar__group, + .l-bar__element { + align-self: flex-start; + } } } - &.__secondary-identifier { + &__secondary-identifier { + grid-column: 1; + grid-row: 1 / #{$max-rows + 1}; // number high enough so all others can fit on the right &.--string, &.--shy, &.--shylink { width: 10rem; } &.--symbol { - min-width: 3rem; + img { width: auto; } } &.--image { - width: 15rem; + img { max-width: $img-large-max-width; }; } - grid-area: second-id; } - &.__primary-identifier { - grid-area: prim-id; + &__primary-identifier { font-weight: s.$il-font-weight-bold; font-size: s.$il-font-size-xxlarge; } - &.__featured { - grid-area: f-prop; - font-size: s.$il-font-size-xlarge; + &__featured { + font-size: s.$il-font-size-large; } - &.__personal-status { - grid-area: status; + &__workflow-actions { + padding-top: $entity-spacer-padding-vertical; } - &.__main-details { - grid-area: f-details; + &__blocking-conditions { + padding-bottom: $entity-spacer-padding-vertical; + font-size: s.$il-font-size-large; } - - &.__availability { - grid-area: availab; + &__main-details { + padding-top: $entity-spacer-padding-vertical; + } + &__details { + font-size: s.$il-font-size-small; } - &.__details { - grid-area: details; + &__reactions { + padding-top: $entity-spacer-padding-vertical; } - &.__reactions { - // display: flex; - // flex-direction: row; - grid-area: reaction; + &__reactionbar { + align-self: stretch; + align-content: flex-end; + grid-column: 2; + grid-row: $max-rows; // always in last row } - &.__featured-reactions { - display: flex; - justify-content: end; - grid-area: f-reaction; + &__featured-reactions { + padding-top: $entity-spacer-padding-vertical; + text-align: end; min-width: max-content; } -} \ No newline at end of file + @include l-brk.on-screen-size(small) { + @include entity-limited-space-design(); + } +} diff --git a/templates/default/070-components/UI-framework/Listing/_ui-component_entitylisting.scss b/templates/default/070-components/UI-framework/Listing/_ui-component_entitylisting.scss index d04b2f0c9bf9..549919a3a6b7 100755 --- a/templates/default/070-components/UI-framework/Listing/_ui-component_entitylisting.scss +++ b/templates/default/070-components/UI-framework/Listing/_ui-component_entitylisting.scss @@ -1,4 +1,33 @@ -.c-listing-entity { +@use "../../../050-layout/basics" as l; +@use "../../../050-layout/layout_grid-auto-columns" as l-auto-grid; +@use "../../../030-tools/tool_multi-line-cap" as t-linecap; + +// so we can force entity small space design on large screens +@use "../../../070-components/UI-framework/Entity/ui-component_entity" as entity; + +$listing-column-min-width: 650px; +$grid-column-min-width: 400px; + +.c-listing-entity, +.c-listing-entity-grid { list-style: none; padding-left: 0; -} \ No newline at end of file +} + +.c-listing-entity { + @include l-auto-grid.make-grid-with-auto-columns($listing-column-min-width); +} + +.c-listing-entity-grid { + @include l-auto-grid.make-grid-with-auto-columns($grid-column-min-width); + .c-entity { + @include entity.entity-limited-space-design(); + &__container { + height: 100%; + .c-listing-property__propertyvalue:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + height: 2lh; + } + } + } +} + diff --git a/templates/default/070-components/UI-framework/Listing/_ui-component_inline.scss b/templates/default/070-components/UI-framework/Listing/_ui-component_inline.scss new file mode 100644 index 000000000000..9edc51348296 --- /dev/null +++ b/templates/default/070-components/UI-framework/Listing/_ui-component_inline.scss @@ -0,0 +1,19 @@ +@use "../../../010-settings" as s; +@use "../../../030-tools/tool_multi-line-cap" as t-cap; +@use "../../../030-tools/tool_focus-outline" as t-focus; +@use "../../../050-layout/basics/" as l; +@use "../../../050-layout/layout_breakpoints" as l-brk; + +.c-listing-inline { + padding: 0; + margin: 0; + list-style: none; + > .c-listing-inline__item { + &::after { + content: ", "; + } + &:last-child::after { + content: ""; + } + } +} diff --git a/templates/default/070-components/UI-framework/Listing/_ui-component_properties.scss b/templates/default/070-components/UI-framework/Listing/_ui-component_properties.scss index 6ab6876c386d..e499c128fbf8 100755 --- a/templates/default/070-components/UI-framework/Listing/_ui-component_properties.scss +++ b/templates/default/070-components/UI-framework/Listing/_ui-component_properties.scss @@ -1,9 +1,13 @@ +@use "../../../010-settings" as s; +@use "../../../030-tools/tool_multi-line-cap" as t-cap; +@use "../../../030-tools/tool_focus-outline" as t-focus; @use "../../../050-layout/basics/" as l; +@use "../../../050-layout/layout_breakpoints" as l-brk; -.c-listing-property__propertylabel:after { - content: ":" +.c-listing-property__propertylabel:not(:has(.glyphicon)):after { + content: ":"; } .c-listing-property + .c-listing-property { margin-top: l.$il-margin-large-vertical; -} \ No newline at end of file +} diff --git a/templates/default/070-components/_index.scss b/templates/default/070-components/_index.scss index 32208c2687f4..488645dedd60 100755 --- a/templates/default/070-components/_index.scss +++ b/templates/default/070-components/_index.scss @@ -21,6 +21,7 @@ @use "./UI-framework/Dropdown/_ui-component_dropdown.scss"; @use "./UI-framework/Dropzone/_ui-component_dropzone.scss"; @use "./UI-framework/Entity/ui-component_entity"; +@use "./UI-framework/Listing/ui-component_inline"; @use "./UI-framework/Input/_ui-component_input.scss"; @use "./UI-framework/Item/_ui-component_item.scss"; @use "./UI-framework/Launcher/ui-component_launcher"; diff --git a/templates/default/delos.css b/templates/default/delos.css index 9e75055539fa..317761afd01e 100644 --- a/templates/default/delos.css +++ b/templates/default/delos.css @@ -702,6 +702,160 @@ table.mceToolbar tbody, table.mceToolbar tr, table.mceToolbar td { min-height: 23px; } +/* + These classes are used to limit the number of rows when displaying larger chunks of text. + The mixin receives $height-in-rows as an integer. The classes il-multi-line-cap-2,3,5,10 + can be used to limit the number of rows for text to 2,3,5 or 10 lines in any template, + e.g. the Standard Listing Panels limit the property values to 3 lines using il-multi-line-cap-3 + + Technical discussion can be found in https://mantis.ilias.de/view.php?id=21583 + The background/gradient fallback can be removed as soon as all browsers support line-clamp. + */ +.t-text-more-less__toggle:checked + .t-text-more-less__label .t-text-more-less__label__more { + display: none; +} +.t-text-more-less__toggle:not(:checked) + .t-text-more-less__label .t-text-more-less__label__less { + display: none; +} +.t-text-more-less__label { + color: #4c6586; + text-decoration: none; +} +.t-text-more-less__label:hover { + color: #3a4c65; + text-decoration: underline; +} +@media screen and (min-width: 769px) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + /* edge, chrome, safari go here... */ + /* may come with next firefox 68, https://caniuse.com/#search=clamp */ + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + position: relative; + max-height: 1.5em; + overflow: hidden; + line-height: 1.5; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + content: ""; + text-align: right; + position: absolute; + bottom: 0; + right: 0; + width: 30%; + height: 1.5em; + background: linear-gradient(to right, rgba(255, 255, 255, 0), rgb(255, 255, 255) 80%); + } + @supports (-webkit-line-clamp: 2) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + display: none; + } + } + @supports (-moz-line-clamp: 2) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + overflow: hidden; + text-overflow: ellipsis; + display: -moz-box; + -moz-line-clamp: 1; + -moz-box-orient: vertical; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + display: none; + } + } +} +@media screen and (max-width: 768px) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + /* edge, chrome, safari go here... */ + /* may come with next firefox 68, https://caniuse.com/#search=clamp */ + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + position: relative; + max-height: 3em; + overflow: hidden; + line-height: 1.5; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + content: ""; + text-align: right; + position: absolute; + bottom: 0; + right: 0; + width: 30%; + height: 1.5em; + background: linear-gradient(to right, rgba(255, 255, 255, 0), rgb(255, 255, 255) 80%); + } + @supports (-webkit-line-clamp: 2) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + display: none; + } + } + @supports (-moz-line-clamp: 2) { + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + overflow: hidden; + text-overflow: ellipsis; + display: -moz-box; + -moz-line-clamp: 2; + -moz-box-orient: vertical; + } + .t-text-more-less:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body:after { + display: none; + } + } +} + +input[type=checkbox].t-text-more-less__toggle { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +input[type=checkbox].t-text-more-less__toggle:focus { + border: inherit; + box-shadow: inherit; + outline: none; + outline-offset: 0px; +} +input[type=checkbox].t-text-more-less__toggle:focus-visible { + border: inherit; + box-shadow: inherit; + outline: none; + outline-offset: 0px; +} +input[type=checkbox].t-text-more-less__toggle:focus ~ label, input[type=checkbox].t-text-more-less__toggle:focus-visible ~ label { + position: relative; + outline: 2px solid #FFFFFF; + outline-offset: 5px; +} +input[type=checkbox].t-text-more-less__toggle:focus ~ label::after, input[type=checkbox].t-text-more-less__toggle:focus-visible ~ label::after { + content: " "; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid #FFFFFF; + outline: 3px solid #0078D7; +} + /* * Normalize */ @@ -1754,6 +1908,12 @@ th { flex-wrap: wrap; align-items: center; } +.l-bar__space-keeper .l-bar__group, +.l-bar__space-keeper .l-bar__element, +.l-bar__group .l-bar__group, +.l-bar__group .l-bar__element { + flex-wrap: nowrap; +} .l-bar__space-keeper:not(:empty) { margin-bottom: calc(-1 * var(--l-bar__element__margin-bottom)); @@ -1766,15 +1926,15 @@ th { .l-bar__group > .l-bar__element { margin-bottom: var(--l-bar__element__margin-bottom); } -.l-bar__space-keeper > .l-bar__element:last-child, -.l-bar__group > .l-bar__element:last-child { - margin-right: 0; -} .l-bar__group > .l-bar__element, .l-bar__space-keeper > .l-bar__element { margin-right: var(--l-bar__gap--elements); } +.l-bar__group > .l-bar__element:last-child, +.l-bar__space-keeper > .l-bar__element:last-child { + margin-right: 0; +} .l-bar__space-keeper > .l-bar__group, .l-bar__group > .l-bar__group { @@ -2125,21 +2285,19 @@ fieldset[disabled] input[type=checkbox] { cursor: not-allowed; } -input[type=file]:focus, +input[type=file]:focus, input[type=file]:focus-visible, input[type=radio]:focus, -input[type=checkbox]:focus { - outline: none; - outline-offset: 0px; -} -input[type=file]:focus-visible, input[type=radio]:focus-visible, +input[type=checkbox]:focus, input[type=checkbox]:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -input[type=file]:focus-visible::after, +input[type=file]:focus::after, input[type=file]:focus-visible::after, +input[type=radio]:focus::after, input[type=radio]:focus-visible::after, +input[type=checkbox]:focus::after, input[type=checkbox]:focus-visible::after { content: " "; position: absolute; @@ -2199,6 +2357,37 @@ img { } } +a:has(img) { + display: inline-block; +} +a:has(img):focus { + border: inherit; + box-shadow: inherit; + outline: none; + outline-offset: 0px; +} +a:has(img):focus-visible { + border: inherit; + box-shadow: inherit; + outline: none; + outline-offset: 0px; +} +a:has(img):focus, a:has(img):focus-visible { + position: relative; + outline: 2px solid #FFFFFF; + outline-offset: 5px; +} +a:has(img):focus::after, a:has(img):focus-visible::after { + content: " "; + position: absolute; + top: -2px; + left: -2px; + right: -2px; + bottom: -2px; + border: 2px solid #FFFFFF; + outline: 3px solid #0078D7; +} + script { display: none !important; } @@ -4110,71 +4299,112 @@ hr.il-divider-with-label { width: 100%; } -.c-entity.__container { +.c-entity__container { display: grid; - grid-template-areas: "f-blocking f-blocking f-blocking f-blocking actions" "second-id f-prop f-prop f-prop actions" "second-id prim-id prim-id prim-id actions" "second-id status status status status" "second-id f-details f-details f-details f-details" "second-id availab availab availab availab" "second-id details details details details" "second-id reaction reaction f-reaction f-reaction"; - grid-template-columns: min-content auto auto min-content min-content; + grid-template-columns: max-content auto; + grid-template-rows: repeat(9, minmax(min-content, auto)); border: 1px solid #dddddd; background-color: white; padding: 4.5px 7.5px; } -.c-entity.__container > *:not(:empty) { +.c-entity__container > * { padding: 4.5px 7.5px; } -.c-entity.__blocking-conditions { - grid-area: f-blocking; - font-size: 1.115rem; +@media screen and (max-width: 768px) { + .c-entity__container { + display: block; + } } -.c-entity.__actions { - display: flex; - justify-content: end; - grid-area: actions; +.c-entity__featured-headerbar { + column-span: 2; } -.c-entity.__actions .dropdown { - height: max-content; +.c-entity__featured-headerbar > .l-bar__space-keeper { + flex-wrap: nowrap; +} +.c-entity__featured-headerbar > .l-bar__space-keeper .l-bar__group, +.c-entity__featured-headerbar > .l-bar__space-keeper .l-bar__element { + align-self: flex-start; } -.c-entity.__secondary-identifier { - grid-area: second-id; +.c-entity__secondary-identifier { + grid-column: 1; + grid-row: 1/10; } -.c-entity.__secondary-identifier.--string, .c-entity.__secondary-identifier.--shy, .c-entity.__secondary-identifier.--shylink { +.c-entity__secondary-identifier.--string, .c-entity__secondary-identifier.--shy, .c-entity__secondary-identifier.--shylink { width: 10rem; } -.c-entity.__secondary-identifier.--symbol { - min-width: 3rem; +.c-entity__secondary-identifier.--symbol img { + width: auto; } -.c-entity.__secondary-identifier.--image { - width: 15rem; +.c-entity__secondary-identifier.--image img { + max-width: 360px; } -.c-entity.__primary-identifier { - grid-area: prim-id; +.c-entity__primary-identifier { font-weight: 600; font-size: 1.5rem; } -.c-entity.__featured { - grid-area: f-prop; - font-size: 1.115rem; +.c-entity__featured { + font-size: 1rem; } -.c-entity.__personal-status { - grid-area: status; +.c-entity__workflow-actions { + padding-top: 18px; } -.c-entity.__main-details { - grid-area: f-details; +.c-entity__blocking-conditions { + padding-bottom: 18px; + font-size: 1rem; } -.c-entity.__availability { - grid-area: availab; +.c-entity__main-details { + padding-top: 18px; } -.c-entity.__details { - grid-area: details; +.c-entity__details { + font-size: 0.75rem; } -.c-entity.__reactions { - grid-area: reaction; +.c-entity__reactions { + padding-top: 18px; } -.c-entity.__featured-reactions { - display: flex; - justify-content: end; - grid-area: f-reaction; +.c-entity__reactionbar { + align-self: stretch; + align-content: flex-end; + grid-column: 2; + grid-row: 9; +} +.c-entity__featured-reactions { + padding-top: 18px; + text-align: end; min-width: max-content; } +@media screen and (max-width: 768px) { + .c-entity__container { + display: flex; + flex-direction: column; + } + .c-entity__container > * { + order: 2; + } + .c-entity__secondary-identifier { + order: 1; + } + .c-entity__secondary-identifier.--image img { + width: calc(100% + 30px); + max-width: unset; + margin-left: -15px; + margin-top: -9px; + } + .c-entity__reactionbar { + flex-grow: 1; + } +} + +.c-listing-inline { + padding: 0; + margin: 0; + list-style: none; +} +.c-listing-inline > .c-listing-inline__item::after { + content: ", "; +} +.c-listing-inline > .c-listing-inline__item:last-child::after { + content: ""; +} .c-field-tag { width: 100%; @@ -5736,7 +5966,7 @@ a[aria-disabled].il-link.link-bulky:hover { background-color: unset; } -.c-listing-property__propertylabel:after { +.c-listing-property__propertylabel:not(:has(.glyphicon)):after { content: ":"; } @@ -5950,11 +6180,49 @@ a[aria-disabled].il-link.link-bulky:hover { padding: 15px 15px; } -.c-listing-entity { +.c-listing-entity, +.c-listing-entity-grid { list-style: none; padding-left: 0; } +.c-listing-entity { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(650px, 1fr)); + gap: 15px; +} + +.c-listing-entity-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(400px, 1fr)); + gap: 15px; +} +.c-listing-entity-grid .c-entity__container { + display: flex; + flex-direction: column; +} +.c-listing-entity-grid .c-entity__container > * { + order: 2; +} +.c-listing-entity-grid .c-entity__secondary-identifier { + order: 1; +} +.c-listing-entity-grid .c-entity__secondary-identifier.--image img { + width: calc(100% + 30px); + max-width: unset; + margin-left: -15px; + margin-top: -9px; +} +.c-listing-entity-grid .c-entity__reactionbar { + flex-grow: 1; +} +.c-listing-entity-grid .c-entity__container { + height: 100%; +} +.c-listing-entity-grid .c-entity__container .c-listing-property__propertyvalue:has(.t-text-more-less__toggle:not(:checked)) .t-text-more-less__text-body { + height: 2lh; +} + .il-maincontrols-slate.disengaged { display: none; } @@ -15309,16 +15577,12 @@ div.ilc_va_icont_VAccordICont { border-bottom: 1px solid #dddddd; background-color: white; } -.ilAwarenessItem > div[role=button]:focus-visible:focus { - outline: none; - outline-offset: 0px; -} -.ilAwarenessItem > div[role=button]:focus-visible:focus-visible { +.ilAwarenessItem > div[role=button]:focus-visible:focus, .ilAwarenessItem > div[role=button]:focus-visible:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -.ilAwarenessItem > div[role=button]:focus-visible:focus-visible::after { +.ilAwarenessItem > div[role=button]:focus-visible:focus::after, .ilAwarenessItem > div[role=button]:focus-visible:focus-visible::after { content: " "; position: absolute; top: -2px; @@ -17157,16 +17421,12 @@ p#copg-auto-save { position: static; } -.ilPageVideo button:focus, .ilPageAudio button:focus { - outline: none; - outline-offset: 0px; -} -.ilPageVideo button:focus-visible, .ilPageAudio button:focus-visible { +.ilPageVideo button:focus, .ilPageVideo button:focus-visible, .ilPageAudio button:focus, .ilPageAudio button:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -.ilPageVideo button:focus-visible::after, .ilPageAudio button:focus-visible::after { +.ilPageVideo button:focus::after, .ilPageVideo button:focus-visible::after, .ilPageAudio button:focus::after, .ilPageAudio button:focus-visible::after { content: " "; position: absolute; top: -2px; @@ -18220,18 +18480,15 @@ a.mailunread, a.mailunread:visited { background-image: url("../images/media/bigplay.svg"); } -.mejs__overlay-button:focus, -.ilPlayerPreviewPlayButton:focus { - outline: none; - outline-offset: 0px; -} -.mejs__overlay-button:focus-visible, +.mejs__overlay-button:focus, .mejs__overlay-button:focus-visible, +.ilPlayerPreviewPlayButton:focus, .ilPlayerPreviewPlayButton:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -.mejs__overlay-button:focus-visible::after, +.mejs__overlay-button:focus::after, .mejs__overlay-button:focus-visible::after, +.ilPlayerPreviewPlayButton:focus::after, .ilPlayerPreviewPlayButton:focus-visible::after { content: " "; position: absolute; @@ -18243,16 +18500,12 @@ a.mailunread, a.mailunread:visited { outline: 3px solid #0078D7; } -.mejs__time-total:focus { - outline: none; - outline-offset: 0px; -} -.mejs__time-total:focus-visible { +.mejs__time-total:focus, .mejs__time-total:focus-visible { position: relative; outline: 1px solid #FFFFFF; outline-offset: 2px; } -.mejs__time-total:focus-visible::after { +.mejs__time-total:focus::after, .mejs__time-total:focus-visible::after { content: " "; position: absolute; top: -1px; @@ -20325,18 +20578,15 @@ a.ilMediaLightboxClose:hover { #ilTab > li > .c-tooltip__container > a:focus-visible::after { content: none; } -#ilTab > li > a:focus, -#ilTab > li > .c-tooltip__container > a:focus { - outline: none; - outline-offset: 0px; -} -#ilTab > li > a:focus-visible, +#ilTab > li > a:focus, #ilTab > li > a:focus-visible, +#ilTab > li > .c-tooltip__container > a:focus, #ilTab > li > .c-tooltip__container > a:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -#ilTab > li > a:focus-visible::after, +#ilTab > li > a:focus::after, #ilTab > li > a:focus-visible::after, +#ilTab > li > .c-tooltip__container > a:focus::after, #ilTab > li > .c-tooltip__container > a:focus-visible::after { content: " "; position: absolute; @@ -20407,18 +20657,15 @@ a.ilMediaLightboxClose:hover { #ilSubTab > li > .c-tooltip__container > a:focus-visible::after { content: none; } -#ilSubTab > li > a:focus, -#ilSubTab > li > .c-tooltip__container > a:focus { - outline: none; - outline-offset: 0px; -} -#ilSubTab > li > a:focus-visible, +#ilSubTab > li > a:focus, #ilSubTab > li > a:focus-visible, +#ilSubTab > li > .c-tooltip__container > a:focus, #ilSubTab > li > .c-tooltip__container > a:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -#ilSubTab > li > a:focus-visible::after, +#ilSubTab > li > a:focus::after, #ilSubTab > li > a:focus-visible::after, +#ilSubTab > li > .c-tooltip__container > a:focus::after, #ilSubTab > li > .c-tooltip__container > a:focus-visible::after { content: " "; position: absolute; @@ -20581,16 +20828,12 @@ img.ilUserXXSmall { .webdav-view-control { text-align: center; } -.webdav-view-control:focus { - outline: none; - outline-offset: 0px; -} -.webdav-view-control:focus-visible { +.webdav-view-control:focus, .webdav-view-control:focus-visible { position: relative; outline: 2px solid #FFFFFF; outline-offset: 5px; } -.webdav-view-control:focus-visible::after { +.webdav-view-control:focus::after, .webdav-view-control:focus-visible::after { content: " "; position: absolute; top: -2px; @@ -20601,15 +20844,6 @@ img.ilUserXXSmall { outline: 3px solid #0078D7; } -/* - These classes are used to limit the number of rows when displaying larger chunks of text. - The mixin receives $height-in-rows as an integer. The classes il-multi-line-cap-2,3,5,10 - can be used to limit the number of rows for text to 2,3,5 or 10 lines in any template, - e.g. the Standard Listing Panels limit the property values to 3 lines using il-multi-line-cap-3 - - Technical discussion can be found in https://mantis.ilias.de/view.php?id=21583 - The background/gradient fallback can be removed as soon as all browsers support line-clamp. - */ /* * Hacks & Tweaks */ diff --git a/templates/default/delos.scss b/templates/default/delos.scss index 45a1b2b15d31..c1ce11074878 100755 --- a/templates/default/delos.scss +++ b/templates/default/delos.scss @@ -11,6 +11,7 @@ // ## Tools // include patterns and tools with @use in component when needed +@forward "./030-tools"; // avoid creating new tools like these that generate CSS utility classes // ## Normalize @use "./040-normalize/" as *; @@ -30,4 +31,4 @@ // # Relative paths differences // SCSS paths are relativ to the SCSS file -// CSS paths (linked fonts) are relative to the compiled template CSS \ No newline at end of file +// CSS paths (linked fonts) are relative to the compiled template CSS