Add action for RPC calls
authorMarcel Werk <burntime@woltlab.com>
Wed, 18 Dec 2024 15:24:05 +0000 (16:24 +0100)
committerMarcel Werk <burntime@woltlab.com>
Wed, 18 Dec 2024 15:24:05 +0000 (16:24 +0100)
ts/WoltLabSuite/Core/Api/PostObject.ts
ts/WoltLabSuite/Core/Component/GridView/Action/Confirmation.ts [new file with mode: 0644]
ts/WoltLabSuite/Core/Component/GridView/Action/Rpc.ts [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Api/PostObject.js
wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Confirmation.js [new file with mode: 0644]
wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Rpc.js [new file with mode: 0644]
wcfsetup/install/files/lib/system/gridView/action/ActionConfirmationType.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/gridView/action/RpcAction.class.php [new file with mode: 0644]

index f5e47c9973767c9c9d6c56f08b08982bd75f0cb8..7d2365b897235f18390e770fbe5e467aace9a135 100644 (file)
 import { prepareRequest } from "WoltLabSuite/Core/Ajax/Backend";
 import { ApiResult, apiResultFromError, apiResultFromValue } from "./Result";
 
-export async function postObject(endpoint: string): Promise<ApiResult<[]>> {
+type Payload = Blob | FormData | Record<string, unknown>;
+
+export async function postObject(endpoint: string, payload?: Payload): Promise<ApiResult<[]>> {
   try {
-    await prepareRequest(endpoint).post().fetchAsJson();
+    await prepareRequest(endpoint).post(payload).fetchAsJson();
   } catch (e) {
     return apiResultFromError(e);
   }
diff --git a/ts/WoltLabSuite/Core/Component/GridView/Action/Confirmation.ts b/ts/WoltLabSuite/Core/Component/GridView/Action/Confirmation.ts
new file mode 100644 (file)
index 0000000..69e5943
--- /dev/null
@@ -0,0 +1,51 @@
+import { confirmationFactory } from "WoltLabSuite/Core/Component/Confirmation";
+
+export enum ConfirmationType {
+  None = "None",
+  SoftDelete = "SoftDelete",
+  SoftDeleteWithReason = "SoftDeleteWithReason",
+  Restore = "Restore",
+  Delete = "Delete",
+  Custom = "Custom",
+}
+
+type ResultConfirmationWithReason = {
+  result: boolean;
+  reason?: string;
+};
+
+export async function handleConfirmation(
+  objectName: string,
+  confirmationType: ConfirmationType,
+  customMessage: string = "",
+): Promise<ResultConfirmationWithReason> {
+  if (confirmationType == ConfirmationType.SoftDelete) {
+    return await confirmationFactory().softDelete(objectName);
+  }
+
+  if (confirmationType == ConfirmationType.SoftDeleteWithReason) {
+    return await confirmationFactory().softDelete(objectName, true);
+  }
+
+  if (confirmationType == ConfirmationType.Restore) {
+    return {
+      result: await confirmationFactory().restore(objectName ? objectName : undefined),
+    };
+  }
+
+  if (confirmationType == ConfirmationType.Delete) {
+    return {
+      result: await confirmationFactory().delete(objectName ? objectName : undefined),
+    };
+  }
+
+  if (confirmationType == ConfirmationType.Custom) {
+    return {
+      result: await confirmationFactory().custom(customMessage).withoutMessage(),
+    };
+  }
+
+  return {
+    result: true,
+  };
+}
diff --git a/ts/WoltLabSuite/Core/Component/GridView/Action/Rpc.ts b/ts/WoltLabSuite/Core/Component/GridView/Action/Rpc.ts
new file mode 100644 (file)
index 0000000..6c09a62
--- /dev/null
@@ -0,0 +1,59 @@
+import { deleteObject } from "WoltLabSuite/Core/Api/DeleteObject";
+import { postObject } from "WoltLabSuite/Core/Api/PostObject";
+import { show as showNotification } from "WoltLabSuite/Core/Ui/Notification";
+import { ConfirmationType, handleConfirmation } from "./Confirmation";
+
+async function handleRpcAction(
+  row: HTMLTableRowElement,
+  objectName: string,
+  endpoint: string,
+  confirmationType: ConfirmationType,
+  customConfirmationMessage: string = "",
+): Promise<void> {
+  const confirmationResult = await handleConfirmation(objectName, confirmationType, customConfirmationMessage);
+  if (!confirmationResult.result) {
+    return;
+  }
+
+  if (confirmationType == ConfirmationType.Delete) {
+    const result = await deleteObject(endpoint);
+    if (!result.ok) {
+      return;
+    }
+  } else {
+    const result = await postObject(
+      endpoint,
+      confirmationResult.reason ? { reason: confirmationResult.reason } : undefined,
+    );
+    if (!result.ok) {
+      return;
+    }
+  }
+
+  if (confirmationType == ConfirmationType.Delete) {
+    row.remove();
+  } else {
+    row.dispatchEvent(
+      new CustomEvent("refresh", {
+        bubbles: true,
+      }),
+    );
+
+    // TODO: This shows a generic success message and should be replaced with a more specific message.
+    showNotification();
+  }
+}
+
+export function setup(table: HTMLTableElement): void {
+  table.addEventListener("action", (event: CustomEvent) => {
+    if (event.detail.action === "rpc") {
+      void handleRpcAction(
+        event.target as HTMLTableRowElement,
+        event.detail.objectName,
+        event.detail.endpoint,
+        event.detail.confirmationType,
+        event.detail.confirmationMessage,
+      );
+    }
+  });
+}
index 364e46f736945a58b4ebef75c6262c25833ae2ed..16316e806dcc6e39d6d76dd12e64eb9b28fabc76 100644 (file)
@@ -11,9 +11,9 @@ define(["require", "exports", "WoltLabSuite/Core/Ajax/Backend", "./Result"], fun
     "use strict";
     Object.defineProperty(exports, "__esModule", { value: true });
     exports.postObject = postObject;
-    async function postObject(endpoint) {
+    async function postObject(endpoint, payload) {
         try {
-            await (0, Backend_1.prepareRequest)(endpoint).post().fetchAsJson();
+            await (0, Backend_1.prepareRequest)(endpoint).post(payload).fetchAsJson();
         }
         catch (e) {
             return (0, Result_1.apiResultFromError)(e);
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Confirmation.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Confirmation.js
new file mode 100644 (file)
index 0000000..7920ecc
--- /dev/null
@@ -0,0 +1,41 @@
+define(["require", "exports", "WoltLabSuite/Core/Component/Confirmation"], function (require, exports, Confirmation_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.ConfirmationType = void 0;
+    exports.handleConfirmation = handleConfirmation;
+    var ConfirmationType;
+    (function (ConfirmationType) {
+        ConfirmationType["None"] = "None";
+        ConfirmationType["SoftDelete"] = "SoftDelete";
+        ConfirmationType["SoftDeleteWithReason"] = "SoftDeleteWithReason";
+        ConfirmationType["Restore"] = "Restore";
+        ConfirmationType["Delete"] = "Delete";
+        ConfirmationType["Custom"] = "Custom";
+    })(ConfirmationType || (exports.ConfirmationType = ConfirmationType = {}));
+    async function handleConfirmation(objectName, confirmationType, customMessage = "") {
+        if (confirmationType == ConfirmationType.SoftDelete) {
+            return await (0, Confirmation_1.confirmationFactory)().softDelete(objectName);
+        }
+        if (confirmationType == ConfirmationType.SoftDeleteWithReason) {
+            return await (0, Confirmation_1.confirmationFactory)().softDelete(objectName, true);
+        }
+        if (confirmationType == ConfirmationType.Restore) {
+            return {
+                result: await (0, Confirmation_1.confirmationFactory)().restore(objectName ? objectName : undefined),
+            };
+        }
+        if (confirmationType == ConfirmationType.Delete) {
+            return {
+                result: await (0, Confirmation_1.confirmationFactory)().delete(objectName ? objectName : undefined),
+            };
+        }
+        if (confirmationType == ConfirmationType.Custom) {
+            return {
+                result: await (0, Confirmation_1.confirmationFactory)().custom(customMessage).withoutMessage(),
+            };
+        }
+        return {
+            result: true,
+        };
+    }
+});
diff --git a/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Rpc.js b/wcfsetup/install/files/js/WoltLabSuite/Core/Component/GridView/Action/Rpc.js
new file mode 100644 (file)
index 0000000..4cf316a
--- /dev/null
@@ -0,0 +1,40 @@
+define(["require", "exports", "WoltLabSuite/Core/Api/DeleteObject", "WoltLabSuite/Core/Api/PostObject", "WoltLabSuite/Core/Ui/Notification", "./Confirmation"], function (require, exports, DeleteObject_1, PostObject_1, Notification_1, Confirmation_1) {
+    "use strict";
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.setup = setup;
+    async function handleRpcAction(row, objectName, endpoint, confirmationType, customConfirmationMessage = "") {
+        const confirmationResult = await (0, Confirmation_1.handleConfirmation)(objectName, confirmationType, customConfirmationMessage);
+        if (!confirmationResult.result) {
+            return;
+        }
+        if (confirmationType == Confirmation_1.ConfirmationType.Delete) {
+            const result = await (0, DeleteObject_1.deleteObject)(endpoint);
+            if (!result.ok) {
+                return;
+            }
+        }
+        else {
+            const result = await (0, PostObject_1.postObject)(endpoint, confirmationResult.reason ? { reason: confirmationResult.reason } : undefined);
+            if (!result.ok) {
+                return;
+            }
+        }
+        if (confirmationType == Confirmation_1.ConfirmationType.Delete) {
+            row.remove();
+        }
+        else {
+            row.dispatchEvent(new CustomEvent("refresh", {
+                bubbles: true,
+            }));
+            // TODO: This shows a generic success message and should be replaced with a more specific message.
+            (0, Notification_1.show)();
+        }
+    }
+    function setup(table) {
+        table.addEventListener("action", (event) => {
+            if (event.detail.action === "rpc") {
+                void handleRpcAction(event.target, event.detail.objectName, event.detail.endpoint, event.detail.confirmationType, event.detail.confirmationMessage);
+            }
+        });
+    }
+});
diff --git a/wcfsetup/install/files/lib/system/gridView/action/ActionConfirmationType.class.php b/wcfsetup/install/files/lib/system/gridView/action/ActionConfirmationType.class.php
new file mode 100644 (file)
index 0000000..7bc5e99
--- /dev/null
@@ -0,0 +1,25 @@
+<?php
+
+namespace wcf\system\gridView\action;
+
+enum ActionConfirmationType
+{
+    case None;
+    case SoftDelete;
+    case SoftDeleteWithReason;
+    case Restore;
+    case Delete;
+    case Custom;
+
+    public function toString(): string
+    {
+        return match ($this) {
+            self::None => 'None',
+            self::SoftDelete => 'SoftDelete',
+            self::SoftDeleteWithReason => 'SoftDeleteWithReason',
+            self::Restore => 'Restore',
+            self::Delete => 'Delete',
+            self::Custom => 'Custom',
+        };
+    }
+}
diff --git a/wcfsetup/install/files/lib/system/gridView/action/RpcAction.class.php b/wcfsetup/install/files/lib/system/gridView/action/RpcAction.class.php
new file mode 100644 (file)
index 0000000..ba02b0a
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+
+namespace wcf\system\gridView\action;
+
+use Closure;
+use wcf\action\ApiAction;
+use wcf\data\DatabaseObject;
+use wcf\data\ITitledObject;
+use wcf\system\gridView\AbstractGridView;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+
+/**
+ * Represents an action that call a rpc endpoint.
+ *
+ * @author      Marcel Werk
+ * @copyright   2001-2024 WoltLab GmbH
+ * @license     GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since       6.2
+ */
+class RpcAction extends AbstractAction
+{
+    public function __construct(
+        protected readonly string $endpoint,
+        protected readonly string|Closure $languageItem,
+        protected readonly ActionConfirmationType $confirmationType = ActionConfirmationType::None,
+        protected readonly string|Closure $confirmationMessage = '',
+        ?Closure $isAvailableCallback = null
+    ) {
+        parent::__construct($isAvailableCallback);
+    }
+
+    #[\Override]
+    public function render(mixed $row): string
+    {
+        \assert($row instanceof DatabaseObject);
+
+        if (\is_string($this->languageItem)) {
+            $label = WCF::getLanguage()->get($this->languageItem);
+        } else {
+            $label = ($this->languageItem)($row);
+        }
+
+        if (\is_string($this->confirmationMessage)) {
+            $confirmationMessage = WCF::getLanguage()->get($this->confirmationMessage);
+        } else {
+            $confirmationMessage = ($this->confirmationMessage)($row);
+        }
+
+        $endpoint = StringUtil::encodeHTML(
+            LinkHandler::getInstance()->getControllerLink(ApiAction::class, ['id' => 'rpc']) .
+                \sprintf($this->endpoint, $row->getObjectID())
+        );
+
+        if ($row instanceof ITitledObject) {
+            $objectName = StringUtil::encodeHTML($row->getTitle());
+        } else {
+            $objectName = '';
+        }
+
+        return <<<HTML
+            <button
+                type="button"
+                data-action="rpc"
+                data-object-name="{$objectName}"
+                data-endpoint="{$endpoint}"
+                data-confirmation-type="{$this->confirmationType->toString()}"
+                data-confirmation-message="{$confirmationMessage}"
+            >
+                {$label}
+            </button>
+            HTML;
+    }
+
+    #[\Override]
+    public function renderInitialization(AbstractGridView $gridView): ?string
+    {
+        $id = StringUtil::encodeJS($gridView->getID());
+
+        return <<<HTML
+            <script data-relocate="true">
+                require(['WoltLabSuite/Core/Component/GridView/Action/Rpc'], ({ setup }) => {
+                    setup(document.getElementById('{$id}_table'));
+                });
+            </script>
+            HTML;
+    }
+}