diff --git a/ts/WoltLabSuite/Core/Api/Styles/ChangeStyle.ts b/ts/WoltLabSuite/Core/Api/Styles/ChangeStyle.ts new file mode 100644 index 00000000000..fe89035d135 --- /dev/null +++ b/ts/WoltLabSuite/Core/Api/Styles/ChangeStyle.ts @@ -0,0 +1,17 @@ +/** + * Change the style of the current user. + * + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ + +import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; +import { fromInfallibleApiRequest } from "../Result"; + +export function changeStyle(styleId: number): Promise<[]> { + return fromInfallibleApiRequest(() => { + return prepareRequest(`${window.WSC_RPC_API_URL}core/styles/${styleId}/change`).post().fetchAsJson(); + }); +} diff --git a/ts/WoltLabSuite/Core/Api/Styles/GetStyleChooser.ts b/ts/WoltLabSuite/Core/Api/Styles/GetStyleChooser.ts new file mode 100644 index 00000000000..c92399da245 --- /dev/null +++ b/ts/WoltLabSuite/Core/Api/Styles/GetStyleChooser.ts @@ -0,0 +1,21 @@ +/** + * Change the style of the current user. + * + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ + +import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend"; +import { fromInfallibleApiRequest } from "../Result"; + +type Response = { + template: string; +}; + +export function getStyleChooser(): Promise { + return fromInfallibleApiRequest(() => { + return prepareRequest(`${window.WSC_RPC_API_URL}core/styles/chooser`).get().fetchAsJson(); + }); +} diff --git a/ts/WoltLabSuite/Core/Controller/Style/Changer.ts b/ts/WoltLabSuite/Core/Controller/Style/Changer.ts index 44f080d4571..1596805f17e 100644 --- a/ts/WoltLabSuite/Core/Controller/Style/Changer.ts +++ b/ts/WoltLabSuite/Core/Controller/Style/Changer.ts @@ -7,10 +7,11 @@ * @woltlabExcludeBundle all */ -import * as Ajax from "../../Ajax"; -import * as Language from "../../Language"; -import UiDialog from "../../Ui/Dialog"; -import { DialogCallbackSetup } from "../../Ui/Dialog/Data"; +import { getPhrase } from "../../Language"; +import { changeStyle } from "WoltLabSuite/Core/Api/Styles/ChangeStyle"; +import { dialogFactory } from "WoltLabSuite/Core/Component/Dialog"; +import { promiseMutex } from "WoltLabSuite/Core/Helper/PromiseMutex"; +import { getStyleChooser } from "WoltLabSuite/Core/Api/Styles/GetStyleChooser"; class ControllerStyleChanger { /** @@ -18,59 +19,43 @@ class ControllerStyleChanger { */ constructor() { document.querySelectorAll(".jsButtonStyleChanger").forEach((link: HTMLAnchorElement) => { - link.addEventListener("click", (ev) => this.showDialog(ev)); + link.addEventListener( + "click", + promiseMutex((ev) => this.showDialog(ev)), + ); }); } /** * Loads and displays the style change dialog. */ - showDialog(event: MouseEvent): void { + async showDialog(event: MouseEvent): Promise { event.preventDefault(); - UiDialog.open(this); - } + const { template } = await getStyleChooser(); + const dialog = dialogFactory().fromHtml(template).withoutControls(); + + dialog.content.querySelectorAll(".styleList > li").forEach((style: HTMLLIElement) => { + style.classList.add("pointer"); + style.addEventListener("click", (event) => { + event.preventDefault(); + + promiseMutex(() => this.#changeStyle(style)); + }); + }); - _dialogSetup(): ReturnType { - return { - id: "styleChanger", - options: { - disableContentPadding: true, - title: Language.get("wcf.style.changeStyle"), - }, - source: { - data: { - actionName: "getStyleChooser", - className: "wcf\\data\\style\\StyleAction", - }, - after: (content) => { - content.querySelectorAll(".styleList > li").forEach((style: HTMLLIElement) => { - style.classList.add("pointer"); - style.addEventListener("click", (ev) => this.click(ev)); - }); - }, - }, - }; + dialog.show(getPhrase("wcf.style.changeStyle")); } /** * Changes the style and reloads current page. */ - private click(event: MouseEvent): void { - event.preventDefault(); + async #changeStyle(style: HTMLLIElement): Promise { + const styleId = parseInt(style.dataset.styleId!, 10); - const listElement = event.currentTarget as HTMLLIElement; + await changeStyle(styleId); - Ajax.apiOnce({ - data: { - actionName: "changeStyle", - className: "wcf\\data\\style\\StyleAction", - objectIDs: [listElement.dataset.styleId], - }, - success: function () { - window.location.reload(); - }, - }); + window.location.reload(); } } @@ -89,5 +74,5 @@ export function setup(): void { * Loads and displays the style change dialog. */ export function showDialog(event: MouseEvent): void { - controllerStyleChanger.showDialog(event); + void controllerStyleChanger.showDialog(event); } diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/ChangeStyle.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/ChangeStyle.js new file mode 100644 index 00000000000..9b6ed4c246a --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/ChangeStyle.js @@ -0,0 +1,18 @@ +/** + * Change the style of the current user. + * + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ +define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.changeStyle = changeStyle; + function changeStyle(styleId) { + return (0, Result_1.fromInfallibleApiRequest)(() => { + return (0, Backend_1.prepareRequest)(`${window.WSC_RPC_API_URL}core/styles/${styleId}/change`).post().fetchAsJson(); + }); + } +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/GetStyleChooser.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/GetStyleChooser.js new file mode 100644 index 00000000000..d660e393baa --- /dev/null +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Api/Styles/GetStyleChooser.js @@ -0,0 +1,18 @@ +/** + * Change the style of the current user. + * + * @author Olaf Braun + * @copyright 2001-2025 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.3 + */ +define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "../Result"], function (require, exports, Backend_1, Result_1) { + "use strict"; + Object.defineProperty(exports, "__esModule", { value: true }); + exports.getStyleChooser = getStyleChooser; + function getStyleChooser() { + return (0, Result_1.fromInfallibleApiRequest)(() => { + return (0, Backend_1.prepareRequest)(`${window.WSC_RPC_API_URL}core/styles/chooser`).get().fetchAsJson(); + }); + } +}); diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Style/Changer.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Style/Changer.js index 9abca5c1e38..08d4ce993c9 100644 --- a/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Style/Changer.js +++ b/wcfsetup/install/files/js/WoltLabSuite/Core/Controller/Style/Changer.js @@ -6,67 +6,43 @@ * @license GNU Lesser General Public License * @woltlabExcludeBundle all */ -define(["require", "exports", "tslib", "../../Ajax", "../../Language", "../../Ui/Dialog"], function (require, exports, tslib_1, Ajax, Language, Dialog_1) { +define(["require", "exports", "../../Language", "WoltLabSuite/Core/Api/Styles/ChangeStyle", "WoltLabSuite/Core/Component/Dialog", "WoltLabSuite/Core/Helper/PromiseMutex", "WoltLabSuite/Core/Api/Styles/GetStyleChooser"], function (require, exports, Language_1, ChangeStyle_1, Dialog_1, PromiseMutex_1, GetStyleChooser_1) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.setup = setup; exports.showDialog = showDialog; - Ajax = tslib_1.__importStar(Ajax); - Language = tslib_1.__importStar(Language); - Dialog_1 = tslib_1.__importDefault(Dialog_1); class ControllerStyleChanger { /** * Adds the style changer to the bottom navigation. */ constructor() { document.querySelectorAll(".jsButtonStyleChanger").forEach((link) => { - link.addEventListener("click", (ev) => this.showDialog(ev)); + link.addEventListener("click", (0, PromiseMutex_1.promiseMutex)((ev) => this.showDialog(ev))); }); } /** * Loads and displays the style change dialog. */ - showDialog(event) { + async showDialog(event) { event.preventDefault(); - Dialog_1.default.open(this); - } - _dialogSetup() { - return { - id: "styleChanger", - options: { - disableContentPadding: true, - title: Language.get("wcf.style.changeStyle"), - }, - source: { - data: { - actionName: "getStyleChooser", - className: "wcf\\data\\style\\StyleAction", - }, - after: (content) => { - content.querySelectorAll(".styleList > li").forEach((style) => { - style.classList.add("pointer"); - style.addEventListener("click", (ev) => this.click(ev)); - }); - }, - }, - }; + const { template } = await (0, GetStyleChooser_1.getStyleChooser)(); + const dialog = (0, Dialog_1.dialogFactory)().fromHtml(template).withoutControls(); + dialog.content.querySelectorAll(".styleList > li").forEach((style) => { + style.classList.add("pointer"); + style.addEventListener("click", (event) => { + event.preventDefault(); + (0, PromiseMutex_1.promiseMutex)(() => this.#changeStyle(style)); + }); + }); + dialog.show((0, Language_1.getPhrase)("wcf.style.changeStyle")); } /** * Changes the style and reloads current page. */ - click(event) { - event.preventDefault(); - const listElement = event.currentTarget; - Ajax.apiOnce({ - data: { - actionName: "changeStyle", - className: "wcf\\data\\style\\StyleAction", - objectIDs: [listElement.dataset.styleId], - }, - success: function () { - window.location.reload(); - }, - }); + async #changeStyle(style) { + const styleId = parseInt(style.dataset.styleId, 10); + await (0, ChangeStyle_1.changeStyle)(styleId); + window.location.reload(); } } let controllerStyleChanger; @@ -82,6 +58,6 @@ define(["require", "exports", "tslib", "../../Ajax", "../../Language", "../../Ui * Loads and displays the style change dialog. */ function showDialog(event) { - controllerStyleChanger.showDialog(event); + void controllerStyleChanger.showDialog(event); } }); diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index 3e860c32740..b48e2813ebe 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -169,6 +169,8 @@ static function (\wcf\event\endpoint\ControllerCollecting $event) { $event->register(new \wcf\system\endpoint\controller\core\styles\DisableStyle()); $event->register(new \wcf\system\endpoint\controller\core\styles\EnableStyle()); $event->register(new \wcf\system\endpoint\controller\core\styles\SetStyleAsDefault()); + $event->register(new \wcf\system\endpoint\controller\core\styles\ChangeStyle()); + $event->register(new \wcf\system\endpoint\controller\core\styles\GetStyleChooser()); $event->register(new \wcf\system\endpoint\controller\core\users\options\DeleteOption()); $event->register(new \wcf\system\endpoint\controller\core\users\options\DisableOption()); $event->register(new \wcf\system\endpoint\controller\core\users\options\EnableOption()); diff --git a/wcfsetup/install/files/lib/command/style/ChangeStyle.class.php b/wcfsetup/install/files/lib/command/style/ChangeStyle.class.php new file mode 100644 index 00000000000..10a51bedd07 --- /dev/null +++ b/wcfsetup/install/files/lib/command/style/ChangeStyle.class.php @@ -0,0 +1,61 @@ + + * @since 6.3 + */ +final class ChangeStyle +{ + public function __construct( + private readonly Style $style + ) {} + + public function __invoke(): void + { + StyleHandler::getInstance()->changeStyle($this->style->styleID); + if (StyleHandler::getInstance()->getStyle()->styleID !== $this->style->styleID) { + // style could not be changed + return; + } + + if (WCF::getUser()->userID) { + $this->saveUserStyle($this->style->styleID, (bool)$this->style->isDefault); + } else { + $this->saveGuestStyle($this->style->styleID, (bool)$this->style->isDefault); + } + + $event = new StyleChanged($this->style); + EventHandler::getInstance()->fire($event); + } + + private function saveUserStyle(int $styleID, bool $isDefaultStyle): void + { + (new UserAction([WCF::getUser()], 'update', [ + 'data' => [ + 'styleID' => $isDefaultStyle ? 0 : $styleID, + ], + ]))->executeAction(); + } + + private function saveGuestStyle(int $styleID, bool $isDefaultStyle): void + { + if ($isDefaultStyle) { + WCF::getSession()->unregister('styleID'); + } else { + WCF::getSession()->register('styleID', $styleID); + } + } +} diff --git a/wcfsetup/install/files/lib/data/style/StyleAction.class.php b/wcfsetup/install/files/lib/data/style/StyleAction.class.php index 11200c0fc82..1c9b7b2e036 100644 --- a/wcfsetup/install/files/lib/data/style/StyleAction.class.php +++ b/wcfsetup/install/files/lib/data/style/StyleAction.class.php @@ -3,11 +3,11 @@ namespace wcf\data\style; use ParagonIE\ConstantTime\Hex; +use wcf\command\style\ChangeStyle; use wcf\command\style\DisableStyle; use wcf\command\style\EnableStyle; use wcf\data\AbstractDatabaseObjectAction; use wcf\data\IToggleAction; -use wcf\data\user\UserAction; use wcf\system\exception\PermissionDeniedException; use wcf\system\image\ImageHandler; use wcf\system\request\LinkHandler; @@ -519,6 +519,8 @@ public function copy() * Validates parameters to change user style. * * @return void + * + * @deprecated 6.3 */ public function validateChangeStyle() { @@ -532,33 +534,20 @@ public function validateChangeStyle() * Changes user style. * * @return void + * + * @deprecated 6.3 Use the `ChangeStyle` command instead. */ public function changeStyle() { - StyleHandler::getInstance()->changeStyle($this->style->styleID); - if (StyleHandler::getInstance()->getStyle()->styleID == $this->style->styleID) { - if (WCF::getUser()->userID) { - // set this as the permanent style - $userAction = new UserAction([WCF::getUser()], 'update', [ - 'data' => [ - 'styleID' => $this->style->isDefault ? 0 : $this->style->styleID, - ], - ]); - $userAction->executeAction(); - } else { - if ($this->style->isDefault) { - WCF::getSession()->unregister('styleID'); - } else { - WCF::getSession()->register('styleID', $this->style->styleID); - } - } - } + (new ChangeStyle($this->style))(); } /** * Validates the 'getStyleChooser' action. * * @return void + * + * @deprecated 6.3 */ public function validateGetStyleChooser() { @@ -569,6 +558,8 @@ public function validateGetStyleChooser() * Returns the style chooser dialog. * * @return array{actionName: string, template: string} + * + * @deprecated 6.3 Use the `\wcf\system\endpoint\controller\core\styles\GetStyleChooser` API-Endpoint instead. */ public function getStyleChooser() { diff --git a/wcfsetup/install/files/lib/event/style/StyleChanged.class.php b/wcfsetup/install/files/lib/event/style/StyleChanged.class.php new file mode 100644 index 00000000000..52446b6e3e1 --- /dev/null +++ b/wcfsetup/install/files/lib/event/style/StyleChanged.class.php @@ -0,0 +1,19 @@ + + * @since 6.3 + */ +final class StyleChanged implements IPsr14Event +{ + public function __construct(public readonly Style $style) {} +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/ChangeStyle.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/ChangeStyle.class.php new file mode 100644 index 00000000000..4cd6e0f8732 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/ChangeStyle.class.php @@ -0,0 +1,44 @@ + + * @since 6.3 + */ +#[PostRequest('/core/styles/{id:\d+}/change')] +final class ChangeStyle implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $style = Helper::fetchObjectFromRequestParameter($variables['id'], Style::class); + + $this->assertStyleCanBeChanged($style); + + (new \wcf\command\style\ChangeStyle($style))(); + + return new JsonResponse([]); + } + + private function assertStyleCanBeChanged(Style $style): void + { + if ($style->isDisabled && !WCF::getSession()->getPermission('admin.style.canUseDisabledStyle')) { + throw new PermissionDeniedException(); + } + } +} diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/GetStyleChooser.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/GetStyleChooser.class.php new file mode 100644 index 00000000000..b1c32b91817 --- /dev/null +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/GetStyleChooser.class.php @@ -0,0 +1,47 @@ + + * @since 6.3 + */ +#[GetRequest('/core/styles/chooser')] +final class GetStyleChooser implements IController +{ + #[\Override] + public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface + { + $styleList = $this->getStyles(); + + return new JsonResponse([ + 'template' => WCF::getTPL()->render('wcf', 'styleChooser', [ + 'styleList' => $styleList, + ]), + ]); + } + + private function getStyles(): StyleList + { + $styleList = new StyleList(); + if (!WCF::getSession()->getPermission('admin.style.canUseDisabledStyle')) { + $styleList->getConditionBuilder()->add("style.isDisabled = ?", [0]); + } + $styleList->sqlOrderBy = "style.styleName ASC"; + $styleList->readObjects(); + + return $styleList; + } +}