From 774d452f44255efae30bb95ba52415ebb41f8502 Mon Sep 17 00:00:00 2001 From: Tim Schmitz Date: Wed, 26 Aug 2026 14:53:38 +0200 Subject: [PATCH] UI: introduce secondary prompt content --- .../ControlCenter/Content/ContentFactory.php | 29 ++++++++------- .../Content/ContentFactoryInterface.php | 11 +++--- .../ControlCenter/ControlCenterGUI.php | 16 ++++----- .../UI/resources/js/Prompt/dist/prompt.min.js | 2 +- .../UI/resources/js/Prompt/rollup.config.js | 4 +-- .../resources/js/Prompt/src/prompt.class.js | 4 +-- .../ILIAS/UI/src/Component/Chart/ScaleBar.php | 3 +- .../UI/src/Component/Prompt/State/Factory.php | 3 +- .../Component/Chart/ScaleBar.php | 13 ++++++- .../Component/Prompt/State/Factory.php | 14 ++++---- .../Component/Prompt/State/Renderer.php | 19 ++++++---- .../Component/Prompt/State/State.php | 36 +++++++++++++------ .../default/Prompt/tpl.promptstate.html | 7 ++-- 13 files changed, 102 insertions(+), 59 deletions(-) diff --git a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactory.php b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactory.php index 65166a5bde3f..5ea3a541f7a1 100644 --- a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactory.php +++ b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactory.php @@ -21,7 +21,6 @@ namespace ILIAS\MetaData\OERHarvester\ControlCenter\Content; use ILIAS\MetaData\OERHarvester\ControlCenter\State\Status; -use ILIAS\UI\Component\Modal\RoundTrip as RoundTripModal; use ILIAS\UI\Factory as UIFactory; use ILIAS\MetaData\OERHarvester\ControlCenter\State\StateInfoInterface; use ILIAS\MetaData\Presentation\UtilitiesInterface as PresentationUtilities; @@ -31,6 +30,7 @@ use ILIAS\MetaData\OERHarvester\ControlCenter\Http\LinkFactoryInterface; use ILIAS\MetaData\OERHarvester\ControlCenter\State\Action; use ILIAS\MetaData\Copyright\RepositoryInterface; +use ILIAS\UI\Component\Prompt\State\State as PromptState; class ContentFactory implements ContentFactoryInterface { @@ -42,20 +42,20 @@ public function __construct( ) { } - public function getInfoContent( + public function getInfoShowState( int $ref_id, int $obj_id, string $type, StateInfoInterface $state_info - ): RoundTripModal { + ): PromptState { $message = $this->getStatusMessage($state_info); $scale = $this->getStatusOverview($state_info); $actions = $this->getActions($ref_id, $obj_id, $type, $state_info); - return $this->ui_factory->modal()->roundtrip( - $this->presentation_utilities->txt('md_publishing_center_title'), - [$message, $scale] - )->withActionButtons($actions); + return $this->ui_factory->prompt()->state()->show( + $message->withButtons($actions), + $scale + )->withTitle($this->presentation_utilities->txt('md_publishing_center_title')); } protected function getStatusOverview(StateInfoInterface $state_info): ScaleBar @@ -136,21 +136,20 @@ protected function getActions( return $buttons; } - public function getConfirmationContent( + public function getConfirmationShowState( int $ref_id, int $obj_id, string $type, Action $action, bool $is_last_reference - ): RoundTripModal { - $modal_content = []; + ): PromptState { $message = match ($action) { Action::WITHDRAW => $this->presentation_utilities->txt('md_publishing_confirmation_info_withdraw'), Action::ACCEPT => $this->presentation_utilities->txt('md_publishing_confirmation_info_accept'), Action::REJECT => $this->presentation_utilities->txt('md_publishing_confirmation_info_reject'), default => '' }; - $modal_content[] = $this->ui_factory->messageBox()->confirmation($message); + $primary_message_box = $this->ui_factory->messageBox()->confirmation($message); $title = match ($action) { Action::WITHDRAW => $this->presentation_utilities->txt('md_publishing_confirmation_withdraw'), Action::ACCEPT => $this->presentation_utilities->txt('md_publishing_confirmation_accept'), @@ -158,11 +157,12 @@ public function getConfirmationContent( default => '' }; + $secondary_message_boxes = []; if ( ($action === Action::REJECT || $action === Action::WITHDRAW) && $is_last_reference ) { - $modal_content[] = $this->ui_factory->messageBox()->info( + $secondary_message_boxes[] = $this->ui_factory->messageBox()->info( $this->presentation_utilities->txt('md_publishing_last_reference_info') ); } @@ -180,7 +180,10 @@ public function getConfirmationContent( il.UI.prompt.get(promptId).show('$action'); });" ); - return $this->ui_factory->modal()->roundtrip($title, $modal_content)->withActionButtons([$button]); + return $this->ui_factory->prompt()->state()->show( + $primary_message_box->withButtons([$button]), + ...$secondary_message_boxes + )->withTitle($title); } public function getSuccessMessage(Action $action): string diff --git a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactoryInterface.php b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactoryInterface.php index 940bface1d36..b0534cd86074 100644 --- a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactoryInterface.php +++ b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/Content/ContentFactoryInterface.php @@ -20,26 +20,27 @@ namespace ILIAS\MetaData\OERHarvester\ControlCenter\Content; -use ILIAS\UI\Component\Modal\RoundTrip as RoundTripModal; use ILIAS\MetaData\OERHarvester\ControlCenter\State\StateInfoInterface; use ILIAS\MetaData\OERHarvester\ControlCenter\State\Action; +use ILIAS\UI\Component\Prompt\State\State as PromptState; +use ILIAS\UI\Implementation\Component\Chart\ScaleBar; interface ContentFactoryInterface { - public function getInfoContent( + public function getInfoShowState( int $ref_id, int $obj_id, string $type, StateInfoInterface $state_info - ): RoundTripModal; + ): PromptState; - public function getConfirmationContent( + public function getConfirmationShowState( int $ref_id, int $obj_id, string $type, Action $action, bool $is_last_reference - ): RoundTripModal; + ): PromptState; public function getSuccessMessage(Action $action): string; } diff --git a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ControlCenterGUI.php b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ControlCenterGUI.php index 7b2f5a523a60..f99be28410b2 100755 --- a/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ControlCenterGUI.php +++ b/components/ILIAS/MetaData/classes/OERHarvester/ControlCenter/ControlCenterGUI.php @@ -113,8 +113,8 @@ protected function isCommandAvailable(Command $cmd): bool protected function view(): void { - $content = $this->content_factory->getInfoContent($this->ref_id, $this->obj_id, $this->type, $this->state_info); - echo $this->ui_renderer->renderAsync($this->ui_factory->prompt()->state()->show($content)); + $show_state = $this->content_factory->getInfoShowState($this->ref_id, $this->obj_id, $this->type, $this->state_info); + echo $this->ui_renderer->renderAsync($show_state); exit; } @@ -144,14 +144,14 @@ protected function publish(): void protected function withdraw(): void { - $content = $this->content_factory->getConfirmationContent( + $show_state = $this->content_factory->getConfirmationShowState( $this->ref_id, $this->obj_id, $this->type, Action::WITHDRAW, $this->object_handler->isOnlyReference($this->ref_id) ); - echo $this->ui_renderer->renderAsync($this->ui_factory->prompt()->state()->show($content)); + echo $this->ui_renderer->renderAsync($show_state); exit; } @@ -175,14 +175,14 @@ protected function submit(): void protected function accept(): void { - $content = $this->content_factory->getConfirmationContent( + $show_state = $this->content_factory->getConfirmationShowState( $this->ref_id, $this->obj_id, $this->type, Action::ACCEPT, $this->object_handler->isOnlyReference($this->ref_id) ); - echo $this->ui_renderer->renderAsync($this->ui_factory->prompt()->state()->show($content)); + echo $this->ui_renderer->renderAsync($show_state); exit; } @@ -198,14 +198,14 @@ protected function confirmAccept(): void protected function reject(): void { - $content = $this->content_factory->getConfirmationContent( + $show_state = $this->content_factory->getConfirmationShowState( $this->ref_id, $this->obj_id, $this->type, Action::REJECT, $this->object_handler->isOnlyReference($this->ref_id) ); - echo $this->ui_renderer->renderAsync($this->ui_factory->prompt()->state()->show($content)); + echo $this->ui_renderer->renderAsync($show_state); exit; } diff --git a/components/ILIAS/UI/resources/js/Prompt/dist/prompt.min.js b/components/ILIAS/UI/resources/js/Prompt/dist/prompt.min.js index 788838ffbdba..bfbceaa00056 100644 --- a/components/ILIAS/UI/resources/js/Prompt/dist/prompt.min.js +++ b/components/ILIAS/UI/resources/js/Prompt/dist/prompt.min.js @@ -12,4 +12,4 @@ * https://www.ilias.de * https://github.com/ILIAS-eLearning */ -!function(t,e){"use strict";class r{#t;#e;#r;constructor(t,e){if(this.#t=t,this.#e=document.getElementById(e),null===this.#e)throw new Error(`Could not find a Prompt for id '${e}'.`);this.#r=this.#e.getElementsByTagName("dialog").item(0)}show(t){this.load(t),this.#r.showModal()}close(){this.#r.close()}async load(t,e={}){await fetch(t,e).then((t=>t.text())).then((t=>{const e=(new this.#t).parseFromString(t,"text/html"),r=e.querySelector('section[data-section="il-prompt-state__title"]'),n=e.querySelector('section[data-section="il-prompt-state__contents"]'),o=e.querySelector('section[data-section="il-prompt-state__buttons"]'),i=e.querySelector('section[data-section="il-prompt-state__command"]'),s=e.querySelector('section[data-section="il-prompt-state__parameters"]'),a=e.querySelector("script"),c=this.#r.querySelector("span.il-prompt__title"),p=this.#r.querySelector("div.il-prompt__contents"),l=this.#r.querySelector("div.il-prompt__buttons");c.innerHTML=r.innerHTML,p.innerHTML=n.innerHTML,l.innerHTML=o.innerHTML,this.#n(p),this.#o(p),a&&this.#i(a.text);const m=[];return s.querySelectorAll("data").forEach((t=>{m[t.innerHTML.trim()]=t.getAttribute("value")})),{cmd:i.innerHTML.trim(),params:m}})).then((t=>{const e=t.cmd,{params:r}=t;"close"===e&&this.close(),"redirect"===e&&window.location.replace(r.redirect)}))}#i(t){const e=this.#e.querySelector("section.il-prompt__scripts"),r=document.createElement("script");r.text=t,e.innerHTML="",e.appendChild(r)}#n(t){t.getElementsByTagName("form").forEach((t=>{t.addEventListener("submit",(e=>{e.preventDefault(),this.load(t.action,{method:t.method,body:new FormData(t)})}))}))}#o(t){t.getElementsByTagName("a").forEach((t=>{const{target:e}=t;if("_blank"!==e){const e=t.href;t.addEventListener("click",(()=>this.load(e))),t.removeAttribute("href")}}))}}t.UI=t.UI||{},t.UI.prompt=new class{#t;#s=[];constructor(t){this.#t=t}init(t){if(void 0!==this.#s[t])throw new Error(`Prompt with id '${t}' has already been initialized.`);this.#s[t]=new r(this.#t,t)}get(t){return this.#s[t]??null}}(e)}(il,DOMParser); +!function(t,e){"use strict";class r{#t;#e;#r;constructor(t,e){if(this.#t=t,this.#e=document.getElementById(e),null===this.#e)throw new Error(`Could not find a Prompt for id '${e}'.`);this.#r=this.#e.getElementsByTagName("dialog").item(0)}show(t){this.load(t),this.#r.showModal()}close(){this.#r.close()}async load(t,e={}){await fetch(t,e).then(t=>t.text()).then(t=>{const e=(new this.#t).parseFromString(t,"text/html"),r=e.querySelector('section[data-section="il-prompt-state__title"]'),o=e.querySelector('section[data-section="il-prompt-state__contents"]'),n=e.querySelector('section[data-section="il-prompt-state__buttons"]'),i=e.querySelector('section[data-section="il-prompt-state__command"]'),s=e.querySelector('section[data-section="il-prompt-state__parameters"]'),c=e.querySelector("script"),a=this.#r.querySelector("span.il-prompt__title"),p=this.#r.querySelector("div.il-prompt__contents"),l=this.#r.querySelector("div.il-prompt__buttons");a.innerHTML=r.innerHTML,p.innerHTML=o.innerHTML,l.innerHTML=n.innerHTML,this.#o(p),this.#n(p),c&&this.#i(c.text);const m=[];return s.querySelectorAll("data").forEach(t=>{m[t.innerHTML.trim()]=t.getAttribute("value")}),{cmd:i.innerHTML.trim(),params:m}}).then(t=>{const e=t.cmd,{params:r}=t;"close"===e&&this.close(),"redirect"===e&&window.location.replace(r.redirect)})}#i(t){const e=this.#e.querySelector("section.il-prompt__scripts"),r=document.createElement("script");r.text=t,e.innerHTML="",e.appendChild(r)}#o(t){t.querySelectorAll("form").forEach(t=>{t.addEventListener("submit",e=>{e.preventDefault(),this.load(t.action,{method:t.method,body:new FormData(t)})})})}#n(t){t.querySelectorAll("a").forEach(t=>{const{target:e}=t;if("_blank"!==e){const e=t.href;t.addEventListener("click",()=>this.load(e)),t.removeAttribute("href")}})}}t.UI=t.UI||{},t.UI.prompt=new class{#t;#s=[];constructor(t){this.#t=t}init(t){if(void 0!==this.#s[t])throw new Error(`Prompt with id '${t}' has already been initialized.`);this.#s[t]=new r(this.#t,t)}get(t){return this.#s[t]??null}}(e)}(il,DOMParser); diff --git a/components/ILIAS/UI/resources/js/Prompt/rollup.config.js b/components/ILIAS/UI/resources/js/Prompt/rollup.config.js index f815677b5a1a..b2bbcc13ce48 100644 --- a/components/ILIAS/UI/resources/js/Prompt/rollup.config.js +++ b/components/ILIAS/UI/resources/js/Prompt/rollup.config.js @@ -15,8 +15,8 @@ ******************************************************************** */ import terser from '@rollup/plugin-terser'; -import copyright from '../../../../../../scripts/Copyright-Checker/copyright'; -import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright'; +import copyright from '../../../../../../scripts/Copyright-Checker/copyright.js'; +import preserveCopyright from '../../../../../../scripts/Copyright-Checker/preserveCopyright.js'; export default { input: './src/prompt.js', diff --git a/components/ILIAS/UI/resources/js/Prompt/src/prompt.class.js b/components/ILIAS/UI/resources/js/Prompt/src/prompt.class.js index e34ae4065d9a..11d345223ec8 100644 --- a/components/ILIAS/UI/resources/js/Prompt/src/prompt.class.js +++ b/components/ILIAS/UI/resources/js/Prompt/src/prompt.class.js @@ -127,7 +127,7 @@ export default class Prompt { * @return {void} */ #captureForms(doc) { - const forms = doc.getElementsByTagName('form'); + const forms = doc.querySelectorAll('form'); forms.forEach( (form) => { form.addEventListener('submit', (e) => { @@ -146,7 +146,7 @@ export default class Prompt { * @return {void} */ #captureLinks(doc) { - const links = doc.getElementsByTagName('a'); + const links = doc.querySelectorAll('a'); links.forEach( (lnk) => { const { target } = lnk; diff --git a/components/ILIAS/UI/src/Component/Chart/ScaleBar.php b/components/ILIAS/UI/src/Component/Chart/ScaleBar.php index dfa924cff4ff..590c8bcac812 100755 --- a/components/ILIAS/UI/src/Component/Chart/ScaleBar.php +++ b/components/ILIAS/UI/src/Component/Chart/ScaleBar.php @@ -21,11 +21,12 @@ namespace ILIAS\UI\Component\Chart; use ILIAS\UI\Component\Component; +use ILIAS\UI\Component\Prompt\IsPromptContent; /** * Interface Scale Bars */ -interface ScaleBar extends Component +interface ScaleBar extends Component, IsPromptContent { /** * Sets a key value pair as items for the list. Key is used as title and value is a boolean marking highlighted values. diff --git a/components/ILIAS/UI/src/Component/Prompt/State/Factory.php b/components/ILIAS/UI/src/Component/Prompt/State/Factory.php index 8d2aacd0553e..970ff4856a66 100644 --- a/components/ILIAS/UI/src/Component/Prompt/State/Factory.php +++ b/components/ILIAS/UI/src/Component/Prompt/State/Factory.php @@ -48,7 +48,8 @@ interface Factory * @return \ILIAS\UI\Component\Prompt\State\State */ public function show( - \ILIAS\UI\Component\Prompt\IsPromptContent $content + \ILIAS\UI\Component\Prompt\IsPromptContent $primary_content, + \ILIAS\UI\Component\Prompt\IsPromptContent ...$secondary_content ): State; /** diff --git a/components/ILIAS/UI/src/Implementation/Component/Chart/ScaleBar.php b/components/ILIAS/UI/src/Implementation/Component/Chart/ScaleBar.php index 9c1f2808d702..2e607565829c 100755 --- a/components/ILIAS/UI/src/Implementation/Component/Chart/ScaleBar.php +++ b/components/ILIAS/UI/src/Implementation/Component/Chart/ScaleBar.php @@ -22,12 +22,13 @@ use ILIAS\UI\Component as C; use ILIAS\UI\Implementation\Component\ComponentHelper; +use ILIAS\UI\Implementation\Component\Prompt\IsPromptContentInternal; /** * Class ScaleBar * @package ILIAS\UI\Implementation\Component\Listing\Descriptive */ -class ScaleBar implements C\Chart\ScaleBar +class ScaleBar implements C\Chart\ScaleBar, IsPromptContentInternal { use ComponentHelper; @@ -55,4 +56,14 @@ public function getItems(): array { return $this->items; } + + public function getPromptTitle(): string + { + return ''; + } + + public function getPromptButtons(): array + { + return []; + } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Factory.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Factory.php index ebd99108be1e..0d662824dbe0 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Factory.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Factory.php @@ -26,21 +26,21 @@ class Factory implements I\State\Factory { - public function show(I\IsPromptContent $content): State - { - return new State($content); + public function show( + I\IsPromptContent $primary_content, + I\IsPromptContent ...$secondary_content + ): State { + return new State($primary_content)->withSecondaryContent(...$secondary_content); } public function close(): State { - return (new State(null)) - ->withCloseModal(true); + return new State(null)->withCloseModal(true); } public function redirect(URI $redirect): State { - return (new State(null)) - ->withRedirect($redirect); + return new State(null)->withRedirect($redirect); } } diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php index d7c22257d2b5..8f3ff1d86a17 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/Renderer.php @@ -53,19 +53,26 @@ protected function renderState(State $component, RendererInterface $default_rend $tpl->parseCurrentBlock(); } - $content_component = $component->getContent(); - if ($content_component === null) { + $secondary_content_components = $component->getSecondaryContent(); + foreach ($secondary_content_components as $secondary_content_component) { + $tpl->setCurrentBlock('secondary'); + $tpl->setVariable('SECONDARY_CONTENT', $default_renderer->render($secondary_content_component)); + $tpl->parseCurrentBlock(); + } + + $primary_content_component = $component->getPrimaryContent(); + if ($primary_content_component === null) { return $tpl->get(); } - $tpl->setVariable('CONTENT', $default_renderer->render($content_component)); + $tpl->setVariable('PRIMARY_CONTENT', $default_renderer->render($primary_content_component)); $tpl->setVariable('TITLE', $component->getTitle()); $buttons = $component->getButtons(); - if ($content_component instanceof \ILIAS\UI\Component\Input\Container\Form\Form) { + if ($primary_content_component instanceof \ILIAS\UI\Component\Input\Container\Form\Form) { $submit_button = $this->getUIFactory()->button()->standard( - $content_component->getSubmitLabel() ?? $this->txt("save"), - $content_component->getSubmitSignal() + $primary_content_component->getSubmitLabel() ?? $this->txt("save"), + $primary_content_component->getSubmitSignal() ); $buttons[] = $submit_button; } diff --git a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/State.php b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/State.php index 36e85c616040..ac7371a5fc70 100644 --- a/components/ILIAS/UI/src/Implementation/Component/Prompt/State/State.php +++ b/components/ILIAS/UI/src/Implementation/Component/Prompt/State/State.php @@ -33,22 +33,23 @@ class State implements I\State\State { use ComponentHelper; - public const CMD_CLOSE = 'close'; - public const CMD_REDIRECT = 'redirect'; + public const string CMD_CLOSE = 'close'; + public const string CMD_REDIRECT = 'redirect'; protected array $buttons = []; protected string $cmd = 'show'; protected array $params = []; protected string $title = ''; + protected array $secondary_content = []; public function __construct( - protected ?I\IsPromptContent $content + protected ?I\IsPromptContent $primary_content ) { } public function getTitle(): string { - return $this->title ? $this->title : $this->content->getPromptTitle(); + return $this->title ? $this->title : $this->primary_content->getPromptTitle(); } public function withTitle(string $title): self @@ -58,27 +59,42 @@ public function withTitle(string $title): self return $clone; } - public function withContent(I\IsPromptContent $content): self + public function withPrimaryContent(I\IsPromptContent $content): self { $clone = clone $this; - $clone->content = $content; + $clone->primary_content = $content; + return $clone; + } + + public function withSecondaryContent(I\IsPromptContent ...$content): self + { + $clone = clone $this; + $clone->secondary_content = $content; return $clone; } /** * @return Component[] */ - public function getContent(): ?I\IsPromptContent + public function getPrimaryContent(): ?I\IsPromptContent + { + return $this->primary_content; + } + + /** + * @return I\IsPromptContent[] + */ + public function getSecondaryContent(): array { - return $this->content; + return $this->secondary_content; } /** - * @return Button[] + * @return Button\Button[] */ public function getButtons(): array { - return $this->content->getPromptButtons(); + return $this->primary_content->getPromptButtons(); } public function withCloseModal(bool $flag): self diff --git a/components/ILIAS/UI/src/templates/default/Prompt/tpl.promptstate.html b/components/ILIAS/UI/src/templates/default/Prompt/tpl.promptstate.html index 47459ef5831f..00bcf32a5aad 100644 --- a/components/ILIAS/UI/src/templates/default/Prompt/tpl.promptstate.html +++ b/components/ILIAS/UI/src/templates/default/Prompt/tpl.promptstate.html @@ -3,7 +3,10 @@ {TITLE}
- {CONTENT} + {PRIMARY_CONTENT} + + {SECONDARY_CONTENT} +
{BUTTONS} @@ -16,4 +19,4 @@ {KEY}
- \ No newline at end of file +