Convert `Acp/Ui/Devtools/Project/Sync` to TypeScript
authorAlexander Ebert <ebert@woltlab.com>
Sun, 3 Jan 2021 11:46:49 +0000 (12:46 +0100)
committerAlexander Ebert <ebert@woltlab.com>
Sun, 3 Jan 2021 11:46:49 +0000 (12:46 +0100)
wcfsetup/install/files/js/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js
wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js [deleted file]
wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts [new file with mode: 0644]
wcfsetup/install/files/ts/WoltLabSuite/Core/Ajax/Data.ts
wcfsetup/install/files/ts/WoltLabSuite/Core/Ajax/Request.ts

index b571bd770be0287f8377212218941fc9448f4bd5..e98b833bf9aab0bf3f8b9437186b228e6e7c8140 100644 (file)
-define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog', 'Ui/Notification'], function (Ajax, Dictionary, Language, UiDialog, UiNotification) {
+define(["require", "exports", "tslib", "../../../../Ajax", "../../../../Language", "../../../../Ui/Dialog", "../../../../Ui/Notification"], function (require, exports, tslib_1, Ajax, Language, Dialog_1, UiNotification) {
     "use strict";
-    var _buttons = new Dictionary();
-    var _buttonStatus = new Dictionary();
-    var _buttonSyncAll = null;
-    var _container = elById('syncPipMatches');
-    var _pips = [];
-    var _projectId = 0;
-    var _queue = [];
-    return {
-        init: function (projectId) {
-            _projectId = projectId;
-            elById('syncShowOnlyMatches').addEventListener('change', function () {
-                _container.classList.toggle('jsShowOnlyMatches');
+    Object.defineProperty(exports, "__esModule", { value: true });
+    exports.init = void 0;
+    Ajax = tslib_1.__importStar(Ajax);
+    Language = tslib_1.__importStar(Language);
+    Dialog_1 = tslib_1.__importDefault(Dialog_1);
+    UiNotification = tslib_1.__importStar(UiNotification);
+    class AcpUiDevtoolsProjectSync {
+        constructor(projectId) {
+            this.buttons = new Map();
+            this.buttonStatus = new Map();
+            this.buttonSyncAll = undefined;
+            this.container = document.getElementById("syncPipMatches");
+            this.pips = [];
+            this.queue = [];
+            this.projectId = projectId;
+            const restrictedSync = document.getElementById("syncShowOnlyMatches");
+            restrictedSync.addEventListener("change", () => {
+                this.container.classList.toggle("jsShowOnlyMatches");
             });
-            var existingPips = [], knownPips = [], tmpPips = [];
-            elBySelAll('.jsHasPipTargets:not(.jsSkipTargetDetection)', _container, (function (pip) {
-                var pluginName = elData(pip, 'plugin-name');
-                var targets = [];
-                elBySelAll('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePip', _container, (function (button) {
-                    var target = elData(button, 'target');
+            const existingPips = [];
+            const knownPips = [];
+            const tmpPips = [];
+            this.container
+                .querySelectorAll(".jsHasPipTargets:not(.jsSkipTargetDetection)")
+                .forEach((pip) => {
+                const pluginName = pip.dataset.pluginName;
+                const targets = [];
+                this.container
+                    .querySelectorAll(`.jsHasPipTargets[data-plugin-name="${pluginName}"] .jsInvokePip`)
+                    .forEach((button) => {
+                    const target = button.dataset.target;
                     targets.push(target);
-                    button.addEventListener('click', (function (event) {
+                    button.addEventListener("click", (event) => {
                         event.preventDefault();
-                        if (_queue.length > 0)
+                        if (this.queue.length > 0) {
                             return;
-                        this._sync(pluginName, target);
-                    }).bind(this));
-                    _buttons.set(pluginName + '-' + target, button);
-                    _buttonStatus.set(pluginName + '-' + target, elBySel('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePipResult[data-target="' + target + '"]', _container));
-                }).bind(this));
-                var data = {
-                    dependencies: JSON.parse(elData(pip, 'sync-dependencies')),
-                    pluginName: pluginName,
-                    targets: targets
+                        }
+                        this.sync(pluginName, target);
+                    });
+                    const identifier = this.getButtonIdentifier(pluginName, target);
+                    this.buttons.set(identifier, button);
+                    this.buttonStatus.set(identifier, this.container.querySelector(`.jsHasPipTargets[data-plugin-name="${pluginName}"] .jsInvokePipResult[data-target="${target}"]`));
+                });
+                const data = {
+                    dependencies: JSON.parse(pip.dataset.syncDependencies),
+                    pluginName,
+                    targets,
                 };
                 if (data.dependencies.length > 0) {
                     tmpPips.push(data);
                 }
                 else {
-                    _pips.push(data);
+                    this.pips.push(data);
                     knownPips.push(pluginName);
                 }
                 existingPips.push(pluginName);
-            }).bind(this));
-            var resolvedDependency = false;
+            });
+            let resolvedDependency = false;
             while (tmpPips.length > 0) {
                 resolvedDependency = false;
-                var openDependencies, item, length = tmpPips.length;
-                for (var i = 0; i < length; i++) {
-                    item = tmpPips[i];
-                    openDependencies = item.dependencies.filter(function (dependency) {
+                tmpPips.forEach((item, index) => {
+                    if (resolvedDependency) {
+                        return;
+                    }
+                    const openDependencies = item.dependencies.filter((dependency) => {
                         // Ignore any dependencies that are not present.
                         if (existingPips.indexOf(dependency) === -1) {
-                            window.console.info('The dependency "' + dependency + '" does not exist and has been ignored.');
+                            window.console.info(`The dependency "${dependency}" does not exist and has been ignored.`);
                             return false;
                         }
-                        return (knownPips.indexOf(dependency) === -1);
+                        return !knownPips.includes(dependency);
                     });
                     if (openDependencies.length === 0) {
                         knownPips.push(item.pluginName);
-                        _pips.push(item);
-                        tmpPips.splice(i, 1);
+                        this.pips.push(item);
+                        tmpPips.splice(index, 1);
                         resolvedDependency = true;
-                        break;
                     }
-                }
+                });
                 if (!resolvedDependency) {
                     // We could not resolve any dependency, either because there is no more pip
                     // in `tmpPips` or we're facing a circular dependency. In case there are items
                     // left, we simply append them to the end and hope for the operation to
                     // complete anyway, despite unmatched dependencies.
-                    tmpPips.forEach(function (pip) {
-                        window.console.warn('Unable to resolve dependencies for', pip);
-                        _pips.push(pip);
+                    tmpPips.forEach((pip) => {
+                        window.console.warn("Unable to resolve dependencies for", pip);
+                        this.pips.push(pip);
                     });
                     break;
                 }
             }
-            var syncAll = elCreate('li');
-            syncAll.innerHTML = '<a href="#" class="button"><span class="icon icon16 fa-refresh"></span> ' + Language.get('wcf.acp.devtools.sync.syncAll') + '</a>';
-            _buttonSyncAll = syncAll.children[0];
-            _buttonSyncAll.addEventListener('click', this._syncAll.bind(this));
-            var list = elBySel('.contentHeaderNavigation > ul');
-            list.insertBefore(syncAll, list.firstElementChild);
-        },
-        _sync: function (pluginName, target) {
-            _buttons.get(pluginName + '-' + target).disabled = true;
-            _buttonStatus.get(pluginName + '-' + target).innerHTML = '<span class="icon icon16 fa-spinner"></span>';
+            const syncAll = document.createElement("li");
+            syncAll.innerHTML = `<a href="#" class="button"><span class="icon icon16 fa-refresh"></span> ${Language.get("wcf.acp.devtools.sync.syncAll")}</a>`;
+            this.buttonSyncAll = syncAll.children[0];
+            this.buttonSyncAll.addEventListener("click", this.syncAll.bind(this));
+            const list = document.querySelector(".contentHeaderNavigation > ul");
+            list.insertAdjacentElement("afterbegin", syncAll);
+        }
+        sync(pluginName, target) {
+            const identifier = this.getButtonIdentifier(pluginName, target);
+            this.buttons.get(identifier).disabled = true;
+            this.buttonStatus.get(identifier).innerHTML = '<span class="icon icon16 fa-spinner"></span>';
             Ajax.api(this, {
                 parameters: {
-                    pluginName: pluginName,
-                    target: target
-                }
+                    pluginName,
+                    target,
+                },
             });
-        },
-        _syncAll: function (event) {
+        }
+        syncAll(event) {
             event.preventDefault();
-            if (_buttonSyncAll.classList.contains('disabled')) {
+            if (this.buttonSyncAll.classList.contains("disabled")) {
                 return;
             }
-            _buttonSyncAll.classList.add('disabled');
-            _queue = [];
-            _pips.forEach(function (pip) {
-                pip.targets.forEach(function (target) {
-                    _queue.push([pip.pluginName, target]);
+            this.buttonSyncAll.classList.add("disabled");
+            this.queue = [];
+            this.pips.forEach((pip) => {
+                pip.targets.forEach((target) => {
+                    this.queue.push([pip.pluginName, target]);
                 });
             });
-            this._syncNext();
-        },
-        _syncNext: function () {
-            if (_queue.length === 0) {
-                _buttonSyncAll.classList.remove('disabled');
+            this.syncNext();
+        }
+        syncNext() {
+            if (this.queue.length === 0) {
+                this.buttonSyncAll.classList.remove("disabled");
                 UiNotification.show();
                 return;
             }
-            var next = _queue.shift();
-            this._sync(next[0], next[1]);
-        },
-        _ajaxSuccess: function (data) {
-            _buttons.get(data.returnValues.pluginName + '-' + data.returnValues.target).disabled = false;
-            _buttonStatus.get(data.returnValues.pluginName + '-' + data.returnValues.target).innerHTML = data.returnValues.timeElapsed;
-            this._syncNext();
-        },
-        _ajaxFailure: function (data, responseText, xhr, requestData) {
-            _buttons.get(requestData.parameters.pluginName + '-' + requestData.parameters.target).disabled = false;
-            var buttonStatus = _buttonStatus.get(requestData.parameters.pluginName + '-' + requestData.parameters.target);
-            buttonStatus.innerHTML = '<a href="#">' + Language.get('wcf.acp.devtools.sync.status.failure') + '</a>';
-            buttonStatus.children[0].addEventListener('click', (function (event) {
+            const next = this.queue.shift();
+            this.sync(next[0], next[1]);
+        }
+        getButtonIdentifier(pluginName, target) {
+            return `${pluginName}-${target}`;
+        }
+        _ajaxSuccess(data) {
+            const identifier = this.getButtonIdentifier(data.returnValues.pluginName, data.returnValues.target);
+            this.buttons.get(identifier).disabled = false;
+            this.buttonStatus.get(identifier).innerHTML = data.returnValues.timeElapsed;
+            this.syncNext();
+        }
+        _ajaxFailure(data, responseText, xhr, requestData) {
+            const identifier = this.getButtonIdentifier(requestData.parameters.pluginName, requestData.parameters.target);
+            this.buttons.get(identifier).disabled = false;
+            const buttonStatus = this.buttonStatus.get(identifier);
+            buttonStatus.innerHTML = '<a href="#">' + Language.get("wcf.acp.devtools.sync.status.failure") + "</a>";
+            buttonStatus.children[0].addEventListener("click", (event) => {
                 event.preventDefault();
-                UiDialog.open(this, Ajax.getRequestObject(this).getErrorHtml(data, xhr));
-            }).bind(this));
-            _buttonSyncAll.classList.remove('disabled');
-        },
-        _ajaxSetup: function () {
+                Dialog_1.default.open(this, Ajax.getRequestObject(this).getErrorHtml(data, xhr));
+            });
+            this.buttonSyncAll.classList.remove("disabled");
+            return false;
+        }
+        _ajaxSetup() {
             return {
                 data: {
-                    actionName: 'invoke',
-                    className: 'wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction',
+                    actionName: "invoke",
+                    className: "wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction",
                     parameters: {
-                        projectID: _projectId
-                    }
-                }
+                        projectID: this.projectId,
+                    },
+                },
             };
-        },
-        _dialogSetup: function () {
+        }
+        _dialogSetup() {
             return {
-                id: 'devtoolsProjectSyncPipError',
+                id: "devtoolsProjectSyncPipError",
                 options: {
-                    title: Language.get('wcf.global.error.title')
+                    title: Language.get("wcf.global.error.title"),
                 },
-                source: null
+                source: null,
             };
         }
-    };
+    }
+    let acpUiDevtoolsProjectSync;
+    function init(projectId) {
+        if (!acpUiDevtoolsProjectSync) {
+            acpUiDevtoolsProjectSync = new AcpUiDevtoolsProjectSync(projectId);
+        }
+    }
+    exports.init = init;
 });
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.js
deleted file mode 100644 (file)
index 8f2cb3f..0000000
+++ /dev/null
@@ -1,200 +0,0 @@
-define(['Ajax', 'Dictionary', 'Language', 'Ui/Dialog', 'Ui/Notification'], function (Ajax, Dictionary, Language, UiDialog, UiNotification) {
-       "use strict";
-       
-       var _buttons = new Dictionary();
-       var _buttonStatus = new Dictionary();
-       var _buttonSyncAll = null;
-       var _container = elById('syncPipMatches');
-       var _pips = [];
-       var _projectId = 0;
-       var _queue = [];
-       
-       return {
-               init: function (projectId) {
-                       _projectId = projectId;
-                       
-                       
-                       elById('syncShowOnlyMatches').addEventListener('change', function() {
-                               _container.classList.toggle('jsShowOnlyMatches');
-                       });
-                       
-                       var existingPips = [], knownPips = [], tmpPips = [];
-                       elBySelAll('.jsHasPipTargets:not(.jsSkipTargetDetection)', _container, (function (pip) {
-                               var pluginName = elData(pip, 'plugin-name');
-                               var targets = [];
-                               
-                               elBySelAll('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePip', _container, (function(button) {
-                                       var target = elData(button, 'target');
-                                       targets.push(target);
-                                       
-                                       button.addEventListener('click', (function(event) {
-                                               event.preventDefault();
-                                               
-                                               if (_queue.length > 0) return;
-                                               
-                                               this._sync(pluginName, target);
-                                       }).bind(this));
-                                       
-                                       _buttons.set(pluginName + '-' + target, button);
-                                       _buttonStatus.set(pluginName + '-' + target, elBySel('.jsHasPipTargets[data-plugin-name="' + pluginName + '"] .jsInvokePipResult[data-target="' + target + '"]', _container));
-                               }).bind(this));
-                               
-                               var data = {
-                                       dependencies: JSON.parse(elData(pip, 'sync-dependencies')),
-                                       pluginName: pluginName,
-                                       targets: targets
-                               };
-                               
-                               if (data.dependencies.length > 0) {
-                                       tmpPips.push(data);
-                               }
-                               else {
-                                       _pips.push(data);
-                                       knownPips.push(pluginName);
-                               }
-                               
-                               existingPips.push(pluginName);
-                       }).bind(this));
-                       
-                       var resolvedDependency = false;
-                       while (tmpPips.length > 0) {
-                               resolvedDependency = false;
-                               
-                               var openDependencies, item, length = tmpPips.length;
-                               for (var i = 0; i < length; i++) {
-                                       item = tmpPips[i];
-                                       
-                                       openDependencies = item.dependencies.filter(function (dependency) {
-                                               // Ignore any dependencies that are not present.
-                                               if (existingPips.indexOf(dependency) === -1) {
-                                                       window.console.info('The dependency "' + dependency + '" does not exist and has been ignored.');
-                                                       return false;
-                                               }
-                                               
-                                               return (knownPips.indexOf(dependency) === -1);
-                                       });
-                                       
-                                       if (openDependencies.length === 0) {
-                                               knownPips.push(item.pluginName);
-                                               _pips.push(item);
-                                               tmpPips.splice(i, 1);
-                                               
-                                               resolvedDependency = true;
-                                               break;
-                                       }
-                               }
-                               
-                               if (!resolvedDependency) {
-                                       // We could not resolve any dependency, either because there is no more pip
-                                       // in `tmpPips` or we're facing a circular dependency. In case there are items
-                                       // left, we simply append them to the end and hope for the operation to
-                                       // complete anyway, despite unmatched dependencies.
-                                       tmpPips.forEach(function(pip) {
-                                               window.console.warn('Unable to resolve dependencies for', pip);
-                                               
-                                               _pips.push(pip);
-                                       });
-                                       
-                                       break;
-                               }
-                       }
-                       
-                       var syncAll = elCreate('li');
-                       syncAll.innerHTML = '<a href="#" class="button"><span class="icon icon16 fa-refresh"></span> ' + Language.get('wcf.acp.devtools.sync.syncAll') + '</a>';
-                       _buttonSyncAll = syncAll.children[0];
-                       _buttonSyncAll.addEventListener('click', this._syncAll.bind(this));
-                       
-                       var list = elBySel('.contentHeaderNavigation > ul');
-                       list.insertBefore(syncAll, list.firstElementChild);
-               },
-               
-               _sync: function (pluginName, target) {
-                       _buttons.get(pluginName + '-' + target).disabled = true;
-                       _buttonStatus.get(pluginName + '-' + target).innerHTML = '<span class="icon icon16 fa-spinner"></span>';
-                       
-                       Ajax.api(this, {
-                               parameters: {
-                                       pluginName: pluginName,
-                                       target: target
-                               }
-                       });
-               },
-               
-               _syncAll: function (event) {
-                       event.preventDefault();
-                       
-                       if (_buttonSyncAll.classList.contains('disabled')) {
-                               return;
-                       }
-                       
-                       _buttonSyncAll.classList.add('disabled');
-                       
-                       _queue = [];
-                       _pips.forEach(function(pip) {
-                               pip.targets.forEach(function (target) {
-                                       _queue.push([pip.pluginName, target]);
-                               });
-                       });
-                       this._syncNext();
-               },
-               
-               _syncNext: function () {
-                       if (_queue.length === 0) {
-                               _buttonSyncAll.classList.remove('disabled');
-                               
-                               UiNotification.show();
-                               
-                               return;
-                       }
-                       
-                       var next = _queue.shift();
-                       this._sync(next[0], next[1]);
-               },
-               
-               _ajaxSuccess: function(data) {
-                       _buttons.get(data.returnValues.pluginName + '-' + data.returnValues.target).disabled = false;
-                       _buttonStatus.get(data.returnValues.pluginName + '-' + data.returnValues.target).innerHTML = data.returnValues.timeElapsed;
-                       
-                       this._syncNext();
-               },
-               
-               _ajaxFailure: function (data, responseText, xhr, requestData) {
-                       _buttons.get(requestData.parameters.pluginName + '-' + requestData.parameters.target).disabled = false;
-                       
-                       var buttonStatus = _buttonStatus.get(requestData.parameters.pluginName + '-' + requestData.parameters.target);
-                       buttonStatus.innerHTML = '<a href="#">' + Language.get('wcf.acp.devtools.sync.status.failure') + '</a>';
-                       buttonStatus.children[0].addEventListener('click', (function (event) {
-                               event.preventDefault();
-                               
-                               UiDialog.open(
-                                       this,
-                                       Ajax.getRequestObject(this).getErrorHtml(data, xhr)
-                               );
-                       }).bind(this));
-                       
-                       _buttonSyncAll.classList.remove('disabled');
-               },
-               
-               _ajaxSetup: function () {
-                       return {
-                               data: {
-                                       actionName: 'invoke',
-                                       className: 'wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction',
-                                       parameters: {
-                                               projectID: _projectId
-                                       }
-                               }
-                       }
-               },
-               
-               _dialogSetup: function() {
-                       return {
-                               id: 'devtoolsProjectSyncPipError',
-                               options: {
-                                       title: Language.get('wcf.global.error.title')
-                               },
-                               source: null
-                       }
-               }
-       };
-});
diff --git a/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts b/wcfsetup/install/files/ts/WoltLabSuite/Core/Acp/Ui/Devtools/Project/Sync.ts
new file mode 100644 (file)
index 0000000..516bb23
--- /dev/null
@@ -0,0 +1,260 @@
+import * as Ajax from "../../../../Ajax";
+import * as Language from "../../../../Language";
+import UiDialog from "../../../../Ui/Dialog";
+import * as UiNotification from "../../../../Ui/Notification";
+import { AjaxCallbackSetup, AjaxResponseException } from "../../../../Ajax/Data";
+import { DialogCallbackSetup } from "../../../../Ui/Dialog/Data";
+
+interface PipData {
+  dependencies: string[];
+  pluginName: string;
+  targets: string[];
+}
+
+type PendingPip = [string, string];
+
+interface AjaxResponse {
+  returnValues: {
+    pluginName: string;
+    target: string;
+    timeElapsed: string;
+  };
+}
+
+interface RequestData {
+  parameters: {
+    pluginName: string;
+    target: string;
+  };
+}
+
+class AcpUiDevtoolsProjectSync {
+  private readonly buttons = new Map<string, HTMLButtonElement>();
+  private readonly buttonStatus = new Map<string, HTMLElement>();
+  private buttonSyncAll?: HTMLAnchorElement = undefined;
+  private readonly container = document.getElementById("syncPipMatches")!;
+  private readonly pips: PipData[] = [];
+  private readonly projectId: number;
+  private queue: PendingPip[] = [];
+
+  constructor(projectId) {
+    this.projectId = projectId;
+
+    const restrictedSync = document.getElementById("syncShowOnlyMatches") as HTMLInputElement;
+    restrictedSync.addEventListener("change", () => {
+      this.container.classList.toggle("jsShowOnlyMatches");
+    });
+
+    const existingPips: string[] = [];
+    const knownPips: string[] = [];
+    const tmpPips: PipData[] = [];
+    this.container
+      .querySelectorAll(".jsHasPipTargets:not(.jsSkipTargetDetection)")
+      .forEach((pip: HTMLTableRowElement) => {
+        const pluginName = pip.dataset.pluginName!;
+        const targets: string[] = [];
+
+        this.container
+          .querySelectorAll(`.jsHasPipTargets[data-plugin-name="${pluginName}"] .jsInvokePip`)
+          .forEach((button: HTMLButtonElement) => {
+            const target = button.dataset.target!;
+            targets.push(target);
+
+            button.addEventListener("click", (event) => {
+              event.preventDefault();
+
+              if (this.queue.length > 0) {
+                return;
+              }
+
+              this.sync(pluginName, target);
+            });
+
+            const identifier = this.getButtonIdentifier(pluginName, target);
+            this.buttons.set(identifier, button);
+            this.buttonStatus.set(
+              identifier,
+              this.container.querySelector(
+                `.jsHasPipTargets[data-plugin-name="${pluginName}"] .jsInvokePipResult[data-target="${target}"]`,
+              ) as HTMLElement,
+            );
+          });
+
+        const data: PipData = {
+          dependencies: JSON.parse(pip.dataset.syncDependencies!),
+          pluginName,
+          targets,
+        };
+
+        if (data.dependencies.length > 0) {
+          tmpPips.push(data);
+        } else {
+          this.pips.push(data);
+          knownPips.push(pluginName);
+        }
+
+        existingPips.push(pluginName);
+      });
+
+    let resolvedDependency = false;
+    while (tmpPips.length > 0) {
+      resolvedDependency = false;
+
+      tmpPips.forEach((item, index) => {
+        if (resolvedDependency) {
+          return;
+        }
+
+        const openDependencies = item.dependencies.filter((dependency) => {
+          // Ignore any dependencies that are not present.
+          if (existingPips.indexOf(dependency) === -1) {
+            window.console.info(`The dependency "${dependency}" does not exist and has been ignored.`);
+            return false;
+          }
+
+          return !knownPips.includes(dependency);
+        });
+
+        if (openDependencies.length === 0) {
+          knownPips.push(item.pluginName);
+          this.pips.push(item);
+          tmpPips.splice(index, 1);
+
+          resolvedDependency = true;
+        }
+      });
+
+      if (!resolvedDependency) {
+        // We could not resolve any dependency, either because there is no more pip
+        // in `tmpPips` or we're facing a circular dependency. In case there are items
+        // left, we simply append them to the end and hope for the operation to
+        // complete anyway, despite unmatched dependencies.
+        tmpPips.forEach((pip) => {
+          window.console.warn("Unable to resolve dependencies for", pip);
+
+          this.pips.push(pip);
+        });
+
+        break;
+      }
+    }
+
+    const syncAll = document.createElement("li");
+    syncAll.innerHTML = `<a href="#" class="button"><span class="icon icon16 fa-refresh"></span> ${Language.get(
+      "wcf.acp.devtools.sync.syncAll",
+    )}</a>`;
+    this.buttonSyncAll = syncAll.children[0] as HTMLAnchorElement;
+    this.buttonSyncAll.addEventListener("click", this.syncAll.bind(this));
+
+    const list = document.querySelector(".contentHeaderNavigation > ul") as HTMLUListElement;
+    list.insertAdjacentElement("afterbegin", syncAll);
+  }
+
+  private sync(pluginName: string, target: string): void {
+    const identifier = this.getButtonIdentifier(pluginName, target);
+    this.buttons.get(identifier)!.disabled = true;
+    this.buttonStatus.get(identifier)!.innerHTML = '<span class="icon icon16 fa-spinner"></span>';
+
+    Ajax.api(this, {
+      parameters: {
+        pluginName,
+        target,
+      },
+    });
+  }
+
+  private syncAll(event: MouseEvent): void {
+    event.preventDefault();
+
+    if (this.buttonSyncAll!.classList.contains("disabled")) {
+      return;
+    }
+
+    this.buttonSyncAll!.classList.add("disabled");
+
+    this.queue = [];
+    this.pips.forEach((pip) => {
+      pip.targets.forEach((target) => {
+        this.queue.push([pip.pluginName, target]);
+      });
+    });
+    this.syncNext();
+  }
+
+  private syncNext(): void {
+    if (this.queue.length === 0) {
+      this.buttonSyncAll!.classList.remove("disabled");
+
+      UiNotification.show();
+
+      return;
+    }
+
+    const next = this.queue.shift()!;
+    this.sync(next[0], next[1]);
+  }
+
+  private getButtonIdentifier(pluginName: string, target: string): string {
+    return `${pluginName}-${target}`;
+  }
+
+  _ajaxSuccess(data: AjaxResponse): void {
+    const identifier = this.getButtonIdentifier(data.returnValues.pluginName, data.returnValues.target);
+    this.buttons.get(identifier)!.disabled = false;
+    this.buttonStatus.get(identifier)!.innerHTML = data.returnValues.timeElapsed;
+
+    this.syncNext();
+  }
+
+  _ajaxFailure(
+    data: AjaxResponseException,
+    responseText: string,
+    xhr: XMLHttpRequest,
+    requestData: RequestData,
+  ): boolean {
+    const identifier = this.getButtonIdentifier(requestData.parameters.pluginName, requestData.parameters.target);
+    this.buttons.get(identifier)!.disabled = false;
+
+    const buttonStatus = this.buttonStatus.get(identifier)!;
+    buttonStatus.innerHTML = '<a href="#">' + Language.get("wcf.acp.devtools.sync.status.failure") + "</a>";
+    buttonStatus.children[0].addEventListener("click", (event) => {
+      event.preventDefault();
+
+      UiDialog.open(this, Ajax.getRequestObject(this).getErrorHtml(data, xhr));
+    });
+
+    this.buttonSyncAll!.classList.remove("disabled");
+
+    return false;
+  }
+
+  _ajaxSetup(): ReturnType<AjaxCallbackSetup> {
+    return {
+      data: {
+        actionName: "invoke",
+        className: "wcf\\data\\package\\installation\\plugin\\PackageInstallationPluginAction",
+        parameters: {
+          projectID: this.projectId,
+        },
+      },
+    };
+  }
+
+  _dialogSetup(): ReturnType<DialogCallbackSetup> {
+    return {
+      id: "devtoolsProjectSyncPipError",
+      options: {
+        title: Language.get("wcf.global.error.title"),
+      },
+      source: null,
+    };
+  }
+}
+
+let acpUiDevtoolsProjectSync: AcpUiDevtoolsProjectSync;
+
+export function init(projectId: number): void {
+  if (!acpUiDevtoolsProjectSync) {
+    acpUiDevtoolsProjectSync = new AcpUiDevtoolsProjectSync(projectId);
+  }
+}
index 738ccb5764bbc48d7084a4c0396784900ca83c90..c5fa0b6f1d56fe5d88bef804bd4312be680bdf5b 100644 (file)
@@ -80,3 +80,20 @@ export interface RequestOptions {
 
   callbackObject?: AjaxCallbackObject | null;
 }
+
+interface PreviousException {
+  message: string;
+  stacktrace: string;
+}
+
+export interface AjaxResponseException extends ResponseData {
+  exceptionID?: string;
+  previous: PreviousException[];
+  file?: string;
+  line?: number;
+  message: string;
+  returnValues?: {
+    description?: string;
+  };
+  stacktrace?: string;
+}
index 71fc024db4435d214009fa8c70e7323b3af7bc84..366393ebc04d4054f87d24e786d58f60faae3138 100644 (file)
  */
 
 import * as AjaxStatus from "./Status";
-import { ResponseData, RequestOptions, RequestData } from "./Data";
+import { ResponseData, RequestOptions, RequestData, AjaxResponseException } from "./Data";
 import * as Core from "../Core";
 import DomChangeListener from "../Dom/Change/Listener";
 import DomUtil from "../Dom/Util";
 import * as Language from "../Language";
 
-interface PreviousException {
-  message: string;
-  stacktrace: string;
-}
-
-interface AjaxResponseException extends ResponseData {
-  exceptionID?: string;
-  previous: PreviousException[];
-  file?: string;
-  line?: number;
-  message: string;
-  returnValues?: {
-    description?: string;
-  };
-  stacktrace?: string;
-}
-
 let _didInit = false;
 let _ignoreAllErrors = false;